IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 1 - SQLAlchemy - Úvod a instalace

Dnes se budeme zabývat temným středověkem a vařit různé elixíry v malé alchymistické dílně pomocí novodobých surovin. Tedy uvedu tato slova na pravou míru. Zavedeme jistou abstrakci nad SQL kódem, která mírně usnadní další život programátora (nikoli chemika) a budeme pracovat s databázovými dotazy jako s běžným datovým typem v Pythonu.

sqlalchemy log - SQLAlchemy pro databáze v Pythonu

Předpokladem k tomuto článku je znalost Pythonu, objektového programování v Pythonu (nebo všeobecná znalost) a k tomu SQL databáze. Alespoň nějaké.

Jistě jste již slyšeli o ORM (Object-Relational Mapping) jako určité programové vrstvě mezi relační databází a objektovými typy. Existuje nesčetné množství knihoven, jež tento problém řeší. Jednou z nich je například Django.ORM nebo Tortoise ORM, ovšem pro tuto chvíli jsem zvolil SQLAlchemy a to hned z několika důvodů:

  • dlouhá historie (2006),
  • komplexnost,
  • poměrně snadné,
  • používané v mnoha aplikacích,
  • velmi dobrá rychlost,
  • bývá užívána ve frameworcích pro web (např. Flask), nikoli součástí

Instalace

Jak již je v Pythonu obvyklé, nejsnadněji knihovnu sqlalchemy přidáme do systému pomocí příkazu (obvyklá a notoricky známá akce):

pip3 install sqlalchemy

Instalaci můžeme provést i přes vývojové prostředí (klasická instalace balíčku). Např. v PyCharm: File => Settings... => Project: JmenoVasehoPro­jektu => Python Interpreter => + => zadání názvu balíčku do vyhledávací řádky - v tomto případně: SQLAlchemy => vybrání balíčku z nabídky => Install Package

První kroky

Vzhledem k tomu, že Alchemy (prostě budu pojmenování zkracovat) je velmi rozsáhlá a popsat komplexně její schopnosti by bylo v rozsahu set stránkové knihy, podíváme se jen na část. Hlavní cíl snažení je použitelnost v různých aplikacích.

Začneme s jednou třídou, jinak vzato, s jednou tabulkou v databázi a postupně budeme pokračovat a rozšiřovat do komplexnější struktury dat.

Prvotní úkol je připojit se k databázi nebo případně ji stvořit. Tedy založit databázový stroj, který se postará o práci s daty na nízké úrovni a nám bude jedno, který typ databáze se používá. Přesto se uchyluji k jednoduchosti a volím SQLite a její souborovou reprezentaci:

from sqlalchemy import create_engine

db = create_engine("sqlite:///database.db", echo=True)

# Varianty pro jiné systémy. Podobně lze použít například: Oracle, MS SQL atd...
db = create_engine('postgresql://uživatel:heslo@localhost:5432/mojedata')
db = create_engine('mysql://uživatel:heslo@localhost/databáze')

Tedy funkce create_engine() vrátí instanci Engine, jež lze dále přizpůsobovat dle dialektu databáze. Dále přes DBAPI se spojí se samotným databázovým strojem. Tohle je však trochu "vyšší dívčí" a možná se jí budeme věnovat mnohem později, pokud bude zájem.

Za zmínku stojí parametr echo, který umožní zobrazení všech výstupů Alchemy včetně prováděných dotazů, velmi vhodné pro další vyladění.

Připojení k databázi v Alchemy je tzv. líné, tedy k žádnému fyzickému spojení nedojde až do okamžiku nutnosti. Pro tento případ je volání metod z Engine jako jsou connect(), execute() atp. Nutno však upozornit, že prakticky se tyto metody nepoužívají přímo, nýbrž se toto odehrává za scénou.

Deklarativní mapování

Již samotná deklarace dat může proběhnout mnoha způsoby a zde si představíme snadný přístup na základě tříd. Budeme však potřebovat nějakou základní modelovou třídu jež umožní pracovat s daty. Tu Alchemy nabízí jako declarative_base, z níž vytvoříme prvotní objekt:

from sqlalchemy.orm import declarative_base
Base = declarative_base(db)

Metoda declarative_base() je ovšem pouze jistá zkratka odkazující na objekt registry. Stejně by to šlo zapsat třeba tímto způsobem:

from sqlalchemy.orm import registry

mapper_registry = registry()
Base = mapper_registry.generate_base()

Na druhou stranu proč si ztěžovat práci a navíc se odchylovat od tříd používáním jen nových typů. Pouze proto, že to jde?

Od třídy Base budeme dále odvozovat datové struktury. Abychom si předvedli něco použitelného, budeme pracovat s daty reálného programu pro skladovou evidenci (tedy velmi zjednodušenou verzi):

SQLAlchemy pro databáze v Pythonu

Každá tabulka bude mít vlastní třídu, kde budou definovány jednotlivé sloupce s jejich typem a jednu funkci, které bude vracet textovou reprezentaci dat.

Datové typy SQLAlchemy

Držíme se konceptu Pythonu. Vše je objekt, tedy i Alchymie se drží této receptury a používá vlastní datové typy, jimiž se odkazuje na nativní databáze. Pro základ bych mohl vyjmenovat datový typ Integer, Float, String, Datetime, Boolean... a spoustu dalších. Pozitivní je, že jsou si s Pythonem spřízněné svým chováním a toto přenáší i do databázových strojů bez ohledu na druh.

Deklarace bez relací

Pro nás bude důležitý objekt typu Column, který představuje jeden sloupek v tabulce (databázové) a může mít i další atributy, jako jsou klíče, indexy a podobně. Vlastně každý sloupek, který má být uložený bude tohoto typu:

from sqlalchemy import Column, Integer, Float

class Vat(Base):
    __tablename__ = "vat"                                            # Pojmenování tabulky v databázi

    vat_id = Column(Integer, primary_key=True, autoincrement=False)  # ID položky
    rate = Column(Float, nullable=False)                             # Sazba v procentech

    def __repr__(self):
        """Textová reprezentace řádku z tabulky"""
        return "<DPH : id={self.vat_id}; sazba={self.rate}>".format(self=self)

Nutností je definovat název tabulky __tablename__ = "tabulka". Pojmenování třídy neřeší uložení, tenhle problém spadá pod metadata a jistou interní chemii na bázi rtuti a olova. Přesto si pojďme trochu rozebrat programovou ukázku.

Proměnná vat_id je normální ID položky, který má být celočíselný. Navíc je to primární klíč (primary_key=True). Alchemy vyžaduje alespoň jeden atribut definovaný s primárním klíčem.

Též obvykle u těchto dat požadujeme zvýšení hodnoty při novém záznamu (autoincrement=True). Zde ovšem budou pouze 3 položky a budou pevné. Bez daně, nižší sazba (10 %) a vyšší sazba (21 %). Pokud se státní legislativa nějak nezmění, tyto položky jsou relativně stálé. rate je sloupek s reálným číslem typu Float, zde je prostě jen procentuální hodnota. Víc není co řešit.

Parametr nullable (jak již pojmenování naznačuje) nám říká, zda atribut může nabývat hodnoty NULL či nikoli.

Metoda __repr__(self) jen vrátí nějaký text, který obsahuje informace o datech v jednom řádku tabulky. Používám stručnou verzi formátovaných argumentů. Myslím, že vy, ostřílení Pythonýrové, byste našli i jiné možnosti, jak udělat hezký textový výstup 😃 Tato metoda však není povinná a klidně se bez ní obejdete - je použita jen pro pochopitelnější výstupy v rámci kurzu.

Několik poznámek k typům

Malý problém může nastat při použití dalších druhů databází. Například Oracle vyžaduje sekvenční identifikátor. To lze řešit kódem:

vat_id = Column(Integer, Sequence("vat_id_seq"), ...

Dále může potíže způsobit deklarace položky typu String, například:

note = Column(String)

se vygeneruje jako note VARCHAR, což třeba SQLite nebo PostgreSQL bude akceptovat. Ovšem MySQL by se to nelíbilo, neboť vyžaduje explicitně zadanou délku řetězce. Je tedy lepší použít preventivně:

note = Column(String(100))

Další možnosti

Data lze deklarovat třeba imperativním způsobem. Na rozdíl od klasického postupu, kde jsou "metadata" vytvářena odděleně, při použití Table se stávají součástí třídy.

Tento postup však nepatří mezi doporučované, proto od něj ustoupím a výhradně se budu věnovat pouze výše uvedenému stylu:

from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry

mapper_registry = registry()

vat_table = Table(
    'vat',  # název tabulky
    mapper_registry.metadata,  # Zaregistrujeme metadata a definujeme sloupky tabulky
    Column('vat_id', Integer, primary_key=True, autoincrement=False),
    Column('rate', Float),
)

# Prázdná třída postačí
class Vat:
    pass

# Namapujeme tabulku na třídu
mapper_registry.map_imperatively(Vat, vat_table)

Abych vás nepřehltil informacemi, dokončíme si naše deklarativní mapování v příští lekci 🙂

V příští lekci, SQLAlchemy - Session a základní práce s daty, si probereme Session a základní práci s daty.


 

Měl jsi s čímkoli problém? Zdrojový kód vzorové aplikace je ke stažení každých pár lekcí. Zatím pokračuj dál, a pak si svou aplikaci porovnej se vzorem a snadno oprav.

Všechny články v sekci
SQLAlchemy pro databáze v Pythonu
Přeskočit článek
(nedoporučujeme)
SQLAlchemy - Session a základní práce s daty
Článek pro vás napsal Virlupus
Avatar
Uživatelské hodnocení:
17 hlasů
Autor se věnuje webovým aplikacím, skladově-účetnímu softwaru, 3D grafice, lexiální analýze a parserování. Studuje fyziku na MFF UK. Učil IT na střední škole.
Aktivity