1. diel - SQLAlchemy - Úvod a inštalácia
Dnes sa budeme zaoberať temným stredovekom a variť rôzne elixíry v malej alchymistickej dielni pomocou novodobých surovín. Teda uvediem tieto slová na pravú mieru. Zavedieme istú abstrakciu nad SQL kódom, ktorá mierne uľahčí ďalší život programátora (nie chemika) a budeme pracovať s databázovými otázkami ako s bežným dátovým typom v Pythone.
Predpokladom k tomuto článku je znalosť Pythona, objektového programovania v Pythone (alebo všeobecná znalosť) ak tomu SQL databázy. Aspoň nejaké.
Iste ste už počuli o ORM (O bject- R
elational M apping) ako určitej programovej vrstve medzi
relačnou databázou a objektovými typmi. Existuje nespočetné množstvo
knižníc, ktoré tento problém riešia. Jednou z nich je napríklad
Django.ORM
alebo Tortoise
ORM, avšak pre túto chvíľu som zvolil SQLAlchemy a to hneď z niekoľkých
dôvodov:
- dlhá história (2006),
- komplexnosť,
- pomerne ľahké,
- používané v mnohých aplikáciách,
- veľmi dobrá rýchlosť,
- býva používaná vo frameworkoch pre web (napr. Flask), nie súčasťou
Inštalácia
Ako už je v Pythone obvyklé, najľahšie knižnicusqlalchemy
pridáme do systému pomocou príkazu (obvyklá a notoricky známa akcia):
pip3 install sqlalchemy
Inštaláciu môžeme vykonať aj cez vývojové prostredie (klasická inštalácia balíčka). Napr. v PyCharm: File => Settings... => Project: MenoVasehoProjektu = > Python Interpreter => + => zadanie názvu balíčka do vyhľadávacieho riadku - v tomto prípade: SQLAlchemy => vyberie balíček z ponuky = > Install Package
Prvé kroky
Vzhľadom k tomu, že Alchemy (jednoducho budem pomenovanie skracovať) je veľmi rozsiahla a opísať komplexne jej schopnosti by bolo v rozsahu stoviek stránkovej knihy, pozrieme sa len na časť. Hlavný cieľ snaženia je použiteľnosť v rôznych aplikáciách.Začneme s jednou triedou, inak vzaté, s jednou tabuľkou v databáze a postupne budeme pokračovať a rozširovať do komplexnejšej štruktúry dát.
Prvotná úloha je pripojiť sa k databáze alebo prípadne ju stvoriť. Teda založiť databázový stroj, ktorý sa postará o prácu s dátami na nízkej úrovni a nám bude jedno, ktorý typ databázy sa používa. Napriek tomu sa uchyľujem k jednoduchosti a volím SQLite a jej súborovú reprezentáciu:
from sqlalchemy import create_engine db = create_engine("sqlite:///database.db", echo=True) # Varianty pre iné systémy. Podobne je možné použiť napríklad: Oracle, MS SQL atď... db = create_engine('postgresql://uživatel:heslo@localhost:5432/mojedata') db = create_engine('mysql://uživatel:heslo@localhost/databáze')
Teda funkcia create_engine()
vráti inštanciu Engine
,
ktorá sa dá ďalej prispôsobovať podľa dialektu databázy. Ďalej cez
DBAPI
sa spoja so samotným databázovým strojom. Toto je však
trochu "vyššia dievčenská" a možno sa jej budeme venovať oveľa neskôr,
ak bude záujem.
Za zmienku stojí parameter echo
, ktorý umožní zobrazenie
všetkých výstupov Alchemy vrátane vykonávaných otázok, veľmi
vhodné pre ďalšie vyladenie.
Pripojenie k databáze v Alchemy je tzv. lenivé, teda k žiadnemu
fyzickému spojeniu nedôjde až do okamihu nutnosti. Pre tento prípad je
volanie metód z Engine
ako sú connect()
,
execute()
atp. Treba však upozorniť, že prakticky sa tieto
metódy nepoužívajú priamo, ale sa toto odohráva za scénou.
Deklaratívne mapovanie
Už samotná deklarácia dát môže prebehnúť mnohými spôsobmi a tu si predstavíme ľahký prístup na základe tried. Budeme však potrebovať nejakú základnú modelovú triedu, ktorá umožní pracovať s dátami. Tú Alchemy ponúka akodeclarative_base
, z ktorej vytvoríme
prvotný objekt:
from sqlalchemy.orm import declarative_base Base = declarative_base(db)
Metóda declarative_base()
je však iba istá skratka
odkazujúca na objekt registry
. Rovnako by to šlo zapísať
napríklad týmto spôsobom:
from sqlalchemy.orm import registry mapper_registry = registry() Base = mapper_registry.generate_base()
Na druhú stranu prečo sa sťažovať prácu a navyše sa odchyľovať od tried používaním len nových typov. Iba preto, že to ide?
Od triedy Base
budeme ďalej odvodzovať dátové štruktúry.
Aby sme si predviedli niečo použiteľné, budeme pracovať s dátami reálneho
programu pre skladovú evidenciu (teda veľmi zjednodušenú verziu):
Každá tabuľka bude mať vlastnú triedu, kde budú definované jednotlivé stĺpce s ich typom a jednu funkciu, ktoré bude vracať textovú reprezentáciu dát.
Dátové typy SQLAlchemy
Držíme sa konceptu Pythona. Všetko je objekt, teda aj Alchýmia sa drží tejto receptúry a používa vlastné dátové typy, ktorými sa odkazuje na natívne databázy. Pre základ by som mohol vymenovať dátový typInteger
, Float
, String
,
Datetime
, Boolean
... a mnoho
ďalších. Pozitívne je, že sú si s Pythonom spriaznené svojim
správaním a toto prenáša aj do databázových strojov bez ohľadu na druh.
Deklarácia bez relácií
Pre nás bude dôležitý objekt typuColumn
, ktorý predstavuje
jeden stĺpik v tabuľke (databázovej) a môže mať aj ďalšie atribúty, ako
sú kľúče, indexy a podobne. Vlastne každý stĺpik, ktorý má byť
uložený bude tohto typu:
from sqlalchemy import Column, Integer, Float class Vat(Base): __tablename__ = "vat" # Pomenovanie tabuľky v databáze vat_id = Column(Integer, primary_key=True, autoincrement=False) # ID položky rate = Column(Float, nullable=False) # Sadzba v percentách def __repr__(self): """Textová reprezentácia riadku z tabuľky""" return "<DPH : id={self.vat_id}; sadzba={self.rate}>".format(self=self)
Nutnosťou je definovať názov tabuľky
__tablename__ = "tabulka"
. Pomenovanie triedy nerieši uloženie,
tento problém spadá pod metadata
a istú internú chémiu na
báze ortuti a olova. Napriek tomu si poďme trochu rozobrať programovú
ukážku.
Premenná vat_id
je normálne ID položky, ktorý má byť
celočíselný. Navyše je to primárny kľúč (primary_key=True
).
Alchemy vyžaduje aspoň jeden atribút definovaný s primárnym
kľúčom.
Tiež obvykle u týchto dát požadujeme zvýšenie hodnoty pri novom
zázname (autoincrement=True
). Tu však budú iba 3 položky a
budú pevné. Bez dane, nižšia sadzba (10 %) a vyššia sadzba (21 %). Pokiaľ
sa štátna legislatíva nejako nezmení, tieto položky sú relatívne stále.
rate
je stĺpik s reálnym číslom typu Float
, tu je
jednoducho len percentuálna hodnota. Viac nie je čo riešiť.
Parameter nullable
(ako už pomenovanie naznačuje) nám
hovorí, či atribút môže nadobúdať hodnoty NULL
alebo
nie.
Metóda __repr__(self)
len vráti nejaký text, ktorý obsahuje
informácie o dátach v jednom riadku tabuľky. Používam stručnú verziu
formátovaných argumentov. Myslím, že vy, ostrieľaní Pythonieri, by ste
našli aj iné možnosti, ako urobiť pekný textový výstup 😃 Táto metóda
však nie je povinná a pokojne sa bez nej zaobídete - je použitá len pre
pochopiteľnejšie výstupy v rámci kurzu.
Niekoľko poznámok k typom
Malý problém môže nastať pri použití ďalších druhov databáz. Napríklad Oracle vyžaduje sekvenčný identifikátor. To je možné riešiť kódom:vat_id = Column(Integer, Sequence("vat_id_seq"), ...
Ďalej môže problémy spôsobiť deklaráciu položky typu
String
, napríklad:
note = Column(String)
sa vygeneruje ako note VARCHAR
, čo napríklad SQLite alebo PostgreSQL bude akceptovať. Avšak MySQL by sa to nepáčilo, pretože vyžaduje explicitne
zadanú dĺžku reťazca. Je teda lepšie použiť preventívne:
note = Column(String(100))
Ďalšie možnosti
Dáta je možné deklarovať napríklad imperatívnym spôsobom. Na rozdiel od klasického postupu, kde sú "metadatá" vytvárané oddelene, pri použitíTable
sa stávajú súčasťou triedy.
Tento postup však nepatrí medzi odporúčané, preto od neho ustúpim a výhradne sa budem venovať iba vyššie uvedenému štýlu:
from sqlalchemy import Table, Column, Integer, String, ForeignKey from sqlalchemy.orm import registry mapper_registry = registry() vat_table = Table( 'vat', # názov tabuľky mapper_registry.metadata, # Zaregistrujeme metadáta a definujeme stĺpiky tabuľky Column('vat_id', Integer, primary_key=True, autoincrement=False), Column('rate', Float), ) # Prázdna trieda postačí class Vat: pass # Namapujeme tabuľku na triedu mapper_registry.map_imperatively(Vat, vat_table)
Aby som vás neprehltil informáciami, dokončíme si naše deklaratívne mapovanie v budúcej lekcii 🙂
V budúcej lekcii, SQLAlchemy - Session a základné práce s dátami , si preberieme Session a základnú prácu s dátami.