[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/generations/ Added
an "install" protocol. Applications can now provide an install
Jim Fulton
jim at zope.com
Fri Feb 18 19:08:56 EST 2005
Log message for revision 29216:
Added an "install" protocol. Applications can now provide an install
script that is run when an application is present for the first time.
Changed:
U Zope3/trunk/src/zope/app/generations/README.txt
U Zope3/trunk/src/zope/app/generations/browser/managers.py
A Zope3/trunk/src/zope/app/generations/demo/install.py
A Zope3/trunk/src/zope/app/generations/demo2/
A Zope3/trunk/src/zope/app/generations/demo2/__init__.py
U Zope3/trunk/src/zope/app/generations/generations.py
U Zope3/trunk/src/zope/app/generations/interfaces.py
U Zope3/trunk/src/zope/app/generations/tests.py
-=-
Modified: Zope3/trunk/src/zope/app/generations/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/generations/README.txt 2005-02-19 00:08:54 UTC (rev 29215)
+++ Zope3/trunk/src/zope/app/generations/README.txt 2005-02-19 00:08:56 UTC (rev 29216)
@@ -18,8 +18,7 @@
>>> import cgi
>>> from pprint import pprint
>>> from zope.interface import implements
- >>> from zope.app.testing import placelesssetup, ztapi
- >>> placelesssetup.setUp()
+ >>> from zope.app.testing import ztapi
>>> from ZODB.tests.util import DB
>>> db = DB()
@@ -32,7 +31,8 @@
>>> root['answers'] = {'Hello': 'Hi & how do you do?',
... 'Meaning of life?': '42',
... 'four < ?': 'four < five'}
- >>> get_transaction().commit()
+ >>> import transaction
+ >>> transaction.commit()
Initial setup
@@ -55,9 +55,10 @@
'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:
+When you start Zope and a database is opened, an
+IDatabaseOpenedWithRootEvent is sent. Zope registers
+evolveMinimumSubscriber by default as a handler for this event. Let's
+simulate this:
>>> class DatabaseOpenedEventStub(object):
... def __init__(self, database):
@@ -111,7 +112,7 @@
... else:
... raise ValueError("Bummer")
... root['answers'] = answers # ping persistence
- ... get_transaction().commit()
+ ... transaction.commit()
>>> manager = MySchemaManager()
>>> ztapi.provideUtility(ISchemaManager, manager, name='some.app')
@@ -185,9 +186,72 @@
just to check if you need to update or if you want to be lazy like the
subscriber which we have called previously.
+Installation
+------------
-Let's clean up after ourselves:
+In the the example above, we manually initialized the answers. We
+shouldn't have to do that manually. The application should be able to
+do that automatically.
- >>> conn.close()
+IInstallableSchemaManager extends ISchemaManager, providing an install
+method for performing an intial installation of an application. This
+is a better alternative than registering database-opened subscribers.
+
+Let's define a new schema manager that includes installation:
+
+
+ >>> ztapi.unprovideUtility(ISchemaManager, name='some.app')
+
+ >>> from zope.app.generations.interfaces import IInstallableSchemaManager
+ >>> class MySchemaManager(object):
+ ... implements(IInstallableSchemaManager)
+ ...
+ ... minimum_generation = 1
+ ... generation = 2
+ ...
+ ... def install(self, context):
+ ... root = context.connection.root()
+ ... root['answers'] = {'Hello': 'Hi & how do you do?',
+ ... 'Meaning of life?': '42',
+ ... 'four < ?': 'four < five'}
+ ... transaction.commit()
+ ...
+ ... 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
+ ... transaction.commit()
+
+ >>> manager = MySchemaManager()
+ >>> ztapi.provideUtility(ISchemaManager, manager, name='some.app')
+
+Now, lets open a new database:
+
>>> db.close()
- >>> placelesssetup.tearDown()
+ >>> db = DB()
+ >>> conn = db.open()
+ >>> 'answers' in conn.root()
+ False
+
+
+ >>> event = DatabaseOpenedEventStub(db)
+ >>> evolveMinimumSubscriber(event)
+
+ >>> conn.sync()
+ >>> root = conn.root()
+
+ >>> pprint(root['answers'])
+ {'Hello': 'Hi & how do you do?',
+ 'Meaning of life?': '42',
+ 'four < ?': 'four < five'}
+ >>> root[generations_key]['some.app']
+ 2
Modified: Zope3/trunk/src/zope/app/generations/browser/managers.py
===================================================================
--- Zope3/trunk/src/zope/app/generations/browser/managers.py 2005-02-19 00:08:54 UTC (rev 29215)
+++ Zope3/trunk/src/zope/app/generations/browser/managers.py 2005-02-19 00:08:56 UTC (rev 29216)
@@ -108,8 +108,11 @@
>>> from zope.app.generations.demo import key
>>> conn.root()[key]
- (2,)
+ ('installed', 'installed', 2)
+ Note that, because the demo package has an install script,
+ we have entries for that script.
+
Which the returned status should indicate:
>>> status['app']
@@ -126,8 +129,8 @@
>>> conn.root()[generations_key]['foo.app1']
2
>>> conn.root()[key]
- (2,)
-
+ ('installed', 'installed', 2)
+
as the status will indicate by returning a 'to' generation
of 0:
@@ -147,7 +150,7 @@
>>> conn.root()[generations_key]['foo.app1']
2
>>> conn.root()[key]
- (2,)
+ ('installed', 'installed', 2)
We'd better clean upp:
Added: Zope3/trunk/src/zope/app/generations/demo/install.py
===================================================================
--- Zope3/trunk/src/zope/app/generations/demo/install.py 2005-02-19 00:08:54 UTC (rev 29215)
+++ Zope3/trunk/src/zope/app/generations/demo/install.py 2005-02-19 00:08:56 UTC (rev 29216)
@@ -0,0 +1,25 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Silly demo evolution module
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+generation = 3
+
+import zope.app.generations.demo
+
+def evolve(context):
+ zope.app.generations.demo.evolve(context, 'installed')
Property changes on: Zope3/trunk/src/zope/app/generations/demo/install.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/zope/app/generations/demo2/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/generations/demo2/__init__.py 2005-02-19 00:08:54 UTC (rev 29215)
+++ Zope3/trunk/src/zope/app/generations/demo2/__init__.py 2005-02-19 00:08:56 UTC (rev 29216)
@@ -0,0 +1 @@
+#
Property changes on: Zope3/trunk/src/zope/app/generations/demo2/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/generations/generations.py
===================================================================
--- Zope3/trunk/src/zope/app/generations/generations.py 2005-02-19 00:08:54 UTC (rev 29215)
+++ Zope3/trunk/src/zope/app/generations/generations.py 2005-02-19 00:08:56 UTC (rev 29216)
@@ -18,7 +18,7 @@
__docformat__ = 'restructuredtext'
from interfaces import GenerationTooHigh, GenerationTooLow, UnableToEvolve
-from interfaces import ISchemaManager
+from interfaces import ISchemaManager, IInstallableSchemaManager
import logging
import os
import zope.interface
@@ -29,12 +29,12 @@
class SchemaManager(object):
"""Schema manager
- Schema managers implement `ISchemaManager` using scripts provided
- as module methods. You create a schema manager by providing
- mimumum and maximum generations and a package providing modules
- named ``evolveN``, where ``N`` is a generation number. Each module
- provides a function, `evolve` that evolves a database from the
- previous generation.
+ Schema managers implement `IInstallableSchemaManager` using
+ scripts provided as module methods. You create a schema
+ manager by providing mimumum and maximum generations and a
+ package providing modules named ``evolveN``, where ``N`` is a
+ generation number. Each module provides a function, `evolve`
+ that evolves a database from the previous generation.
For the sake of the example, we'll use the demo package defined
in here. See the modules there for simple examples of evolution
@@ -75,15 +75,36 @@
>>> manager.getInfo(3) is None
True
+ If a package provides an install script, then it will be called
+ when the manager's intall method is called:
+
+ >>> conn.sync()
+ >>> del conn.root()[key]
+ >>> get_transaction().commit()
+ >>> conn.root().get(key)
+
+ >>> manager.install(context)
+ >>> get_transaction().commit()
+ >>> conn.sync()
+ >>> conn.root()[key]
+ ('installed',)
+
+ If there is not install script, the manager will do nothing on
+ an install:
+
+ >>> manager = SchemaManager(1, 3, 'zope.app.generations.demo2')
+ >>> manager.install(context)
+
We'd better clean up:
>>> context.connection.close()
>>> conn.close()
>>> db.close()
+
"""
- zope.interface.implements(ISchemaManager)
+ zope.interface.implements(IInstallableSchemaManager)
def __init__(self, minimum_generation=0, generation=0, package_name=None):
if generation < minimum_generation:
@@ -105,12 +126,26 @@
"""Evolve a database to reflect software/schema changes
"""
- evolver = __import__(
- "%s.evolve%d" % (self.package_name, generation),
- {}, {}, ['*'])
+ name = "%s.evolve%d" % (self.package_name, generation)
+ evolver = __import__(name, {}, {}, ['*'])
+
evolver.evolve(context)
+ def install(self, context):
+ """Evolve a database to reflect software/schema changes
+ """
+
+ name = "%s.install" % self.package_name
+
+ try:
+ evolver = __import__("%s.install" % self.package_name,
+ {}, {}, ['*'])
+ except ImportError:
+ pass
+ else:
+ evolver.evolve(context)
+
def getInfo(self, generation):
"""Get the information from the evolver function's doc string."""
evolver = __import__(
@@ -331,6 +366,9 @@
if generation is None:
# This is a new database, so no old data
+ if IInstallableSchemaManager.providedBy(manager):
+ manager.install(context)
+
generations[key] = manager.generation
get_transaction().commit()
continue
Modified: Zope3/trunk/src/zope/app/generations/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/generations/interfaces.py 2005-02-19 00:08:54 UTC (rev 29215)
+++ Zope3/trunk/src/zope/app/generations/interfaces.py 2005-02-19 00:08:56 UTC (rev 29216)
@@ -66,3 +66,15 @@
If no information is available, `None` should be returned.
"""
+
+class IInstallableSchemaManager(ISchemaManager):
+ """Manage schema evolution for an application, including installation."""
+
+ def install(context):
+ """Perform any initial installation tasks
+
+ The application has never had the application installed
+ before. The schema manager should bring the database to the
+ current generation.
+
+ """
Modified: Zope3/trunk/src/zope/app/generations/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/generations/tests.py 2005-02-19 00:08:54 UTC (rev 29215)
+++ Zope3/trunk/src/zope/app/generations/tests.py 2005-02-19 00:08:56 UTC (rev 29216)
@@ -17,13 +17,22 @@
"""
import unittest
-from zope.testing.doctestunit import DocTestSuite, DocFileSuite
+from zope.testing import doctest
+from zope.app.testing import placelesssetup
+
+def tearDownREADME(test):
+ placelesssetup.tearDown(test)
+ test.globs['db'].close()
+
def test_suite():
return unittest.TestSuite((
- DocFileSuite('README.txt'),
- DocTestSuite('zope.app.generations.generations'),
- DocTestSuite('zope.app.generations.utility'),
+ doctest.DocFileSuite(
+ 'README.txt',
+ setUp=placelesssetup.setUp, tearDown=tearDownREADME,
+ ),
+ doctest.DocTestSuite('zope.app.generations.generations'),
+ doctest.DocTestSuite('zope.app.generations.utility'),
))
if __name__ == '__main__':
More information about the Zope3-Checkins
mailing list