Az SQLAlchemy a Pythonhoz írt open source Objektum-relációs leképző (ORM) rendszer. Széleskörű, adatbázis-független réteget nyújt, kezdve az egyszerű lekérdezések generálásától az átfogó, akár többszörös összekapcsoláson át egészen a táblák alapvető információinak kinyeréséig. Könnyű használhatósága és teljesítménye miatt ez a ma leggyakrabban használt ORM eszköz a Python programnyelvhez.
Az SQLAlchemy egyik nagy előnye, hogy képes mind rendkívül magas, mind pedig alacsony absztrakciót nyújtani attól függően, hogy az adott rendszer milyen elvárásokat támaszt. Ennek elérése érdekében a hibernate-ben is alkalmazott Data Mapper mintát használja. A leggyakrabban használt Python keretrendszerek támogatást biztosítanak hozzá, így a fejlesztők válláról lekerülhetnek a munkamenet-kezeléssel, illetve a perzisztenciával, felhasználókezeléssel kapcsolatos gyakoribb problémák.
Column('remote_user_id', Integer, ForeignKey(users.c.user_id))
SQLAlchemy-ben a Column osztály reprezentálja a tábla egy-egy oszlopát, attribútumai pedig az oszlop tulajdonságait határozzák meg. Az osztálynak kötelezően meg kell adni, hogy mi az oszlop neve, típusa. Ezeken felül további paraméterekben megadhatóak egyéb tulajdonságok, mint például az alapértelmezett érték, külső kulcs definíciók, indexelés. Az oszlop osztályoknak kulcsszerepe van lekérdezések esetén, ugyanis ezek segítségével adhatóak meg a lekérdezés legfőbb tulajdonságai, például hogy milyen oszlopok szerepelnek a lekérdezésben, illetve mik a lekérdezés feltételei.
Ezen osztály reprezentál egy, az adatbázisban szereplő táblát. A Table osztálynak kötelezően meg kell adni a tábla nevét, amelyet az adatbázisban használ, illetve a tábla oszlopait. A Table osztály ezen kívül lehetőséget ad különböző események kezelőinek megadására (<esemény>, <kezelő_függvény>) formátumban. Ezeket az eseménykezelőket a konstruktor listeners paraméteréhez kell adni, mint listát. Például:
Table(
'sometable',
autoload=True,
listeners=[
('column_reflect', listen_for_reflect)
])
Megfeleltetés (angolul mapping) segítségével az egyes Table osztályok megfeleltethetőek python osztályoknak. Ekkor az adott tábla oszlopai a megadott python osztály attribútumaival vannak reprezentálva. Az SQLAlchemy megköveteli, hogy minden ily módon létrehozott megfeleltető osztálynak legyen legalább egy elsődleges kulcs oszlopa, azaz nem lehetséges olyan tábla megfeleltetése, amelynek nincs meghatározva legalább egy elsődleges kulcsa. Megfeleltetés után az adott táblára a megfeleltető osztályon keresztül hivatkozhatunk, de mint python osztály. Például:
my_table = Table("my_table", metadata,
Column('id', Integer, primary_key=True),
Column('type', String(50)),
Column("some_alt", Integer)
)
class MyClass(object):
pass
mapper(MyClass, my_table,
polymorphic_on=my_table.c.type,
properties={
'alt':my_table.c.some_alt
})
végrehajtása után a MyClass.alt attribútum megfeleltethető az adatbázisban szereplő my_table tábla some_alt oszlopával.
Mappelt osztályok létrehozásának egyszerűbb módja, ha olyan osztályokat hozunk létre, amelyek a declarative osztályból származnak. Ekkor a fentebb leírt három lépés helyett egy egyszerű lépésben létrehozhatók mapper osztályok. Minden ilyen mapper osztály egy közös ősből származik. Ezt az ős osztályt az SQLAlchemy declarative_base függvénye adja. Minden leszármazott osztálynak meg kell határoznia, hogy milyen adatbázis táblát reprezentál, ez pedig a __tablename__ attribútummal tehető meg. Az osztályban megadhatóak a tábla oszlopai, esetleges relációi, melyek az adott osztály attribútumaiként fognak viselkedni. A mappelt tábla az osztály __table__ attribútuma segítségével érhető el. Például:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class SomeClass(Base):
__tablename__ = 'some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
A fenti esetben a Base osztályt az SQLAlchemy szolgáltatja. A SomeClass osztály az adatbázisban szereplő 'some_table' táblát reprezentálja, a name attribútuma pedig ezen tábla name oszlopát. Ez esetben a Column osztálynak nem szükséges nevet megadni, az SQLAlchemy ilyenkor az azt megfeleltető attribútum nevét fogja az oszlophoz rendelni.
A következő példa két tábla (mozik és rendezők), illetve a köztük lévő n-1 kapcsolat megvalósítását mutatja be. Látható, hogyan hozható létre a felhasználó által definiált osztályokból az adatbázis tábla, illetve hogyan lehet az adott táblához oszlopokat rendelni, ezen oszlopok tulajdonságait (például típus) megadni, lekérdezni, illetve adatbázisba menteni.
Két tábla létrehozása:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation, sessionmaker
Base = declarative_base()
class Movie(Base):
__tablename__ = 'movies'
id = Column(Integer, primary_key=True)
title = Column(String(255), nullable=False)
year = Column(Integer)
directed_by = Column(Integer, ForeignKey('directors.id'))
director = relation("Director", backref='movies', lazy=False)
def __init__(self, title=None, year=None):
self.title = title
self.year = year
def __repr__(self):
return "Movie(%r, %r, %r)" % (self.title, self.year, self.director)
class Director(Base):
__tablename__ = 'directors'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False, unique=True)
def __init__(self, name=None):
self.name = name
def __repr__(self):
return "Director(%r)" % (self.name)
engine = create_engine('dbms://user:pwd@host/dbname')
Base.metadata.create_all(engine)
Látható, hogy az egyes táblákat egy-egy osztály reprezentálja, a táblák oszlopait pedig ezen osztályok attribútumai. A __tablename__ attribútum határozza meg, hogy az adatbázisban a tábla milyen néven fog létrejönni. Egy oszlopot a Column osztály reprezentál, melynek átadhatók az adott oszlop főbb tulajdonságai. Így például a
Column(Integer, primary_key=True)
parancs egy olyan oszlopot definiál a táblában, melynek típusa integer, és elsődleges kulcs. Az osztályok közötti relációkat speciálisan a relationship függvény segítségével adhatjuk meg. Ezen függvénynek különböző paramétereivel a kapcsolat több tulajdonságát is definiálhatjuk. A függvény első paramétere a tábla neve, vagy a táblát reprezentáló osztály. A kapcsolattól függően szükséges lehet magának a kapcsolási feltételnek a beállítása is. A backref segítségével megadható, hogy a kapcsolat másik oldaláról (jelen esetben a Director osztály) milyen attribútumon keresztül érhető el a reláció (jelen esetben a rendező filmjei). A lazy feltétel segítségével az SQLAlchemy-t informálható, hogy a relációban szereplő adatokat mikor töltse be az adatbázisból.True esetén a relációban szereplő adatok a reláció első eléréskor kerülnek betöltésre, False esetében pedig közvetlen az objektum feltöltésekor.
Adatok beszúrása az egyes táblákba:
Session = sessionmaker(bind=engine)
session = Session()
m1 = Movie("Star Trek", 2009)
m1.director = Director("JJ Abrams")
d2 = Director("George Lucas")
d2.movies = [Movie("Star Wars", 1977), Movie("THX 1138", 1971)]
try:
session.add(m1)
session.add(d2)
session.commit()
except:
session.rollback()
Hasonlóan a java-ban használt Hibernate-hez itt is az adatbázis műveleteket a session-ok végzik. Minden session alapja az úgynevezett engine, amely az adott adatbázissal való kapcsolatot reprezentálja, az általunk megadott connection string segítségével. A táblákba beszúrás annyit jelent, hogy az általunk létrehozott osztályok megfelelő attribútumait feltöltjük az értékekkel, majd beszúrjuk őket az adott adatbázishoz tartozó session-be. A session nem csak a beszúrást, de a perzisztenciát is kezeli, így már létező rekordot reprezentáló osztályon végzett módosítás akár azonnal megjelenhet az adatbázisban is. A session-ök nagy előnye, hogy viszonylag egyszerűen bővíthetőek, vagyis egyszerűen adható hozzá új funkcionalitás. Többek között speciális eseménykezelő függvények, amelyek flush, commit, vagy a sessionhoz tartozó valamely objektum változása esetén hívódnak meg.
alldata = session.query(Movie).all()
for somedata in alldata:
print somedata
Az SQLAlchemy a következő lekérdezést küldi el az adatbázis kezelőnek:
SELECT movies.id, movies.title, movies.year, movies.directed_by, directors.id, directors.name
FROM movies LEFT OUTER JOIN directors ON directors.id = movies.directed_by
A végeredmény:
Movie('Star Trek', 2009L, Director('JJ Abrams'))
Movie('Star Wars', 1977L, Director('George Lucas'))
Movie('THX 1138', 1971L, Director('George Lucas'))
Itt megfigyelhető a relációban szereplő lazy feltétel hatása. True esetében ugyan lekérdezné az egyes mozikat, de nem kérdezné le a mozik rendezőit csak akkor, amikor a rendező relációt először használjuk. False esetén a mozik lekérdezésével egy időben minden mozihoz lekérdezi a rendezőt is.
Magának a lekérdezésnek az alapja a query osztály. Egy-egy ilyen query-t a session segítségével építünk fel. Maga a query osztály létrehozása nem egyenlő a lekérdezés futtatásával, a lekérdezés futtatása akár a query létrehozása után is történhet. Futtatásra többféle parancs használható, például az all, amely minden eredményt lekérdez, vagy a first, ami csak akkor ad eredményt, ha létezik, egyébként hibát kapunk. Maga a query függvény egy osztályt, vagy oszlopok (Column) sorozatát várja paraméterül. Lekérdezéshez feltételeket a filter[1] függvény segítségével kapcsolhatunk. Maga a lekérdezés elkészítése a mapper osztályok segítségével rendkívül egyszerű. Például ha csak azon mozikat szeretnénk lekérdezni, amelyek címe 'Star Wars', akkor azt a következőképpen tehetjük meg:
alldata = session.query(Movie).filter(Movie.title=='Star Wars').all()
for somedata in alldata:
print somedata
Lekérdezéshez tábla kapcsolása a join[2] segítségével végezhető el. A join-nak nem feltétlenül kell kapcsolási feltételt megadni, ha a kapcsolás egyértelmű, akkor azt a definíciókból az SQLAlchemy magától elvégzi.