[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