Ubuntu 12.04 „Precise Pangolin“
Ubuntu 10.04 „Lucid Lynx“
SQLAlchemy
ist ein Objekt-relationaler Mapper (ORM) und eine SQL-Werkzeugsammlung für Python.
SQLAlchemy legt sich dabei als Zwischenschicht zwischen die Anwendung und die relationale Datenbank. Der Vorteil ist dabei, dass sich via SQLAlchemy alle unterstützten Datenbanken einheitlich ansprechen lassen, da die Unterschiede in den SQL-Dialekten durch das Programm „ausgeglichen“ werden. Das ORM-Modul gilt als extrem leistungsfähig und ermöglicht auch komplexe „Mappings“ zwischen einer Datenbank und Python-Objekten. So nutzt z.B. auch die von ubuntuusers.de verwendete Portal-Software Inyoka SQLAlchemy für die Datenbankanbindung.
SQLAlchemy unterstützt eine Reihe von relationalen Datenbanken, wie z.B. SQLite, MySQL und PostgreSQL aus dem OpenSource Lager, aber auch proprietäre Datenbanken wie Orcale und MS SQL Server finden Unterstützung. Da die Anzahl der unterstützten Datenbanken in der Regel mit jeder Hauptversion von SQLAlchemy steigt, sollte man für weitere Details die jeweilige Dokumentation lesen.
SQLAlchemy ist in den Paketquellen enthalten und kann über
python-sqlalchemy (universe)
mit apturl
Paketliste zum Kopieren:
sudo apt-get install python-sqlalchemy
sudo aptitude install python-sqlalchemy
installiert werden[1].
Optional kann man auch die jeweils passende Dokumentation mit installieren:
python-sqlalchemy-doc (universe)
mit apturl
Paketliste zum Kopieren:
sudo apt-get install python-sqlalchemy-doc
sudo aptitude install python-sqlalchemy-doc
Da die Version aus den Paketquellen nicht unbedingt die neuste ist bzw. nicht unbedingt die neuste „Unterversion“ ist, so dass unter Umständen Fehlerkorrekturen fehlen, kann man SQLAlchemy alternativ auch über easy_install installieren.
Egal ob man SQLAlchemy über die Paketquellen oder manuell installiert - die entsprechenden Python-Module für die diversen Datenbanken müssen immer nachträglich installiert werden.
Die Anbindung an SQLite ist seit Python 2.5. - und damit bei allen unterstützen Ubuntu-Versionen - in Python selbst enthalten, so dass hier keine weitere Installation notwendig ist.
Ansonsten muss man eines oder mehrere der folgenden Pakete nachinstallieren:
für MySQL benötigt man das Paket python-mysqldb.
für PostgreSQL benötigt man eines der folgenden Pakete: python-psycopg2
für MS SQLServer und Sybase: python-pymssql (universe)
Weitere mögliche Treiber, welche vielleicht nicht in den Ubuntu-Quellen zu finden sind, listet die Seite Supported DB-APIs auf.
Wie in der Einleitung bereits erwähnt besteht SQLAlchemy aus mehreren Komponenten, die unabhängig von einander genutzt werden können. Man kann also z.B. auch „nur“ eine „Engine“ oder die „SQL Expression Language“ benutzen, ohne den kompletten ORM importieren zu müssen.
Alle folgenden Befehle können direkt in der interaktiven Python-Konsole ausgeführt werden, welche man durch den Aufruf von
python
im Terminal öffnet[2].
Die Engine , also die Anbindung an die Datenbank an sich, stellt mit die unterste Ebene der Benutzerschnittstelle von SQLAlchemy dar. Der Vorteil gegenüber einer „gewöhnlichen“ Verbindung ist, dass die Verbindung über SQLAlchemy je nach Notwendigkeit automatisch auf- und abgebaut wird und direkt auch ein Cursor-ähnliches Objekt zur Datenabfrage bereit stellt. Ebenso verwaltet SQLAlchemy automatisch mehrere Verbindungen zu einer Datenbank über den sogenannten „Connection Pool“.
Die Datenbank wird immer gemäß einem URL-ähnlichen Schema angegeben, das wie folgt aussieht:
DATENBANK://NUTZER:PASSWORD@SERVER:PORT/DATENBANKNAME
Der Port ist optional und kann weggelassen werden, so lang die Datenbank auf deren Standardport läuft.
Will sich der Benutzer otto
mit dem Passwort normal
also z.B. mit einem lokalen MySQL Datenbankserver und der Datenbank Namens test
verbinden, so sieht dies so aus:
mysql://otto:normal@localhost/test
Eine kleine Abweichung gibt es bei SQLite, da SQLite keine Benutzerverwaltung in dieser Form kennt und die Datenbank immer als lokale Datei vorliegt:
sqlite:///meine_db.db #relativer Pfad sqlite:////pfad/zu/meine_db.db #absoluter Pfad sqlite:// #keine Angaben = In-Memory Datenbank
Die Anbindung wird via SQLAlchemy so erzeugt:
1 2 | >>> from sqlalchemy import create_engine >>> engine = create_engine('mysql://otto:normal@localhost/test') |
Natürlich kennt create_engine
eine Vielzahl von Optionen und Parametern. Erwähnt werden soll hier aber nur echo=True
. Damit gibt sich SQLAlchemy besonders geschwätzig und gibt z.B. alle generierten SQL-Befehle auf Stdout, also in der Regel der Konsole, aus.
Nun kann über die Execute-Funktion ein SQL-Query abgesetzt werden, z.B.:
1 2 3 4 5 | >>> ergebnis = engine.execute("SELECT foo FROM bar WHERE spam = 'egg'") >>> for erg in ergebnis: ... print erg (Ausgabe der Ergebnisse) |
Egal ob man später das ORM-Modul oder die SQL Expression Language benutzen möchte, das Anlegen von Tabellen erfolgt in beiden Fällen gleich. Dazu speichert das Programm Daten, Schemen etc. zu Tabellen mit Hilfe sogenannter database metadata .
Im folgenden Beispiel werden die notwendigen Importe durchgeführt, im aktuellen Verzeichnis die SQLite Datenbanknamens uude.de angelegt, die Metadaten an die Engine gebunden und dann eine einfache Tabelle Namens "programme" angelegt, welche aus drei Spalten besteht:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | >>> from sqllchemy import create_engine, MetaData, Table, Column, Integer, String >>> engine = create_engine('sqlite:///uude.db', echo=True) >>> metadata = MetaData() >>> metadata.bind = engine >>> tab = Table('programme', metadata, ... Column('id', Integer, primary_key = True), ... Column('name',String(20)), ... Column('desktop',String(10))) >>> metadata.create_all() 2011-01-31 19:40:54,960 INFO sqlalchemy.engine.base.Engine.0x...8a8c PRAGMA table_info("programme") 2011-01-31 19:40:54,960 INFO sqlalchemy.engine.base.Engine.0x...8a8c () 2011-01-31 19:40:54,961 INFO sqlalchemy.engine.base.Engine.0x...8a8c CREATE TABLE programme ( id INTEGER NOT NULL, name VARCHAR(20), desktop VARCHAR(10), PRIMARY KEY (id) ) 2011-01-31 19:40:54,962 INFO sqlalchemy.engine.base.Engine.0x...8a8c () 2011-01-31 19:40:54,978 INFO sqlalchemy.engine.base.Engine.0x...8a8c COMMIT |
Wie man sieht gibt SQLAlchemy durch das Parameter echo=True
die abgesetzten SQL-Befehle in der Konsole aus. Der Befehl metadata.create_all()
legt dabei letztendlich die Tabelle an. Per Voreinstellung wird dabei immer zuerst automatisch geprüft, ob die Tabelle existiert, d.h. bestehende Daten werden nie gelöscht.
SQLAlchemy bietet auch die Möglichkeit, eine existierende Tabelle in die Metadaten zu übernehmen. Angenommen, in der gleichen Datenbank gäbe es bereits eine Tabelle "distributionen". Dann würde der Befehl
1 | >>> tab2 = Table('distributionen', metadata, autoload=True) |
diese automatisch tab2
zuordnen.
Nach dem Anlegen von Metadaten bzw. der Zuordnung zu einer Variablen sind die einzelnen Spalten einer Datenbank direkt ansprechbar. Im Falle der Tabelle "tab" und der Spalte "id" zum Beispiel so:
1 2 | >>> tab.c.id Column('id', Integer(), table=<programme>, primary_key=True, nullable=False) |
Das c
ist dabei die Kurzform für "Column".
Die SQL Expression Language ist, vereinfacht ausgedrückt, die „pythonische“ Variante von SQL. Das heißt es gibt diverse Python-Klassen und Funktionen, die die üblichen SQL-Befehl darstellen. Dies wird im folgenden anhand einiger kurzer Beispiele gezeigt.
Ausgehend von der weiter oben angelegten Tabelle wird diese mit ein paar Datensätzen befüllt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | >>> ergebnis = engine.execute(tab.insert(), name='Gedit', desktop='GNOME') 2011-01-31 20:11:24,238 INFO sqlalchemy.engine.base.Engine.0x...8a8c INSERT INTO programme (name, desktop) VALUES (?, ?) 2011-01-31 20:11:24,238 INFO sqlalchemy.engine.base.Engine.0x...8a8c ('Gedit', 'GNOME') 2011-01-31 20:11:24,239 INFO sqlalchemy.engine.base.Engine.0x...8a8c COMMIT >>> engine.execute(tab.insert(), name=Kate, desktop='KDE') 2011-01-31 20:11:38,700 INFO sqlalchemy.engine.base.Engine.0x...8a8c INSERT INTO programme (name, desktop) VALUES (?, ?) 2011-01-31 20:11:38,700 INFO sqlalchemy.engine.base.Engine.0x...8a8c ('Kate', 'KDE') 2011-01-31 20:11:38,702 INFO sqlalchemy.engine.base.Engine.0x...8a8c COMMIT <sqlalchemy.engine.base.ResultProxy object at 0x88e0d0c> >>> conn = engine.connect() >>> conn.execute(tab.insert(), [ ... {'name': 'Evolution', 'desktop': 'GNOME'}, ... {'name': 'KOffice', 'desktop': 'KDE'}, ... {'name': 'Thunar', 'desktop': 'Xfce'}]) 2011-01-31 20:18:07,228 INFO sqlalchemy.engine.base.Engine.0x...8a8c INSERT INTO programme (name, desktop) VALUES (?, ?) 2011-01-31 20:18:07,229 INFO sqlalchemy.engine.base.Engine.0x...8a8c (('Evolution', 'GNOME'), ('KOffice', 'KDE'), ('Thunar', 'Xfce')) 2011-01-31 20:18:07,230 INFO sqlalchemy.engine.base.Engine.0x...8a8c COMMIT <sqlalchemy.engine.base.ResultProxy object at 0x9989a4c> >>> conn.close() |
Wie man sieht kennt SQLAlchemy sowohl die explizite (mit connection
) und implizite (ohne connection
) Ausführung von Befehlen, ebenso kann das Ergebnis, der sogenannte "ResultProxy" einer Variablen zugeordnet werden, wie im ersten Befehl. Außerdem ist zu sehen, dass SQLAlchemy standardmäßig im „Auto-Commit“ Modus arbeitet, bei dem Befehle direkt in der Datenbank ausgeführt werden. Dieses Verhalten lässt sich natürlich durch das Setzen der entsprechenden Option ändern.
Da die Datenbank nun ein wenig Inhalt erhalten hat, können auch Abfragen durchgeführt werden:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | >>> from sqlalchemy import select >>> query = select([tab.c.name,tab.c.desktop]) >>> ergebnis = engine.execute(query) 2011-01-31 20:42:24,304 INFO sqlalchemy.engine.base.Engine.0x...8a6c SELECT programme.name, programme.desktop FROM programme 2011-01-31 20:42:24,305 INFO sqlalchemy.engine.base.Engine.0x...8a6c () >>> for erg in ergebnis: ... print erg['name'], erg['desktop'] ... Gedit GNOME Kate KDE Evolution GNOME KOffice KDE Thunar Xfce #nur GNOME Programme wählen: >>> query = select([tab.c.name,tab.c.desktop]) >>> query = query.where(tab.c.desktop == 'GNOME') >>> engine.execute(query).fetchall() 2011-01-31 20:45:44,692 INFO sqlalchemy.engine.base.Engine.0x...8a6c SELECT programme.name, programme.desktop FROM programme WHERE programme.desktop = ? 2011-01-31 20:45:44,693 INFO sqlalchemy.engine.base.Engine.0x...8a6c ('GNOME',) [(u'Gedit', u'GNOME'), (u'Evolution', u'GNOME')] |
Die Beispiele stellen natürlich nur einen Bruchteil der Funktionalität und Leistungsfähigkeit der SQL Expression Language dar. Viele weitere Beispiele findet man in der weiter oben verlinkten Dokumenation bzw. in dem dort befindlichen Tutorial.
Der Objekt-Relationale Mapper von SQLAlchemy ist eines der leistungsfähigsten Module. Mit seiner Hilfe können Tabellen (bzw. Spalten) von relationalen Datenbanken auf ein Python Objekt bzw. eine Python Klasse „gemappt“ d.h. abgebildet werden. Eine grundlegenden Einführung in den ORM findet man in der Dokumentation.
Das Arbeiten mit dem ORM erfolgt grundsätzlich in drei Schritten:
als erstes wird ein Mapper angelegt
dann wird eine sogenannte Session gestartet
nun können Abfrage gemacht werden, Einträge angelegt werden etc.
Im folgenden Beispiel wird ein sehr einfaches Mapping angelegt. Der Mapper von SQLAlchemy ist wesentlich leistungsfähiger und unterstützt unter anderem auch Relationen, Rückwärtsreferenzen, Joins, Vererbung etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | >>> class Programme(object): ... pass ... >>> from sqlalchemy.orm import mapper >>> mapper(Programme, tab) <Mapper at 0x88e0dcc; Programme> >>> neues_programm = Programme() >>> neues_programm.name = 'Totem' >>> neues_programm.desktop = 'GNOME' >>> neues_programm.desktop 'GNOME' >>> from sqlalchemy.orm import sessionmaker >>> Session = sessionmaker(bind=engine) >>> session = Session() >>> session.add(neues_programm) >>> session.commit() 2011-01-31 21:12:24,481 INFO sqlalchemy.engine.base.Engine.0x...8a6c BEGIN 2011-01-31 21:12:24,483 INFO sqlalchemy.engine.base.Engine.0x...8a6c INSERT INTO programme (name, desktop) VALUES (?, ?) 2011-01-31 21:12:24,484 INFO sqlalchemy.engine.base.Engine.0x...8a6c ('Totem', 'GNOME') 2011-01-31 21:12:24,488 INFO sqlalchemy.engine.base.Engine.0x...8a6c COMMIT >>> neues_programm.id 2011-01-31 21:12:37,969 INFO sqlalchemy.engine.base.Engine.0x...8a6c BEGIN 2011-01-31 21:12:37,970 INFO sqlalchemy.engine.base.Engine.0x...8a6c SELECT programme.id AS programme_id, programme.name AS programme_name, programme.desktop AS programme_desktop FROM programme WHERE programme.id = ? 2011-01-31 21:12:37,971 INFO sqlalchemy.engine.base.Engine.0x...8a6c (6,) 6 |
Wie man sieht reicht es, für das Mapping eine leere Klasse anzulegen. Aber natürlich kann man auch das sonst notwendige __init__
definieren sowie weitere Funktionen in der Klasse anlegen.
Abfragen sind so auch möglich:
1 2 3 4 5 6 7 8 9 10 11 | ergebnis = session.query(Programme.name).filter(Programme.desktop == 'GNOME') >>> for erg in ergebnis: ... print erg ... 2011-01-31 21:19:00,944 INFO sqlalchemy.engine.base.Engine.0x...8a6c SELECT programme.name AS programme_name FROM programme WHERE programme.desktop = ? 2011-01-31 21:19:00,944 INFO sqlalchemy.engine.base.Engine.0x...8a6c ('GNOME',) (u'Gedit',) (u'Evolution',) (u'Totem',) |
Weitere Informationen zu Abfragen via ORM findet man in der Dokumentation.
Alle obigen Beispiele zeigen nur einige wenige Grundlagen von SQLAlchemy. Wer sich näher mit SQLAlchemy beschäftigen möchte, dem sei die sehr gute und ausführliche, offizielle Dokumentaion empfohlen, welche zwei Tutorials, viele Detailinformationen und Beispiele sowie eine umfassende API-Referenz bietet.
Möchte man nachträglich ein ORM zusammen mit einer bestehenden Datenbank verwenden, so kann oder möchte man wahrscheinlich nicht mehr das gegebene Datenbankschema ändern. Natürlich ist die Verwendung von SQLAlchemy trotzdem ohne weiteres möglich, wenn man alle Tabellen „von Hand“ gegen die Datenbank mappt. Dies kann aber recht mühselig sein, besonders dann, wenn die Datenbank viele verschiedene Tabellen enthält.
An dieser Stelle kommt SqlSoup ins Spiel. Das Modul ist so etwas wie ein „Mapper im Vollautomatik-Modus“. Dies bedeutet, dass man mit einem einzelnen Befehl die komplette Mapping der Datenbank auf Python-Objekte erfolgt. Die Objekte werden dabei ebenfalls von SqlSoup angelegt. Im direkten Vergleich zum regulären Mapper bietet SqlSoup weniger Möglichkeiten, eben weil ein Großteil der Konfiguration automatisch läuft. Für viele Anwendungsfälle sollte es aber nichts desto trotz ausreichend sein.
SqlSoup ist zwar kein direkter Bestandteil von SQLAlchemy, ist aber trotzdem Paket enthalten, d.h. es wird immer mit SQLAlchemy zusammen installiert.
Im folgenden, kurzen Beispiel wird die weiter oben angelegte Datenbank "uude.db" mit Hilfe von SqlSoup automatisch auf Python Objekte gemappt, ein Abfrage gemacht, ein weiterer Eintrag hinzugefügt und zum Schluss werden alle Programme für den KDE Desktop angezeigt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> from sqlalchemy.ext.sqlsoup import SqlSoup >>> db = SqlSoup('sqlite:///uude.db') >>> db.Programme.all() [MappedProgramme(id=1,name=u'Gedit',desktop=u'GNOME'), MappedProgramme(id=2,name=u'Kate',desktop=u'KDE'), MappedProgramme(id=3,name=u'Evolution',desktop=u'GNOME'), MappedProgramme(id=4,name=u'KOffice',desktop=u'KDE'), MappedProgramme(id=5,name=u'Thunar',desktop=u'Xfce'), MappedProgramme(id=6,name=u'Totem',desktop=u'GNOME')] >>> db.Programme.insert(name='Amarok',desktop='KDE') MappedProgramme(id=None,name='Amarok',desktop='KDE') >>> db.flush() >>> db.Programme.all() [MappedProgramme(id=1,name=u'Gedit',desktop=u'GNOME'), MappedProgramme(id=2,name=u'Kate',desktop=u'KDE'), MappedProgramme(id=3,name=u'Evolution',desktop=u'GNOME'), MappedProgramme(id=4,name=u'KOffice',desktop=u'KDE'), MappedProgramme(id=5,name=u'Thunar',desktop=u'Xfce'), MappedProgramme(id=6,name=u'Totem',desktop=u'GNOME'), MappedProgramme(id=7,name='Amarok',desktop='KDE')] >>> for prog in db.Programme.filter(db.Programme.desktop=='KDE'): ... print prog.id, prog.name ... 2 Kate 4 KOffice 7 Amarok |
Eine vollständige Übersicht über das Modul findet man in der SqlSoup Dokumentation .
Ausführlicheres Tutorial , greift auch ein paar fortgeschrittene Themen auf
Elexir - Python-Bibliothek, welche auf SQLAlchemy aufbaut und ein vereinfachtes Mapping zwischen Datenbank und Python Objekten bietet
Alternativen:
Storm - ein ORM, welches von Canonical entwickelt wird