[Checkins] SVN: five.localsitemanager/trunk/ merged icemac-absolute-path-components: implement registry returning utilities with correct absolute path
Michael Howitz
mh at gocept.com
Tue Aug 26 11:22:41 EDT 2008
Log message for revision 90341:
merged icemac-absolute-path-components: implement registry returning utilities with correct absolute path
Changed:
U five.localsitemanager/trunk/CHANGES.txt
U five.localsitemanager/trunk/src/five/localsitemanager/localsitemanager.txt
U five.localsitemanager/trunk/src/five/localsitemanager/registry.py
-=-
Modified: five.localsitemanager/trunk/CHANGES.txt
===================================================================
--- five.localsitemanager/trunk/CHANGES.txt 2008-08-26 15:17:06 UTC (rev 90340)
+++ five.localsitemanager/trunk/CHANGES.txt 2008-08-26 15:22:41 UTC (rev 90341)
@@ -6,6 +6,22 @@
* Added buildout for project, so testing can be done using ``bin/test``.
+* Added ability to register utilities with an absolute path. These
+ utilities are returned wrapped into their original context. This
+ change is backward compatible to existing registries.
+
+ But registering utilities having an acquisition context will behave
+ different because these utilities will be returned in their original
+ context. To restore the previous behavior, register utilities
+ unwrapped (aq_base).
+
+ For storing path information the component must implement
+ getPhysicalPath and have an absolute path.
+
+ When a component registered as utility is moved and registered again
+ the path stored in registry gets updated.
+
+
0.4 - 2008-07-23
----------------
Modified: five.localsitemanager/trunk/src/five/localsitemanager/localsitemanager.txt
===================================================================
--- five.localsitemanager/trunk/src/five/localsitemanager/localsitemanager.txt 2008-08-26 15:17:06 UTC (rev 90340)
+++ five.localsitemanager/trunk/src/five/localsitemanager/localsitemanager.txt 2008-08-26 15:22:41 UTC (rev 90341)
@@ -108,7 +108,12 @@
-----------
Now to mix a little required Zope 2 confusion into everything, we must ensure
-that the aq chain is predictable. And based on consensus we decided that the
+that the aq chain is predictable.
+
+Path relative to component registry
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+And based on consensus we decided that the
acquired parent of a returned utility should be the ``ISite`` that owns the
``ISiteManager`` that returned the utility. We need to ensure all the ways of
getting a utility have been covered. Of course this should only happen if the
@@ -203,6 +208,154 @@
>>> Acquisition.aq_parent(comp) is site
True
+Absolute Path
+~~~~~~~~~~~~~
+
+The approach to return utilites with a acquisition chain relative to
+the component registry has the problem that it can return the wrong
+physical path for the utility so computed URLs for the utility are
+also wrong.
+
+This can be fixed when the parent object of a utility is a local
+component registry. But this is not like in Zope3 and has the problem
+that utility registration is only visible below its registry.
+
+Alternative solution: register the utility with its acquisition
+context and it will be returned wrapped into its original
+context. (Only the physical path is stored not the context itself.)
+
+We set up a hierarchy of folders to show the behavior:
+
+ >>> a = self.app._setObject('a', Folder('a'), set_owner=False)
+ >>> b = self.app.a._setObject('b', Folder('b'), set_owner=False)
+ >>> util = self.app.a.b._setObject(
+ ... 'util', AQTestUtility('util'), set_owner=False)
+
+Now we can make `a` a local component registry an register `util`
+there. We expect the utility to implement getPhysicalPath and raise an
+exception otherwise:
+
+ >>> make_objectmanager_site(self.app.a)
+ >>> setActiveSite(self.app.a)
+ >>> sitemanager_a = self.app.a.getSiteManager()
+ >>> sitemanager_a.registerUtility(self.app.a.b.util,
+ ... name=u'with_aq_chain',
+ ... provided=ITestUtility)
+ Traceback (most recent call last):
+ AttributeError: Component <Utility AQTestUtility "util"> does not implement getPhysicalPath, so register it unwrapped or implement this method.
+ >>> import OFS.SimpleItem
+ >>> class SITestUtility(OFS.SimpleItem.SimpleItem, TestUtility): pass
+ >>> si_util = self.app.a.b._setObject('si_util', SITestUtility('si_util'))
+ >>> sitemanager_a.registerUtility(self.app.a.b.si_util,
+ ... name=u'with_aq_chain',
+ ... provided=ITestUtility)
+
+When we query the utility, it is returned with its original context:
+
+ >>> si_util = sitemanager_a.getUtility(ITestUtility, name='with_aq_chain')
+ >>> si_util
+ <SITestUtility at /a/b/si_util>
+ >>> si_util.getPhysicalPath()
+ ('', 'a', 'b', 'si_util')
+ >>> si_util.aq_chain
+ [<SITestUtility at /a/b/si_util>, <Folder at /a/b>, <Folder at /a>, <Application at >, <ZPublisher.BaseRequest.RequestContainer object at 0x...>]
+ >>> si_util.absolute_url()
+ 'http://nohost/a/b/si_util'
+
+ >>> zope.component.getUtility(ITestUtility, name='with_aq_chain')
+ <SITestUtility at /a/b/si_util>
+
+If we move a registered component (which has an absolute path) to new
+place, the registration gets updated after calling registerUtility
+again:
+
+ >>> ignored = self.app.a.b._setObject('c', Folder('c'))
+ >>> si_util = self.app.a.b.si_util.aq_base
+ >>> self.app.a.b._delObject('si_util')
+ >>> si_util.id = 'si_util_cped'
+ >>> ignored = self.app.a.b.c._setObject('si_util_cped', si_util)
+ >>> sitemanager_a.registerUtility(
+ ... self.app.a.b.c.si_util_cped,
+ ... name=u'with_aq_chain',
+ ... provided=ITestUtility)
+ >>> zope.component.getUtility(ITestUtility, name='with_aq_chain')
+ <SITestUtility at /a/b/c/si_util_cped>
+
+And just to mix things up a bit. Getting back multiple utilities
+should allow us to test aq, non-aq based components and components
+having an absolute aqusition path.
+
+First we have to register aq and non-aq based components in our
+registry (a component having an absolute aqusition path already exists):
+
+ >>> sitemanager_a.registerUtility(AQTestUtility('test'),
+ ... name=u'aq_wrapped',
+ ... provided=ITestUtility)
+ >>> sitemanager_a.registerUtility(TestUtility('test'),
+ ... name=u'hello_world',
+ ... provided=ITestUtility)
+
+We start with getUtilitiesFor():
+
+ >>> utils = [x for x in sitemanager_a.getUtilitiesFor(ITestUtility)]
+ >>> len(utils)
+ 3
+
+ >>> nonaqutils = [(name, comp)
+ ... for name, comp in utils if not IAcquirer.providedBy(comp)]
+ >>> len(nonaqutils)
+ 1
+ >>> name, comp = nonaqutils[0]
+ >>> Acquisition.aq_parent(comp) is None
+ True
+
+ >>> aqutils = [(name, comp)
+ ... for name, comp in utils if IAcquirer.providedBy(comp)]
+ >>> len(aqutils)
+ 2
+ >>> aqutils
+ [(u'aq_wrapped', <Utility AQTestUtility "test">),
+ (u'with_aq_chain', <SITestUtility at /a/b/c/si_util_cped>)]
+
+And then getAllUtilitiesRegisteredFor():
+
+ >>> utils = [x for x in
+ ... sitemanager_a.getAllUtilitiesRegisteredFor(ITestUtility)]
+ >>> len(utils)
+ 3
+
+ >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)]
+ >>> len(nonaqutils)
+ 1
+ >>> comp = nonaqutils[0]
+ >>> Acquisition.aq_parent(comp) is None
+ True
+
+ >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)]
+ >>> len(aqutils)
+ 2
+ >>> aqutils
+ [<SITestUtility at /a/b/c/si_util_cped>, <Utility AQTestUtility "test">]
+
+And registeredUtilities():
+
+ >>> utils = [r.component for r in sitemanager_a.registeredUtilities()]
+ >>> len(utils)
+ 3
+
+ >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)]
+ >>> len(nonaqutils)
+ 1
+ >>> comp = nonaqutils[0]
+ >>> Acquisition.aq_parent(comp) is None
+ True
+
+ >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)]
+ >>> len(aqutils)
+ 2
+ >>> aqutils
+ [<SITestUtility at /a/b/c/si_util_cped>, <Utility AQTestUtility "test">]
+
Nested Sites
------------
@@ -317,7 +470,23 @@
>>> util1_1.aq_chain
[<Utility AQTestUtility "util1_1">, <Folder at folder1/folder1_1>, <Folder at folder1>]
+Utilities stored with relative path
+-----------------------------------
+If we register a utility which has only a relative path, the path is
+_not_ stored and the utility is returned relative to the registry. (In
+the example we register folder_1/folder1_1/util in the registry of
+folder_1.):
+
+ >>> folder1_1._setObject('util', SITestUtility('util'), set_owner=False)
+ 'util'
+ >>> sm1.registerUtility(folder1_1.util,
+ ... name=u'util2',
+ ... provided=ITestUtility)
+ >>> sm1.getUtility(ITestUtility, name='util2')
+ <SITestUtility at folder1/util>
+
+
Acquisition Context of Global Utilities
---------------------------------------
Modified: five.localsitemanager/trunk/src/five/localsitemanager/registry.py
===================================================================
--- five.localsitemanager/trunk/src/five/localsitemanager/registry.py 2008-08-26 15:17:06 UTC (rev 90340)
+++ five.localsitemanager/trunk/src/five/localsitemanager/registry.py 2008-08-26 15:22:41 UTC (rev 90341)
@@ -1,15 +1,18 @@
import Acquisition
+import persistent
import OFS.ObjectManager
from Acquisition.interfaces import IAcquirer
from zope.app.component.hooks import getSite
from zope.app.component.interfaces import ISite
from zope.component.persistentregistry import PersistentAdapterRegistry
from zope.component.persistentregistry import PersistentComponents
-from zope.component.registry import UtilityRegistration
+from zope.component.registry import UtilityRegistration, _getUtilityProvided
from zope.interface.adapter import VerifyingAdapterLookup
from zope.interface.adapter import _lookup
from zope.interface.adapter import _lookupAll
from zope.interface.adapter import _subscriptions
+import zope.event
+import zope.component.interfaces
from ZPublisher.BaseRequest import RequestContainer
from five.localsitemanager.utils import get_parent
@@ -99,6 +102,11 @@
only if the comp has an aq wrapper to begin with.
"""
+ # If component is stored as a ComponentPathWrapper, we traverse to
+ # the component using the stored path:
+ if isinstance(comp, ComponentPathWrapper):
+ return getSite().unrestrictedTraverse(comp.path)
+
# BBB: The primary reason for doing this sort of wrapping of
# returned utilities is to support CMF tool-like functionality where
# a tool expects its aq_parent to be the portal object. New code
@@ -156,6 +164,16 @@
return base.__of__(_rewrap(parent))
+class ComponentPathWrapper(persistent.Persistent):
+
+ def __init__(self, component, path):
+ self.component = component
+ self.path = path
+
+ def __eq__(self, other):
+ return self.component == other
+
+
class PersistentComponents \
(PersistentComponents,
OFS.ObjectManager.ObjectManager):
@@ -177,3 +195,51 @@
reg.component=_wrap(reg.component, self)
yield reg
+ def registerUtility(self, component, provided=None, name=u'', info=u'',
+ event=True):
+ if provided is None:
+ provided = _getUtilityProvided(component)
+
+ registration = self._utility_registrations.get((provided, name))
+ if (registration == (component, info)):
+ # already registered
+ if isinstance(registration[0], ComponentPathWrapper):
+ self.utilities.unsubscribe((), provided, registration[0])
+ # update path
+ registration[0].path = component.getPhysicalPath()
+ self.utilities.subscribe((), provided, registration[0])
+ return
+
+ subscribed = False
+ for ((p, _), data) in self._utility_registrations.iteritems():
+ if p == provided and data[0] == component:
+ subscribed = True
+ break
+
+ wrapped_component = component
+ if hasattr(component, 'aq_parent'):
+ # component is acquisition wrapped, so try to store path
+ if not hasattr(component, 'getPhysicalPath'):
+ raise AttributeError(
+ 'Component %r does not implement getPhysicalPath, '
+ 'so register it unwrapped or implement this method.' %
+ component)
+ path = component.getPhysicalPath()
+ # If the path is relative we can't store it because we
+ # have nearly no chance to use the path for traversal in
+ # getUtility.
+ if path[0] == '':
+ # We have an absolute path, so we can store it.
+ wrapped_component = ComponentPathWrapper(
+ Acquisition.aq_base(component), path)
+ self._utility_registrations[(provided, name)] = wrapped_component, info
+ self.utilities.register((), provided, name, wrapped_component)
+
+ if not subscribed:
+ self.utilities.subscribe((), provided, wrapped_component)
+
+ if event:
+ zope.event.notify(zope.component.interfaces.Registered(
+ UtilityRegistration(self, provided, name, component, info)
+ ))
+
More information about the Checkins
mailing list