4. TurboGears

Grundlagen zu TurboGears
SQLObject
Kid
CherryPy
MochiKit
Abschließende Übersicht

4.1 Grundlagen zu TurboGears

TurboGears wird seit 2005 von der Firma Zesty News unter der Leitung von Kevin Dangoor programmiert. Der gesamte Quellcode des Projekts steht offen zur Verfügung und kann, genau wie das Produkt selber, frei verwendet werden. Die grundlegenden Konzepte, wie man in den späteren Absätzen erkennen kann, orientieren sich teilweise stark an Ruby on Rails, TurboGears kann aber nicht als direkter Clone bezeichnet werden, da die Komponenten, aus denen es zusammengesetzt ist, teilweise länger bestehen als Rails.

Aus wirtschaftlichen Gründen hat man sich dazu entschieden, kein komplett neues Web-Framework aufzusetzen, sondern lediglich bereits bestehende Komponenten (nicht-full-stack Frameworks) zu einem großen (full-stack Framework) zusammenzufassen. Dies ist jedoch nicht nur aus wirtschaftlicher Sicht sinnvoll, sondern auch aus der der Entwicklung: Komplexe Komponenten müssen nicht von Grund auf neu erstellt und getestet werden, viel mehr verlässt man sich auf schon länger bestehende und etablierte Teilkomponenten. Diese sind besonders gut für ihre spezifische Aufgabe ausgerichtet, könne mit relativ geringem Aufwand ausgetauscht werden und müssen nur noch verbunden werden.

TurboGears ist aus den folgenden vier grundlegenden nicht-full-stack Framweworks aufgebaut:

Diese werden nun in den folgenden Abschnitten weiter erläutert.

4.2 SQLObject

SQLObject ist die Model-Komponente von TurboGears, dient also der Haltung der im System vorhandenen Daten. Da Python eine objekt-orientierte Sprache ist, bietet es sich an, die anfallenden Daten auch als Objekte zu hinterlegen. Deshalb fungiert SQLObject als ein so genannter "object relational mapper", oder kurz: ORM. Dieser kann aus Python-Objekten die entsprechenden Datenbank-Tabellen generieren.

SQLObject unterstütz zur Zeit eine große Menge an verschiedenen SQL-Datenbanken und wird ständig um neue Erweitert. Für den Anwender von SQLObject macht es keinen Unterschied, auf welcher hersteller-spezifischen Datenbank gearbeitet wird, da alle Zugriffe intern in die entsprechende Anfrage umgewandelt werden. Weiter muss der Benutzer keine SQL-Anfragen schreiben, dies geschieht implizit über den Zugriff auf hinterlegte Objekte. Um die Auslastung der Datenbank nicht zu hoch zu treiben, können Anfragen automtisch gecached werden.

Wie aus einer Python-Klasse eine Datenbank generiert wird, ist im folgenden Codeabschnitt demonstiert:

from
sqlobject
import
*
from
datetime
import
datetime

class
Person
(SQLObject):
firstName = StringCol(length=100)
middleInitial = StringCol(length=1, default=
None
)
lastName = StringCol(length=100)
lastContact = DateTimeCol(default=datetime.now)

In den Zeilen wird das SQLObject-Modul importiert, in Zeile zwei ein Modul zur Zeitverarbeitung. In Zeile vier wird dann eine Klasse "Person" deklariert, welche von der Basis-Klasse "SQLObject" abgeleitet wird. Die Basis-Klass stellt entsprechende Methoden zum Umgang mit der angebundenen Datenbank bereit.

In den folgenden vier Anweisungen werden die Attribute der Klasse festgelegt. Links vom Gleichheitszeichen steht der Name des zu erzeugenden Attributs, auf der rechten Seite wird diesem dann ein Wert zugewiesen. Der Typ einer Variable, bzw. eines Attributs, wird in Python nicht explizit angegeben, sondern ist implizit durch den belegenden Wert gegeben.

In diesem konkreten Fall werden auf der rechten Seite neue Instanzen der Klassen "StringCol" und "DateTimeCol" instanziiert. Diesen können zusätzlich noch Eigenschaften, wie Länge oder default-Belegung, mit übergeben werden. Der SQL-Typ einer Spalte, in denen die einzelnen Attribute einer Klasse gehalten werden, kann nun aus dem Inhalt der Attribute abgelesen werden. Möchte man einen Fremdschlüssel festlegen, n:1- oder m:n Beziehnungen ausdrücken, so kann dies über die Klassen "ForeignKey" und "RelatetJoin" beschrieben werden.

Im folgenden Code-Block ist zu sehen, welcher SQL-Code aus der obenen gegebenen Klasser erzeugt wird:

CREATE TABLE Person (
id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
middle_initial CHAR(1),
last_name VARCHAR(100) NOT NULL,
last_contact TIMESTAMP NOT NULL
);

Es ist deutlich zu erkennen, dass jedes in der Python-Klasse deklariertes Attribut, zu einer entsprechenden Spalte in der Tabelle umgewandelt wurde. Hinzugekommen ist noch eine die Spalte "id", die als Primärschlüssel verwendet wird. Das beispielhafte Erzeugen einer Tabelle under der Zugriff auf diese sind im folgenden Codeblock kurz angerissen:

#Erzeugen der oben beschriebenen Tabelle

Person.createTable()

#Eine Person anlegen

p = Person(firstName=
"Sebastian"
, lastName=
"Hempel"
)

#Zugriff über die ID

p = Person.get(1)

#Zugriff über einen Ausdruck

p = Person.select(Person.q.firstName==
"Sebastian"
)

#Zugriff über ein echtes SQL-Statement

p = Person.select(
"""person.id=0"""
)

Wie in der letzten Zeile des obrigen Beispiels zu sehen, ist es ebenfalls möglich Objekte über einen SQL-Ausdruck anzufordern. Ebenfalls ist eine automatische Klassen-Generierung aus einer Datenbank-Tabelle vorgesehen.

4.3 Kid

Kid ist ein weiterer Bestandteile von TurboGears und stellt aus Sicht des MVC-Konzepts die View-Komponente dar. Bei Kid handelt es sich um eine so genannte "Template-Engine", die verschiedene Template-Sprachen verarbeiten kann. Templates im Bereich der Web-Frameworks dienen der Trennung von Inhalt und Gestaltung einer Seite, bieten also eine Vorlage wie bestimmte Daten in das Layout integriert werden können. Zu diesem Zweck stehen verschiedene Platzhalter in der Template-Sprache zur Verfügung, die durch Inhalt ersetzt, oder durch gestaltende Logik ausgetauscht werden.

Neben der von Kid bereit gestellten Template-Sprache lassen sich auch andere Sprachen verwenden. Dazu gehören beispielsweise Cheetah, XSLT oder aber auch die Zope Page Templates (ZPT). Um einen Eindruck über die mitgelieferte Sprache zu vermitteln, ist im folgenden Code-Abschnitt eine kurzes Beispiel-Template aufgeführt:

<?python
spam=
"SPAM"

eggs=
"EGGS"
?>

<html xmlns:py="http://purl.org/kid/ns#">
<head>
<title py:content="spam">Dummy</title>
</head>
<body>
<b py:replace="eggs"/>
</body>
</html>

Wie man sieht, wird stets wohlgeformtes XML, bzw. XHTML, in den Templates hinterlegt. Nach der Auswertung ist dies aber nicht mehr gewährleistet, da die Daten aus beliebigen Python-Ausdrücken stammen k�nen. In den python-processing-instructions (ersten vier Zeilen), kann beliebiger Python-Code stehen, welcher zur Auswertungszeit des Templates ausgeführt wird. Hier können komplexere Berechnungen durchgeführt werden, aber auch einfache Variablen-Belegungen.

Weiter ist es möglich, den Inhalt von Elementen, bzw. ein komplettes Element durch das Ergebnis eines Python-Ausdrucks zu ersetzen. Dies wird durch die Attribute py:content, bzw. py:relplace symbolisiert. Die wichtigsten Attribute, hier ohne vorgestellten Namensraum, werden in der folgenden Tabelle kurz beschrieben:

contentErsetzen des Inhalts des Elements durch den angegebenen Ausdruck
replaceErsetzen des gesamten Elements durch den angegebenen Ausdruck
ifPrüft, ob der angegebene Ausdruck wahr ist und wertet nur dann die enthaltenen Elemente aus
attrsWert eines Attributs setzen
forÜber eine, durch den angegebenen Ausdruck zurückgegebene, Liste laufen
defDefinieren einer Template-Funktion, die mehrmals aufgerufen werden kann. Dies ist dann sinnvoll, wenn ein Objekt beispielsweise mehrfach ausgegeben werden soll (bspw. ein Eintag in einem Gästebuch)

Neben dem oben genannten Beispiel mit Verwendung eines Namespaces, ist dieses auch ohne diesen möglich:

<?python
spam=
"SPAM"

eggs=
"EGGS"
?>

<html>
<head>
<title>${spam}</title>
</head>
<body>
${eggs}
</body>
</html>
Innerhalb der geschwungenen Klammern kann beliebiger Python-Code stehen, so dass PHP-ähnliches Programmieren ermöglicht wird.

4.4 CherryPy

Aus Sicht des MVC-Konzeptes handelt es sich bei CherryPy um die Controler-Komponente von Turbo-Gears. Die Aufgabe besteht also im Wesentlichen darin, Anfragen vom Client, bzw. von einem Server, anzunehmen und diese durch entsprechende Kombination von Daten(Modellen) und Templates(Views) zu beantworten. Neben diesen beiden Aspekten ermöglicht es, einen eigenen Web-Server aufzusetzen, oder aber auch die Integrationen in einen bereits bestehenden, durch beispielsweise CGI, mod_python oder eine WSGI kompatible Schnittstelle.

WSGI: Webserver Gateway Interface
Hier bei handelt es sich um eine einheitliche Schnittstelle, bzw. genauer um eine zusätzliche Ebene, zwischen Servern und Anwendungen. Diese ermöglicht einen Datenaustausch, ohne dass der Server die konkrete Anwendung kennen muss, und ohne dass die Anwendung das konkrete Übetragungsprotokoll (CGI, FastCGI, ...) implementieren muss. Es wird also eine Kommunikation zwischen beliebigen Anwendungen und beliebigen Servern über beliebige Protokolle ermöglicht.
CherryPy selbst kann nur Strings an den Server, bzw. an den Client übermitteln, bietet also selbst keine Möglichkeit zur Generierung von HTML. Letztere Aufgabe wird bei TurboGears, wie weiter oben bereits beschrieben, durch Kid durchgeführt. Neben der Low-Level-Funktionalität der Übertragung der angeforderten Daten, werden Module für unter anderem Datei-Uploads, Cookies und Sessions zur Verfügung gestellt. Auf deren Daten kann jeder Zeit über ein Dictionary zugegriffen werden.

Um die folgenden Beispiel zu verstehen, sei noch der wichtige Punkt erwähnt, das CherryPy die Seiten einer Anwendung als Objekte betrachtet. Diese bieten dann die entsprechenden Methoden zum Rendern des HTMLs aus dem Templates und ermöglichen eine beliebige Verschachtelung.

Beispiel: URL-Mapping mit CherryPy
Gegeben sei die URL www.spam.de/eggs/foo/bar?id=42

Diese wird von CherryPy in die folgenden drei Komponenten zerlegt:

  1. www.spam.de - Die Adresse des Web-Servers
  2. /eggs/foo/bar - Das Dokument auf das Zugegriffen werden soll
  3. id=42 - Der Parameter, mit dem das Dokument aufgerufen werden soll
Für die folgende Betrachtung sind nur die letzten beiden Bestandteile relevant. Wie weiter oben bereits erwähnt, werden alle Seiten der Awendung von CherryPy durch Objekte modelliert. Daher ist der Dokumenten-String nach dem folgenden Schema zu interpretieren:
Man nehme das root-Element und aus diesem das Element foo. Dann führe man auf dem foo-Element die Methode bar mit dem Parameter id="42" aus.
Oder konkret in Python-Syntax:
root.eggs.foo.bar(id=
"42"
)

Wie eine konkrete Seite aufgebaut wird, kann dem folgenden Code-Beispiel entnommen werden:

import
cherrypy

class
Root
:
def
index
(self):
return
"root"

index.exposed =
True

class
Eggs
:
def
index
(self):
return
"eggs"

index.exposed =
True

class
Foo
:
def
bar
(self, id):
return
"foo"

bar.exposed =
True


#Aufbauen der Anwendung

cherrypy.root = Root()
cherrypy.root.eggs = Eggs()
cherrypy.root.eggs.foo = Foo()

#Starten des Servers

cherrypy.server.start()
Zu Beginn wird das Modul "cherrypy" importiert, welches die Verwendung von CherryPy ermöglicht. In den folgenden drei größeren Blöcken werden die drei Klassen "Root", "Eggs" und "Foo" erzeugt. Die ersten beiden enthalten eine Methode "index" die genau dann aufgerufen wird, wenn am Ende des Dokumenten-Teils kein "/" steht, also der Zugriff auf einen "Ordner" verlangt wird. Wie man an der Klasse "Foo" sehen kann, ist es auch möglich eigene Methoden zu definieren, die genau dann aufgerufen werden, wenn der letzte Teil des Dokumenten-Strings dem Namen der Methode entspricht. Alle drei Methoden liefern einen einfachen String zurück, der dann etsprechend im Browser des Anwenders gerendert werden würde. Da mit jedoch eine Zugriff von außen möglich wird, muss die Methode noch als öffentlich gekennzeichnet werden, welches hier beispielsweise durch
bar.exposed =
True
geschieht.

Im letzten Teil des Beispiels werden dann Instanzen der einzelnen Seiten erzeugt und in einander verschachtelt. Dies ist auf beliebig komplexe Weise möglich. Im letzten Statement wird noch der Web-Server gestartet, so dass von außen auf die Seiten zugegriffen werden kann.

4.5 MochiKit

MochiKit läßt sich nich direkt in das MVC-Konzeptes einordnen, da es nur auf der Client-Seite agiert. Basis für dieses Framework sind Ajax (Asynchronous JavaScript and XML) und JSON. Bei JSON handelt es sich um ein Datenaustauschformat ähnlich zu XML, ist aber wesentlich kompakter, auf der anderen Seite jedoch nicht so allgemeingültig.

Betrachtet man MochiKit etwas genauer, so stellt man fest, dass es im Prinzip nur eine umfangreiche Bibliothek von komplexen Funktionen ist. Dazu gehören beispielsweise die Möglichkeit einer einfachen Definition von durch die Maus beweglichen Objekten, Manipulationen am DOM-Baum oder auch visuelle Effekte. Dies geschieht mit wenigen Anweisungen, so dass eine vernünftig gestaltete Tabelle bereits mit einem Aufruf erzeugt werden kann. Da nicht jede Anwendung Dynamik auf der Seite des Clients benötigt, ist die Verwendung von MochiKit optional.

4.6 Abschließende Übersicht

Das folgende Bild verdeutlich die Zusammenhänge der einzelnen Komponenten von TurboGears:


Abbildung 4.6.1: Zusammenspiel der Komponenten; Quelle: TurboGears.com
Im Folgenden ist beispielhaft eine Anfrage eines Clients an TurboGears aufgezeichnet:
Im ersten Schritt sendet der Client eine Anfrage für eine bestimmte Seite der Anwendung an den Web-Server, welcher selbst wiederrum die Anfrage, an CherryPy weiterleitet. Als nächstes führt CherryPy das URL-Mapping für die Anfrage durch, und versucht das entsprechende Objekt auszuführen. Kann dieses nicht gefunden werden, so wird eine Fehlermeldung generiert. Andernfalls wird die angeforderte Methode(controller code) mit allen angegebenen Parametern aufgerufen. Diese Methode stößt nun das Laden der geforderten Modelle an und überreicht diese an "TurboGears", welches hier beispielhaft für die Verknüpfung der Komponenten steht. Dort werden die anlaufenden Daten an die Template-Engine Kid weitergeleitet und unter Umständen JSON-Code direkt an CherryPy gesendet, wo diese mit den ausgewerteten Template-Daten verbunden werden. An dieser Stelle hat CherryPy alle zu sendenen Daten zusammengestellt und kann diese nun über den Web-Server zurück an den Client senden, auf dessen Maschine dann unter Umständen MochiKit weitere Aufgaben übernimmt.