Allseits beliebt

Die leistungsfähige Python-Bibliothek SQLAlchemy

Markus Jais

Spätestens seit Javas Hibernate und Ruby On Rails‘Active Record sind objektrelationale Mapper und Persistenz-Frameworks in aller Munde. Auch für Python gibt es einige solcher Frameworks. Eines, welches kürzlich dazu gekommen ist und sich schnell zum Liebling vieler Python-Programmierer entwickelt, ist SQLAlchemy.

SQLAlchemy ist ein relativ junges Projekt und trotzdem ist es vielen Python-Programmierern bereits ans Herz gewachsen. Es handelt sich dabei um ein umfangreiches, aber trotzdem einfach zu benutzendes Framework für den Umgang mit relationalen Datenbanken zusammen mit Python. Schauen wir uns nun aber selbst an, was dieses Framework mit dem etwas mystischen Namen dem Programmierer zu bieten hat. Dieser Artikel basiert auf Version 0.28 von SQLAlchemy. Es ist gut möglich, dass es zum Zeitpunkt, wenn Sie diesen Artikel lesen, bereits eine neuere Version gibt. Ich hoffe aber, dass die gezeigten Beispiele auch dann noch unverändert laufen.

Beispiel-Datenbank

Um die Fähigkeiten von SQLAlchemy demonstrieren zu können, benötigen wir erst einmal eine Datenbank mit einigen Tabellen. Als Beispiel dient uns die Verwaltung einer einfachen Buch-Sammlung. Das reicht für unsere Zwecke, um einige der Features von SQLAlchemy zu demonstrieren. Wir werden uns hier nur auf die Datenbankanbindung konzentrieren. Falls Sie SQLAlchemy für Webanwendungen verwenden wollen, empfehle ich Ihnen einen Blick auf Turbogears zu werfen. Dort wird SQLAlchemy mittlerweile unterstützt und auch für Django ist einen Integration von SQLAlchemy geplant.

Wenn wir eine völlig neue Anwendung entwickeln, kann uns SQLAlchemy auch dabei helfen, die Datenbanktabellen zu erstellen und die Beziehungen der einzelnen Tabellen festzulegen. Falls Sie bereits eine Datenbank mit den entsprechenden Tabellen haben, entfällt dieser Schritt für Sie. Wir verwenden für unser Beispiel MySQL. Die Datenbank selbst erzeugen wir entweder per Kommandozeile oder mit einem grafischen Tool wie dem My-SQL-Administrator.
Nennen wir unsere erste Datenbank book_collection1. Nachdem wir diese erzeugt und die nötigen Rechte vergeben haben (Unser User braucht dazu mindestens die Rechte für CREATE, INSERT, UPDATE, DELETE und SELECT.), können wir mit SQLAlchemy unsere Tabellen erstellen. Den dazu nötigen Quellcode finden Sie im Listing 1.

Listing 1
------------------------------------------------------------------------------
#!/usr/bin/env python


from sqlalchemy import *


metadata = BoundMetaData('mysql://bookuser:bookpass@localhost/book_collection1')

author_table = Table('authors', metadata,
					 Column('id', Integer, primary_key=True),
					 Column('firstname', String(40)),
					 Column('lastname', String(40)),
					 mysql_engine='InnoDB'
					 )

book_table = Table('books', metadata,
				   Column('id', Integer, primary_key=True),
				   Column('title', String(100)),
				   Column('author_id', Integer, ForeignKey("authors.id")),
				   mysql_engine='InnoDB'
				   )



metadata.engine.echo = True

author_table.create()
book_table.create()

Wie Sie sehen können, definieren wir unseren neuen Tabellen nicht mit SQL, sondern mit Python-Code. Für jede Tabelle rufen wir den Konstruktor der Klasse Table auf. Der erste Parameter ist der Name der Tabelle, der zweite ein BoundMetaData-Objekt, welches ich gleich noch näher erklären werde. Anschließend folgen die einzelnen Spalten der Tabelle, welche jeweils mit einem Column-Objekt erzeugt werden. Die Parameter des Column-Objekts sind relativ selbsterklärend. String wird, wie Sie bestimmt schon vermutet haben, auf VARCHAR in der Datenbank abgebildet. Wenn Sie MySQL verwenden, können sie noch die Storage Engine von MySQL angeben. Wir verwenden hier InnoDB, da wir später auch Transaktionen verwenden wollen (Nicht alle Storage Engines von MySQL unterstützen Transaktionen. Mehr dazu in der MySQL-Dokumentation.).

Das bereits erwähnte BoundMetaData-Objekt ist eine Sammlung von Tabellen und deren Datenbank-Schemas. Die hier verwendeten Klassen BoundMetaData, Table, Column und ForeignKey stellen eine umfangreiche Schnittstelle zur Verfügung, die Beschreibung dieser Klassen liegt aber außerhalb des Umfangs dieses Artikels. Ich empfehle Ihnen daher einen Blick in die SQLAlchemy-Dokumentation zu werfen, falls Sie sich näher für diese Klassen interessieren. Die Zeile metadata.engine.echo = True ist hauptsächlich für Debugging-Zwecke interessant. Sie bewirkt, dass das von SQLAlchemy erzeugte SQL ausgegeben wird. So können Sie bei eventuell auftretenden Fehlern gleich sehen, warum irgendetwas nicht funktioniert. Auch wenn Sie gerade erst mit SQLAlchemy beginnen, ist es ratsam, dieses Feature einzuschalten, da Sie dann besser verstehen, was „unter der Haube“ vor sich geht.

Daten einfügen

Eine Datenbank macht natürlich nur Sinn, wenn man auch Daten in ihr ablegt. Deshalb speichern wir jetzt zwei Autoren in unserer authors-Tabelle. Den Quellcode dazu sehen Sie in Listing 2.

Listing 2
--------------------------------------------------------------------------------
#!/usr/bin/env python

from booklib import *

i = author_table.insert()
i.execute(firstname = "J.R.R", lastname = "Tolkien")
i.execute(firstname = "Martin", lastname = "Fowler")

Die insert-Methode erzeugt ein Objekt, mit welchem wir Daten in einer Tabelle speichern können. Wir müssen dazu nur die execute-Methode aufrufen und die Spaltennamen als Parameter mit den jeweiligen Werten übergeben. Analog zu insert gibt es auch weitere Methoden zum Löschen (delete), Ändern (update) und Auslesen (select) von Daten. Neben diesen vier Grundoperationen werden auch Joins und vieles mehr unterstützt. Wie man Daten auslesen kann sehen Sie in Listing 3.

Listing 3
----------------------------------------------------------------------------
#!/usr/bin/env python

from booklib import *

result = author_table.select().execute()

first = result.fetchone()
print first
print

for item in result.fetchall():
	print "id: %d" % item[0]
	print "firstname: %s" % item[1]
	print "lastname: %s" %  item[2]
	print

Nach dem Aufruf der select-Methode holen wir uns zuerst nur den ersten Eintrag und geben diesen aus. Nachfolgenden holen wir uns alle (verbleibenden) Datensätzen (hier nur noch einer, darum ist die Schleife im Prinzip überflüssig, zeigt aber wie es mit mehreren Datensätzen funktionieren würde) und geben diese etwas schöner formatiert aus als unseren ersten Datensatz. Das hier gezeigte Verfahren ist zwar nicht weiter schwer, aber es wäre besser, wenn wir mit echten Python-Objekten arbeiten könnten. Wie das geht und welche Möglichkeiten SQLAlchemy für OR-Mapping zur Verfügung stellt, sehen Sie in den nachfolgenden Beispielen.

OR-Mapping

Wenn Sie bereits andere OR-Mapping-Lösungen wie Hibernate für Java oder ActiveRecord für Ruby verwendet haben, wissen Sie, wie elegant es sein kann, ausschließlich mit Objekten zu arbeiten und sich nicht weiter mit Datenbank-Details aufzuhalten. SQLAlchemy bietet eine leistungsfähige OR-Mapping-Lösung, welche ich Ihnen mit den nachfolgenden Beispielen demonstrieren möchte. Da wir dazu einige Klassen brauchen, welche wir öfter benötigen, bauen wir uns eine kleine Bibliothek, wo wir die Klassen und einige wichtige Methodenaufrufe implementieren. Siehe dazu Listing 4.

Listing 4
---------------------------------------------------------------------------
from sqlalchemy import *

class Book(object):
	def __init__(self, title = ""):
		self.title = title
		
	def __repr__(self):
		return "title: %s" % (self.title)
	
class Author(object):
	def __init__(self, firstname = "", lastname = ""):
		self.firstname = firstname
		self.lastname  = lastname
		
	def __repr__(self):
		return "name: %s" % (self.name)


metadata = BoundMetaData('mysql://bookuser:bookpass@localhost/book_collection1')
metadata.engine.echo = True

author_table = Table('authors', metadata, autoload=True)
book_table = Table('books', metadata, autoload=True)

book_mapper = mapper(Book, book_table)
author_mapper = mapper(Author, author_table)
author_mapper.add_property('books', relation(Book))

Wenn Sie die Beispiele selbst ausprobieren wollen, speichern Sie diesen Code in einer Datei mit den Namen booklib.py. Als Erstes erzeugen wir zwei Klassen für Bücher und Autoren. Diese haben keinerlei Abhängigkeiten von SQLAlchemy. Anschließend erzeugen wir auch gleich ein BoundMetaData-Objekt. Um das eigentliche OR-Mapping zu definieren, benötigen wir zuerst die entsprechenden Table-Objekte, welche wir dann mithilfe der Methode mapper verwenden, um das eigentliche Mapping zu erzeugen (mapper erzeugt eine Instanz der Klasse Mapper. Details dazu in der SQLAlchemy-Dokumentation). Die letzte Zeile definiert eine so genannte Property „books“ für Autoren und eine One-to-Many-Beziehung. In unserem ersten Beispiel gehen wir von der vereinfachten Annahme aus, dass ein Autor zwar mehrere Bücher schreiben kann, ein Buch aber immer nur einen Autor hat. Weiter unten, wenn Sie sehen, wie man das Ganze verwendet, wird diese Zeile auch noch verständlicher. In Listing 5 sehen Sie eine erste Verwendung unserer kleinen Bibliothek.

Listing 5
------------------------------------------------------------------------
#!/usr/bin/env python


from booklib import *


session = create_session()
a = Author()
a.firstname = "Friedrich"
a.lastname = "Goethe"
session.save(a)
session.flush()

Alles, was wir tun müssen, ist eine Session und ein Author-Objekt zu erzeugen. Wir können den Vornamen entweder im Konstruktor übergeben (siehe unsere Klassendefinition), aber auch einfach wie hier setzen. Dass das alles richtig zugewiesen wird, darum kümmert sich SQLAlchemy. Beachten Sie, dass die Klassenproperties wie firstname und lastname genauso heißen wie die Spaltennamen der Datenbanktabellen. Wenn wir unser Objekt erzeugt haben, können wir dieses mit session.save speichern. session.flush ist auch notwendig und erzeugt automatisch eine Transaktion, welche die Daten endgültig in der Datenbank speichert (commit). Wie Sie die Transaktionen selbst steuern, sehen Sie weiter unten.

Der aufmerksame Leser wird bemerkt haben, dass es natürlich keinen Friedrich Goethe gab (zumindest keinen berühmten Autor). Daher müssen wir unseren Fehler korrigieren und aus Friedrich Goethe einen Friedrich Schiller machen. Den dazu notwendigen Quellcode sehen Sie in Listing 6.

Listing 6
--------------------------------------------------------------------------
#!/usr/bin/env python


from booklib import *


session = create_session()
query = session.query(Author)

a = query.get_by(firstname="Friedrich")
a.lastname = "Schiller"

session.save(a)
session.flush()

Hier erzeugen wir ein query-Objekt. Dieses bietet die Methode get_by, welcher wir unsere Kriterien für die Datenbankabfrage übergeben können. In unserem Beispiel liefert die Abfrage ein Author-Objekt zurück, bei welchem wir dann die korrekten Nachnamen setzen und anschließend die Daten wieder in die Datenbank schreiben.

Geschrieben von
Markus Jais
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.