[Zope3-checkins]
SVN: Zope3/trunk/src/zope/app/generations/README.txt
Added a README.txt for zope.app.generations.
Gintautas Miliauskas
gintas at pov.lt
Sun Jan 30 14:14:23 EST 2005
Log message for revision 28985:
Added a README.txt for zope.app.generations.
Changed:
A Zope3/trunk/src/zope/app/generations/README.txt
-=-
Added: Zope3/trunk/src/zope/app/generations/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/generations/README.txt 2005-01-30 19:13:23 UTC (rev 28984)
+++ Zope3/trunk/src/zope/app/generations/README.txt 2005-01-30 19:14:23 UTC (rev 28985)
@@ -0,0 +1,193 @@
+A quick introduction to generations
+===================================
+
+Generations are a way of updating objects in the database when the application
+schema changes. An application schema is essentially the structure of data,
+the structure of classes in the case of ZODB or the table descriptions in the
+case of a relational database.
+
+When you change your application's data structures, for example,
+you change the semantic meaning of an existing field in a class, you will
+have a problem with databases that were created before your change. For a
+more thorough discussion and possible solutions, see
+http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/DatabaseGenerations
+
+We will be using the component architecture, and we will need a database and a
+connection:
+
+ >>> import cgi
+ >>> from pprint import pprint
+ >>> from zope.interface import implements
+ >>> from zope.app.tests import placelesssetup, ztapi
+ >>> placelesssetup.setUp()
+
+ >>> from ZODB.tests.util import DB
+ >>> db = DB()
+ >>> conn = db.open()
+ >>> root = conn.root()
+
+Imagine that our application is an oracle: you can teach it to react to
+phrases. Let's keep it simple and store the data in a dict:
+
+ >>> root['answers'] = {'Hello': 'Hi & how do you do?',
+ ... 'Meaning of life?': '42',
+ ... 'four < ?': 'four < five'}
+ >>> get_transaction().commit()
+
+
+Initial setup
+-------------
+
+Here's some generations-specific code. We will create and register a
+SchemaManager. SchemaManagers are responsible for the actual updates of the
+database. This one will be just a dummy. The point here is to make the
+generations module aware that our application supports generations.
+
+The default implementation of SchemaManager is not suitable for this test
+because it uses Python modules to manage generations. For now, it
+will be just fine, since we don't want it to do anything just yet.
+
+ >>> from zope.app.generations.interfaces import ISchemaManager
+ >>> from zope.app.generations.generations import SchemaManager
+ >>> dummy_manager = SchemaManager(minimum_generation=0, generation=0)
+ >>> ztapi.provideUtility(ISchemaManager, dummy_manager, name='some.app')
+
+'some.app' is a unique identifier. You should use a URI or the dotted name
+of your package.
+
+When you start Zope and a database is opened, an IDatabaseOpenedEvent is sent.
+Zope registers evolveMinimumSubscriber by default as a handler for this event.
+Let's simulate this:
+
+ >>> class DatabaseOpenedEventStub(object):
+ ... def __init__(self, database):
+ ... self.database = database
+ >>> event = DatabaseOpenedEventStub(db)
+
+ >>> from zope.app.generations.generations import evolveMinimumSubscriber
+ >>> evolveMinimumSubscriber(event)
+
+The consequence of this action is that now the database contains the fact
+that our current schema number is 0. When we update the schema, Zope3 will
+have an idea of what the starting point was. Here, see?
+
+ >>> from zope.app.generations.generations import generations_key
+ >>> root[generations_key]['some.app']
+ 0
+
+In real life you should never have to bother with this key directly,
+but you should be aware that it exists.
+
+
+Upgrade scenario
+----------------
+
+Back to the story. Some time passes and one of our clients gets hacked because
+we forgot to escape HTML special characters! The horror! We must fix this
+problem ASAP without losing any data. We decide to use generations to impress
+our peers.
+
+Let's update the schema manager (drop the old one and install a new custom
+one):
+
+ >>> ztapi.unprovideUtility(ISchemaManager, name='some.app')
+
+ >>> class MySchemaManager(object):
+ ... implements(ISchemaManager)
+ ...
+ ... minimum_generation = 1
+ ... generation = 2
+ ...
+ ... def evolve(self, context, generation):
+ ... root = context.connection.root()
+ ... answers = root['answers']
+ ... if generation == 1:
+ ... for question, answer in answers.items():
+ ... answers[question] = cgi.escape(answer)
+ ... elif generation == 2:
+ ... for question, answer in answers.items():
+ ... del answers[question]
+ ... answers[cgi.escape(question)] = answer
+ ... else:
+ ... raise ValueError("Bummer")
+ ... root['answers'] = answers # ping persistence
+ ... get_transaction().commit()
+
+ >>> manager = MySchemaManager()
+ >>> ztapi.provideUtility(ISchemaManager, manager, name='some.app')
+
+We have set `minimum_generation` to 1. That means that our application
+will refuse to run with a database older than generation 1. The `generation`
+attribute is set to 2, which means that the latest generation that this
+SchemaManager knows about is 2.
+
+evolve() is the workhorse here. Its job is to get the database from
+`generation`-1 to `generation`. It gets a context which has the attribute
+'connection', which is a connection to the ZODB. You can use that to change
+objects like in this example.
+
+In this particular implementation generation 1 escapes the answers (say,
+critical, because they can be entered by anyone!), generation 2 escapes the
+questions (say, less important, because these can be entered by authorized
+personell only).
+
+In fact, you don't really need a custom implementation of ISchemaManager. One
+is available, we have used it for a dummy previously. It uses Python modules
+for organization of evolver functions. See its docstring for more information.
+
+In real life you will have much more complex object structures than the one
+here. To make your life easier, there are two very useful functions available
+in zope.app.generations.utility: findObjectsMatching() and
+findObjectsProviding(). They will dig through containers recursively to help
+you seek out old objects that you wish to update, by interface or by some other
+criteria. They are easy to understand, check their docstrings.
+
+
+Generations in action
+---------------------
+
+So, our furious client downloads our latest code and restarts Zope. The event
+is automatically sent again:
+
+ >>> event = DatabaseOpenedEventStub(db)
+ >>> evolveMinimumSubscriber(event)
+
+Shazam! The client is happy again!
+
+ >>> pprint(root['answers'])
+ {'Hello': 'Hi & how do you do?',
+ 'Meaning of life?': '42',
+ 'four < ?': 'four < five'}
+
+Because evolveMinimumSubscriber is very lazy, it only updates the database just
+enough so that your application can use it (to the `minimum_generation`, that
+is). Indeed, the marker indicates that the database generation has been bumped
+to 1:
+
+ >>> root[generations_key]['some.app']
+ 1
+
+We see that generations are working, so we decide to take the next step
+and evolve to generation 2. Let's see how this can be done manually:
+
+ >>> from zope.app.generations.generations import evolve
+ >>> evolve(db)
+
+ >>> pprint(root['answers'])
+ {'Hello': 'Hi & how do you do?',
+ 'Meaning of life?': '42',
+ 'four < ?': 'four < five'}
+ >>> root[generations_key]['some.app']
+ 2
+
+Default behaviour of `evolve` upgrades to the latest generation provided by
+the SchemaManager. You can use the `how` argument to evolve() when you want
+just to check if you need to update or if you want to be lazy like the
+subscriber which we have called previously.
+
+
+Let's clean up after ourselves:
+
+ >>> conn.close()
+ >>> db.close()
+ >>> placelesssetup.tearDown()
Property changes on: Zope3/trunk/src/zope/app/generations/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Zope3-Checkins
mailing list