Skip to content

Commit

Permalink
branching version 1.0 (last version to support zope.component < 3.5.0)
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Howitz committed Aug 26, 2008
2 parents c4d8228 + a65898a commit 8d5e5f6
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 2 deletions.
16 changes: 16 additions & 0 deletions CHANGES.txt
Expand Up @@ -6,6 +6,22 @@ Changelog

* 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
----------------

Expand Down
171 changes: 170 additions & 1 deletion src/five/localsitemanager/localsitemanager.txt
Expand Up @@ -108,7 +108,12 @@ Acquisition
-----------

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
Expand Down Expand Up @@ -203,6 +208,154 @@ And registeredUtilities():
>>> 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
------------

Expand Down Expand Up @@ -317,6 +470,22 @@ registered with:
>>> 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
---------------------------------------
Expand Down
68 changes: 67 additions & 1 deletion src/five/localsitemanager/registry.py
@@ -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
Expand Down Expand Up @@ -99,6 +102,11 @@ def _wrap(comp, registry):
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
Expand Down Expand Up @@ -156,6 +164,16 @@ def _rewrap(obj):
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):
Expand All @@ -177,3 +195,51 @@ def registeredUtilities(self):
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)
))

0 comments on commit 8d5e5f6

Please sign in to comment.