[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 &amp; how do you do?',
+    ...                            'Meaning of life?': '42',
+    ...                            'four &lt; ?': 'four &lt; 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 &amp; how do you do?',
+     'Meaning of life?': '42',
+     'four &lt; ?': 'four &lt; 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