[Zope3-checkins] SVN: Zope3/branches/srichter-blow-services/src/zope/app/ Persistent Modules work again.

Stephan Richter srichter at cosmos.phy.tufts.edu
Mon Jan 10 17:29:05 EST 2005


Log message for revision 28777:
  Persistent Modules work again.
  

Changed:
  U   Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml
  A   Zope3/branches/srichter-blow-services/src/zope/app/module/README.txt
  U   Zope3/branches/srichter-blow-services/src/zope/app/module/__init__.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/module/browser/__init__.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/module/configure.zcml
  U   Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/adapter.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/configure.zcml
  A   Zope3/branches/srichter-blow-services/src/zope/app/module/manager.py
  A   Zope3/branches/srichter-blow-services/src/zope/app/module/tests.py

-=-
Modified: Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml	2005-01-10 22:29:04 UTC (rev 28777)
@@ -58,7 +58,7 @@
   <include package="zope.app.keyreference" />
 
   <!-- Misc. Service Manager objects -->
-  <!-- XXX: include package="zope.app.module" /-->
+  <include package="zope.app.module" />
 
   <!-- Broken-object support -->
   <include package="zope.app.broken" />

Added: Zope3/branches/srichter-blow-services/src/zope/app/module/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/README.txt	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/README.txt	2005-01-10 22:29:04 UTC (rev 28777)
@@ -0,0 +1,146 @@
+=========================
+Persistent Python Modules
+=========================
+
+Persistent Python modules allow us to develop and store Python modules in the
+ZODB in contrast to storing them on the filesystem. You might want to look at
+the `zodbcode` package for the details of the implementation. In Zope 3 we
+implemented persistent modules as utilities. These utilities are known as
+module managers that manage the source code, compiled module and name of the
+module. We then provide a special module registry that looks up the utilities
+to find modules.
+
+
+The Module Manager
+------------------
+
+One can simply create a new module manager by instantiating it:
+
+  >>> from zope.app.module.manager import ModuleManager
+  >>> manager = ModuleManager()
+
+If I create the manager without an argument, there is no source code:
+
+  >>> manager.source
+  ''
+  
+When we add some code 
+
+  >>> manager.source = """\n
+  ... foo = 1
+  ... def bar(): return foo
+  ... class Blah(object):
+  ...     def __init__(self, id): self.id = id 
+  ...     def __repr__(self): return 'Blah(id=%s)' %self.id 
+  ... """
+
+we can get the compiled module and use the created objects:
+
+  >>> module = manager.getModule()
+  >>> module.foo
+  1
+  >>> module.bar()
+  1
+  >>> module.Blah('blah')
+  Blah('blah')
+
+We can also ask for the name of the module:
+
+  >>> manager.name
+  >>> module.__name__
+
+But why is it `None`? Because we have no registered it yet. Once we register
+and activate the registration a name will be set:
+
+  >>> from zope.app.testing import setup
+  >>> root = setup.createSampleFolderTree()
+  >>> root_sm = setupcreateSiteManager(root)
+
+  >>> from zope.app.module import interfaces
+  >>> manager = setup.addUtility(root_sm, 'zope.mymodule',
+  ...                            interfaces.IModuleManager)
+
+  >>> manager.name
+  'zope.mymodule'
+  >>> manager.getModule().__name__
+  'zope.mymodule'  
+
+Next, let's ensure that the module's persistence works correctly. To do that
+let's create a database and add the root folder to it:
+
+  >>> from ZODB.tests.util import DB
+  >>> db = DB()
+  >>> conn = db.open()
+  >>> conn.root()['Application'] = root
+
+  >>> from transaction import get_transaction
+  >>> get_transaction().commit()
+
+Let's now reopen the database to test that the module can be seen from a
+different connection.
+          
+  >>> conn2 = db.open()
+  >>> root2 = conn2.root()['Application']
+  >>> module2 = root2.getSiteManager().queryUtility(
+  ...     interfaces.IModuleManager, 'zope.mymodule').getModule()
+  >>> module2.foo
+  1
+  >>> module2.bar()
+  1
+  >>> module2.Blah('blah')
+ 
+
+Module Lookup API
+-----------------
+
+The way the persistent module framework hooks into Python is via module
+registires that behave pretty much like `sys.modules`. Zope 3 provides its own
+module registry that uses the registered utilities to look up modules:
+
+  >>> from zope.app.module import ZopeModuleRegistry
+  >>> ZopeModuleRegistry.getModule('zope.mymodule')
+
+But why did we not get the module back? Because we have not set the site yet:
+
+  >>> from zope.app.component import hooks
+  >>> hooks.setSite(root)
+
+Now it will find the module and we can retrieve a list of all persistent
+module names:
+
+  >>> ZopeModuleRegistry.getModule('zope.mymodule') is module
+  True
+  >>> ZopeModuleRegistry.modules()
+  ['zope.mymodule']
+
+Additionally, the package provides two API functions that lookup a module in
+the registry and then in `sys.modules`:
+
+  >>> import zope.app.module
+  >>> zope.app.module.findModule('zope.mymodule') is module
+  True  
+  >>> zope.app.module.findModule('zope.app.module') is zope.app.module
+  True  
+
+The second function can be used to lookup objects inside any module:
+
+  >>> zope.app.module.resolve('zope.mymodule.foo')
+  1
+  >>> zope.app.module.resolve('zope.app.module.foo.resolve')
+
+In order to use this framework in real Python code import statements, we need
+to install the importer hook, which is commonly done with an event subscriber:
+
+  >>> event = object()
+  >>> zope.app.module.installPersistentModuleImporter(event)
+
+Now we can simply import the persistent module:
+
+  >>> import zope.mymodule
+  >>> zope.mymodule.Blah('my id')
+  Blah('my id')
+
+Finally, we unregister the hook again:
+
+  >>> zope.app.module.uninstallPersistentModuleImporter(event)
+

Modified: Zope3/branches/srichter-blow-services/src/zope/app/module/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/__init__.py	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/__init__.py	2005-01-10 22:29:04 UTC (rev 28777)
@@ -18,6 +18,7 @@
 __docformat__ = 'restructuredtext'
 import sys
 import zodbcode.interfaces
+import zodbcode.module
 
 from zope.interface import implements
 from zope.app import zapi
@@ -30,7 +31,7 @@
 
     def findModule(self, name):
         """See zodbcode.interfaces.IPersistentModuleImportRegistry"""
-        return zapi.getUtility(IModuleManager, name)
+        return zapi.queryUtility(IModuleManager, name)
 
     def modules(self):
         """See zodbcode.interfaces.IPersistentModuleImportRegistry"""
@@ -55,7 +56,11 @@
 
 # Installer function that can be called from ZCML.
 # This installs an import hook necessary to support persistent modules.
+importer = zodbcode.module.PersistentModuleImporter()
 
 def installPersistentModuleImporter(event):
-    from zodbcode.module import PersistentModuleImporter
-    PersistentModuleImporter().install()
+    importer.install()
+
+def uninstallPersistentModuleImporter(event):
+    importer.uninstall()
+

Modified: Zope3/branches/srichter-blow-services/src/zope/app/module/browser/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/browser/__init__.py	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/browser/__init__.py	2005-01-10 22:29:04 UTC (rev 28777)
@@ -17,40 +17,10 @@
 """
 __docformat__ = 'restructuredtext'
 
-from zope.app.module import Manager
-from zope.event import notify
-from zope.app.event.objectevent import ObjectCreatedEvent
-from zope.app.publisher.browser import BrowserView
 from zope.proxy import removeAllProxies
-from zope.app.exception.interfaces import UserError
 
-from zope.app.i18n import ZopeMessageIDFactory as _
+class ViewModule(object):
 
-
-class AddModule(BrowserView):
-
-    def action(self, source):
-        name = self.context.contentName
-        if not name:
-            raise UserError(_(u"module name must be provided"))
-        mgr = Manager(name, source)
-        mgr = self.context.add(mgr)  # local registration
-        mgr.execute()
-        self.request.response.redirect(self.context.nextURL())
-        notify(ObjectCreatedEvent(mgr))
-
-class EditModule(BrowserView):
-
-    def update(self):
-        if "source" in self.request:
-            self.context.source = self.request["source"]
-            self.context.execute()
-            return _(u"The source was updated.")
-        else:
-            return u""
-
-class ViewModule(BrowserView):
-
     def getModuleObjects(self):
         module = removeAllProxies(self.context.getModule())
         remove_keys = ['__name__', '__builtins__', '_p_serial']

Modified: Zope3/branches/srichter-blow-services/src/zope/app/module/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/configure.zcml	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/configure.zcml	2005-01-10 22:29:04 UTC (rev 28777)
@@ -1,6 +1,6 @@
 <configure xmlns="http://namespaces.zope.org/zope">
 
-  <localUtility class=".ModuleManager">
+  <localUtility class=".manager.ModuleManager">
     <require
         permission="zope.ManageCode"
         interface=".interfaces.IModuleManager"
@@ -19,10 +19,10 @@
      />
 
   <adapter
-      for="zope.app.site.interfaces.ISiteManagementFolder"
+      for="zope.app.component.interfaces.ISiteManagementFolder"
       provides="zope.app.filerepresentation.interfaces.IFileFactory"
       name=".py"
-      factory=".ModuleFactory"
+      factory=".manager.ModuleFactory"
       permission="zope.ManageContent"
       />
 

Modified: Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/adapter.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/adapter.py	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/adapter.py	2005-01-10 22:29:04 UTC (rev 28777)
@@ -1,3 +1,4 @@
+
 ##############################################################################
 #
 # Copyright (c) 2002 Zope Corporation and Contributors.

Modified: Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/configure.zcml	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/fssync/configure.zcml	2005-01-10 22:29:04 UTC (rev 28777)
@@ -4,7 +4,7 @@
     >
 
   <fssync:adapter
-      class="zope.app.module.Manager"
+      class="zope.app.module.manager.ModuleManager"
       factory=".adapter.ModuleAdapter"
       />
 

Added: Zope3/branches/srichter-blow-services/src/zope/app/module/manager.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/manager.py	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/manager.py	2005-01-10 22:29:04 UTC (rev 28777)
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# Copyright (c) 2002-2005 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.
+#
+##############################################################################
+"""Module Manager implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import persistent
+import zodbcode.module
+import zope.interface
+
+from zope.app.container.contained import Contained
+from zope.app.filerepresentation.interfaces import IFileFactory
+from zope.app.module.interfaces import IModuleManager
+
+
+class ModuleManager(persistent.Persistent, Contained):
+
+    zope.interface.implements(IModuleManager)
+
+    def __init__(self, source=''):
+        # The name is set, once the registration is activated.
+        self.name = None
+        self._source = None
+        self.source = source
+
+    def execute(self):
+        """See zope.app.module.interfaces.IModuleManager"""
+        try:
+            mod = self._module
+        except AttributeError:
+            mod = self._module = zodbcode.module.PersistentModule(self.name)
+
+        zodbcode.module.compileModule(mod, ZopeModuleRegistry, self.source)
+        self._recompile = False
+
+    def getModule(self):
+        """See zope.app.module.interfaces.IModuleManager"""
+        if self._recompile:
+            self.execute()
+        return self._module
+
+    def _getSource(self):
+        return self._source
+
+    def _setSource(self, source):
+        if self._source != source:
+            self._source = source
+            self._recompile = True
+
+    # See zope.app.module.interfaces.IModuleManager
+    source = property(_getSource, _setSource)
+
+
+class ModuleFactory(object):
+    """Special factory for creating module managers in site managment
+    folders."""
+    
+    zope.interface.implements(IFileFactory)
+
+    def __init__(self, context):
+        self.context = context
+
+    def __call__(self, name, content_type, data):
+        assert name.endswith(".py")
+        name = name[:-3]
+        m = ModuleManager(name, data)
+        m.__parent__ = self.context
+        m.execute()
+        return m
+
+
+def setNameOnActivation(event):
+    """Set the module name upon registration activation."""
+    module = event.object.component
+    if isinstance(module, ModuleManager):
+        module.name = event.object.name
+        module._recompile = True
+
+def unsetNameOnDeactivation(event):
+    """Unset the permission id up registration deactivation."""
+    module = event.object.component
+    if isinstance(module, ModuleManager):
+        module.name = None
+        module._recompile = True

Added: Zope3/branches/srichter-blow-services/src/zope/app/module/tests.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/module/tests.py	2005-01-10 21:58:02 UTC (rev 28776)
+++ Zope3/branches/srichter-blow-services/src/zope/app/module/tests.py	2005-01-10 22:29:04 UTC (rev 28777)
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Registration Tests
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import unittest
+
+from zope.testing import doctest
+from zope.app.component import interfaces
+from zope.app.module import manager
+from zope.app.testing import setup, ztapi
+
+def setUp(test):
+    setup.placefulSetUp()
+    ztapi.subscribe(
+        (interfaces.registration.IRegistrationActivatedEvent,), None,
+        manager.setNameOnActivation)
+    ztapi.subscribe(
+        (interfaces.registration.IRegistrationDeactivatedEvent,), None,
+        manager.unsetNameOnDeactivation)
+
+def tearDown(test):
+    setup.placefulTearDown()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('../README.txt',
+                             setUp=setUp, tearDown=tearDown),
+        ))
+
+if __name__ == "__main__":
+    unittest.main(defaultTest='test_suite')



More information about the Zope3-Checkins mailing list