[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