Function
zope.app.generations.generations.evolve

Signature

evolve(db, how=0)

Documentation String

Evolve a database

We evolve a database using registered application schema managers. Here's an example (silly) schema manager:

>>> from zope.app.generations.interfaces import ISchemaManager
>>> class FauxApp(object):
...     zope.interface.implements(ISchemaManager)
...
...     erron = None # Raise an error is asked to evolve to this
...
...     def __init__(self, name, minimum_generation, generation):
...         self.name, self.generation = name, generation
...         self.minimum_generation = minimum_generation
...
...     def evolve(self, context, generation):
...         if generation == self.erron:
...             raise ValueError(generation)
...
...         context.connection.root()[self.name] = generation

We also need to set up the component system, since we'll be registering utilities:

>>> from zope.app.testing.placelesssetup import setUp, tearDown
>>> setUp()

Now, we'll create and register some handlers:

>>> from zope.app.testing import ztapi
>>> app1 = FauxApp('app1', 0, 1)
>>> ztapi.provideUtility(ISchemaManager, app1, name='app1')
>>> app2 = FauxApp('app2', 5, 11)
>>> ztapi.provideUtility(ISchemaManager, app2, name='app2')

If we create a new database, and evolve it, we'll simply update the generation data:

>>> from ZODB.tests.util import DB
>>> db = DB()
>>> conn = db.open()
>>> root = conn.root()
>>> evolve(db)
>>> conn.sync()
>>> root[generations_key]['app1']
1
>>> root[generations_key]['app2']
11

But nothing will have been done to the database:

>>> root.get('app1')
>>> root.get('app2')

Now if we increase the generation of one of the apps:

>>> app1.generation += 1
>>> evolve(db)

We'll see that the generation data has updated:

>>> conn.sync()
>>> root[generations_key]['app1']
2
>>> root[generations_key]['app2']
11

And that the database was updated for that application:

>>> root.get('app1')
2
>>> root.get('app2')

If there is an error updating a particular generation, but the generation is greater than the minimum generation, then we won't get an error from evolve, but we will get a log message.

>>> from zope.testing import loggingsupport
>>> handler = loggingsupport.InstalledHandler('zope.app.generations')
>>> app1.erron = 4
>>> app1.generation = 7
>>> evolve(db)
>>> print handler
zope.app.generations ERROR
  Failed to evolve database to generation 4 for app1

The database will have been updated for previous generations:

>>> conn.sync()
>>> root[generations_key]['app1']
3
>>> root.get('app1')
3

If we set the minimum generation for app1 to something greater than 3:

>>> app1.minimum_generation = 5

Then we'll get an error if we try to evolve, since we can't get past 3 and 3 is less than 5:

>>> evolve(db)
Traceback (most recent call last):
...
UnableToEvolve: (4, u'app1', 7)

We'll also get a log entry:

>>> print handler
zope.app.generations ERROR
  Failed to evolve database to generation 4 for app1
zope.app.generations ERROR
  Failed to evolve database to generation 4 for app1

So far, we've used evolve in its default policy, in which we evolve as far as we can up to the current generation. There are two other policies:

EVOLVENOT -- Don't evolve, but make sure that the application is
at the minimum generation

EVOLVEMINIMUM -- Evolve only to the minimum generation

Let's change unset erron for app1 so we don't get an error when we try to evolve.

>>> app1.erron = None

Now, we'll call evolve with EVOLVENOT:

>>> evolve(db, EVOLVENOT)
Traceback (most recent call last):
...
GenerationTooLow: (3, u'app1', 5)

We got an error because we aren't at the minimum generation for app1. The database generation for app1 is still 3 because we didn't do any evolution:

>>> conn.sync()
>>> root[generations_key]['app1']
3
>>> root.get('app1')
3

Now, if we use EVOLVEMINIMUM instead, we'll evolve to the minimum generation:

>>> evolve(db, EVOLVEMINIMUM)
>>> conn.sync()
>>> root[generations_key]['app1']
5
>>> root.get('app1')
5

If we happen to install an app that has a generation that is less that the database generation, we'll get an error, because there is no way to get the database to a generation that the app understands:

>>> app1.generation = 2
>>> app1.minimum_generation = 0
>>> evolve(db)
Traceback (most recent call last):
...
GenerationTooHigh: (5, u'app1', 2)

We'd better clean up:

>>> handler.uninstall()
>>> conn.close()
>>> db.close()
>>> tearDown()