Zope3: Nutzung von Datenbank-Generationen (database generations)

Zope3 bietet einen genialen Weg, Objekte auf Stand zu bringen, wenn sich das Schema geändert hat.

by Michael Howitz posted at 2006-03-09 15:10 last modified 2006-03-09 15:10

Einleitung

Wenn man mit Zope3 eine Anwendung entwickelt. und diese weiterpflegt nachdem sie (eventuell in mehreren Instanzen) produktiv gegangen ist, kommt es vor, dass sich Schemas von Objekten ändern. Zope3 bietet mit database generations einen Weg die bestehenden Objekte in der ZODB auf den aktuellen Stand zu bringen.

Beispiel

Nehmen wir folgende Beispiel-Applikation:
interfaces.py:

from zope.interface import Interface
from zope.schema import TextLine

class IPerson(Interface):
"""Eine Person."""
vorname = TextLine(title=u"Vorname")
nachname = TextLine(title=u"Nachname")

person.py:

from zope.interface import implements
from persistent import Persistent
from zope.app.containter.contained import Contained
from myapp.interfaces import IPerson

class Person(Persistent, Contained):
"Implementation von IPerson"
implements(IPerson)
vorname = None
nachname = None

Update des Schemas

Nachdem diese Anwedung produktiv gegangen ist und schon viele Personen angelegt wurden. Wird festgelegt, dass auch das Geburtsdatum erfasst werden können muss. Also ändert man die Applikation wie folgt:

interfaces.py:

from zope.interface import Interface
from zope.schema import TextLine, Date

class IPerson(Interface):
"""Eine Person."""
vorname = TextLine(title=u"Vorname")
nachname = TextLine(title=u"Nachname")
gebdat = Date(title=u"Geburtsdatum", required=False) # muss nicht angegeben werden

person.py:

from zope.interface import implements
from persistent import Persistent
from zope.app.containter.contained import Contained
from myapp.interfaces import IPerson

class Person(Persistent, Contained):
"Implementation von IPerson"
implements(IPerson)
vorname = None
nachname = None
gebdat = None

Damit ändern sich aber nicht die bereits in der ZODB existierenden Objekte. Hier kommen dann database generations ins Spiel.

Database Generations

Man legt innerhalb des Paktes ein Verzeichnis an. Für das Beispiel nennen wir es myappgenerations.
In diesem Verzeichnis wird eine Datei __init__.py angelegt:

from zope.app.generations.generations import SchemaManager

key = 'myapp.myappgenerations' # Name des Paketes Punkt Name des Verzeichnisses

MyAppSchemaManager = SchemaManager(
minimum_generation=0,
generation=1,
package_name=key)

Unter minimum_generation wird eingetragen, welche Datenbankgeneration mindestens benötigt wird, um mit der Anwendung zu arbeiten.
Unter generation wird eingetragen, welche Datenbankgeneration mit dem derzeitigen Stand maximal möglich ist.

Es wird eine configure.zcml benötigt:

<configure xmlns="http://namespaces.zope.org/zope">
<utility
name="mycompany.myapp"
provides="zope.app.generations.interfaces.ISchemaManager"
component=".MyAppSchemaManager"
/>
</configure>

Der Name des Utilities sollte die Anwendung eindeutig identifizieren.

Um von Datenbankgeneration 0 (was der Default ist) auf Generation 1 zu kommen, wird das Script evolve1.py im Verzeichnis myappgenerations ausgeführt:

from zope.app.zopeappgenerations import getRootFolder
from zope.app.generations.utility import findObjectsProviding

from myapp.interfaces import IPerson

generation = 1 # gibt die Generation der ZODB an, die mit diesem Script erreicht wird

def evolve(context):
u"""Fügt IPerson das Geburtsdatum hinzu.
""" # Dieser Doc-String wird später im ZMI angezeigt.

root = getRootFolder(context) # Ermitteln des Zope-Root-Folders

for person in findObjectsProviding(root, IPerson):
# Schleife über alle Objekte, die IPerson erfüllen.
person.gebdat = None # Setzen des Defaults

Achtung: findObjectsProviding sollte nur benutzt werden, wenn alle zu findenden Objekte sich in Containern befinden bzw. über eine ununterbrochene Kette von values()-Aufrufen vom root aus erreichbar sind!

Database Generations benutzen

Da wir in unserem SchemaManager gesagt haben, dass die minimale Version die 0 sein soll, wird das Update nicht automatisch asgeführt, da ja Generation 0 ausreicht. Man kann aber per Hand ein Upgrade auf Generation 1 machen.
Dazu meldet man sich als Manager am ZMI (Rotterdam-Skin) an. Einer der Links ist mit "Prozess verwalten" bzw. im Englischen mit "Manage process" beschriftet. Ein Klick darauf führt zu einer Seite auf der es ein Tab "Datenbank-Schemas" gibt. Dieses dient zur Verwaltung der Datenbankgenerationen. Wenn ein Paket, welches Datenbankgenerationen benutzt nicht aktuell ist, kann man die ZODB mittels eines Klicks auf "evolve" aktualisieren. Es wird dann genau um eine Datenbankgeneration aktualisiert.

Weitere Datenbankgenerationen

Wenn dann die nächste Datenbankgeneration ansteht, ist in __init__.py im Schema-Manager die generation zu ändern.
Es wird ein neues evolven.py benötigt. (n ist die Nummer der Datenbankgeneration.) Zope ist neu zu starten und man kann auf die neue Generation aktualisieren.

Weitere Möglichkeiten

Mitunter ist die neue Datenbankgeneration zwingend erforderlich, damit die Anwendung noch funktioniert. Dann setzt man im SchemaManager den Parameter minimum_generation auf die Generation, die mindestens benötigt wird. Beim nächsten Neustart führt Zope das Update bis zu dieser Version automatisch aus.

Category(s)
Zope 3
The URL to Trackback this entry is:
http://blog.gocept.com/zope3-nutzung-von-datenbank-generationen-database-generations/tbping

Re:Zope3: Nutzung von Datenbank-Generationen (database generations)

Posted by Bernd Dorn at 2006-05-09 22:33
In Falle dieses Beispiels würde es eigentlich keine Generations brauchen, da ja gebdat in der Klasse definiert wird und somit auch in allen alten instanzen automatisch vorhanden ist.

Ein Beispiel das Generations braucht wäre etwa:

class MyContainer(Container):
def __init__(self):
self['etwas'] = Etwas()

Re:Zope3: Nutzung von Datenbank-Generationen (database generations)

Posted by Michael Howitz at 2006-05-10 08:43
Der Kommentar von Bernd Dorn kling zwar logisch, aber wie bereits im Blog-Eintrag geschrieben, erscheinen diezu einer Klasse neu hinzugefügte Attribute nur auf der Klasse. (Die Änderung bezieht sich ja nur auf die Klasse!) Auf die Instanzen der Klasse (Objekte) können die neuen Attribute nur mittels database generations übertragen werden.

Re:Zope3: Nutzung von Datenbank-Generationen (database generations)

Posted by Bernd Dorn at 2006-05-12 17:15
in:
for person in findObjectsProviding(root, IPerson):
# Schleife über alle Objekte, die IPerson erfüllen.
person.gebdat = None # Setzen des Defaults

wird None auf jedes person object gesetzt, aber würde man vor dem setzen
person.gebdat ausgeben, gibt es auch None zurück
es ändert sich durch dieses evolve nichts ausser dass gebdat auf None im objekt gesetzt werd, da aber None immutable ist, ist das irrelevant

btw: die seite hier ist sehr nüztlich, ich hab schon einiges abgekuckt hier :)

sg


Re:Zope3: Nutzung von Datenbank-Generationen (database generations)

Posted by Michael Howitz at 2006-05-16 08:59
Der letzte Kommentar von Bernd Dorn hat durchaus seine Berechtigung. Ich habe aber folgende Beobachtung gemacht:
Ich benutze zope.formlib zur Anzeige der Formulare. Wenn ich nun zu einer Klasse für die schon Objekte in der ZODB existieren ein neues Schema-Feld hinzufüge (im Interface und als Attribut auf der Klasse), dann bekomme ich regelmäßig einen AttributeError, wenn ich mir das Formular zu einem dieser Objekte anzeigen lasse. Der AttributeError betrifft immer das neue Attribut, welches es laut zope.formlib nicht gibt, obwohl es auf der Klasse definiert ist.