Local Site Manager ================== We start from an empty folder. Since ``OFS.Folder``'s extend ``ObjectSiteManager`` they all get to be ``IPossibleSite``'s. >>> from OFS.Folder import Folder >>> site = Folder('site') Of course we now need to transform that IPossibleSite into a real ISite. >>> import zope.component >>> from zope.site.hooks import setSite >>> from zope.site.hooks import setHooks >>> setHooks() >>> from five.localsitemanager import make_objectmanager_site >>> make_objectmanager_site(site) >>> sitemanager = site.getSiteManager() >>> sitemanager Make sure this site has it's ``__bases__`` configure appropriately. >>> sitemanager.__bases__ (,) Utilities --------- Utilities can now be registered with our site manager. We can confirm this by setting up a test utility. >>> from OFS.SimpleItem import SimpleItem >>> from zope import interface >>> class ITestUtility(interface.Interface): pass >>> class TestUtility(object): ... interface.implements(ITestUtility) ... def __init__(self, id): ... self.id = id ... def __repr__(self): ... return '' % (self.__class__.__name__, self.id) >>> sitemanager.registerUtility(TestUtility('test'), ... name=u'hello_world', ... provided=ITestUtility) >>> sitemanager.getUtility(ITestUtility, name=u'hello_world') Make sure the utility lookup only works when the correct active site has been configured. >>> setSite() >>> zope.component.queryUtility(ITestUtility, name=u'hello_world') is None True >>> setSite(site) >>> zope.component.queryUtility(ITestUtility, name=u'hello_world') Utilities are also registrable as factory: >>> def test_utility_factory(): ... return TestUtility('factory_test') >>> sitemanager.registerUtility(factory=test_utility_factory, ... name=u'factory_test', ... provided=ITestUtility) >>> sitemanager.getUtility(ITestUtility, name=u'factory_test') You can unregister a registered utility: >>> sitemanager.registerUtility(TestUtility('test_to_be_removed'), ... name='to_remove', ... provided=ITestUtility) >>> utility = sitemanager.getUtility(ITestUtility, name='to_remove') >>> utility >>> sitemanager.unregisterUtility(utility, name='to_remove') True >>> sitemanager.getUtility(ITestUtility, name='to_remove') Traceback (most recent call last): ... ComponentLookupError: (, 'to_remove') Trying to unregister a utility that is not registered should return False. >>> sitemanager.unregisterUtility(provided=ITestUtility) False Adapters --------- Adapters can now be registered with our site manager. We can confirm this by setting up a test adapter. >>> from OFS.SimpleItem import SimpleItem >>> from zope import interface >>> class IFoo(interface.Interface): pass >>> class Foo(object): ... interface.implements(IFoo) ... def __init__(self, id): ... self.id = id >>> class ITestAdapter(interface.Interface): pass >>> class TestAdapter(object): ... interface.implements(ITestAdapter) ... def __init__(self, context): ... self.context = context ... def __repr__(self): ... return '' % (self.__class__.__name__, ... self.context.id) >>> sitemanager.registerAdapter(TestAdapter, ... required=(IFoo,), ... provided=ITestAdapter) >>> sitemanager.getAdapter(Foo('foo'), ITestAdapter) Make sure the adapter lookup only works when the correct active site has been configured. >>> setSite() >>> zope.component.queryAdapter(Foo('foo'), ITestAdapter) is None True >>> setSite(site) >>> zope.component.queryAdapter(Foo('foo'), ITestAdapter) >>> ITestAdapter(Foo('foo')) Acquisition ----------- Now to mix a little required Zope 2 confusion into everything, we must ensure that the Acquisition 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 utility is Acquisition aware to begin with. >>> import Acquisition >>> from Acquisition.interfaces import IAcquirer First off, our utility isn't Acquisition-wrapped so asking it what is aq_parent is should return None. >>> comp = sitemanager.queryUtility(ITestUtility, name=u'hello_world') >>> Acquisition.aq_parent(comp) is None True So now we setup a utility that is Acquisition-aware. >>> class AQTestUtility(Acquisition.Explicit, TestUtility): pass >>> sitemanager.registerUtility(AQTestUtility('test'), ... name=u'aq_wrapped', ... provided=ITestUtility) And of course the Acquisition parent should be the site now. >>> comp = sitemanager.getUtility(ITestUtility, name=u'aq_wrapped') >>> Acquisition.aq_parent(comp) is site True And just to mix things up a bit. Getting back multiple utilities should allow us to test both Acquisition and non-Acquisition based components. We start with getUtilitiesFor(): >>> utils = [x for x in sitemanager.getUtilitiesFor(ITestUtility)] >>> len(utils) 3 >>> nonaqutils = [(name, comp) ... for name, comp in utils if not IAcquirer.providedBy(comp)] >>> len(nonaqutils) 2 >>> [Acquisition.aq_parent(comp) is None for name, comp in nonaqutils] [True, True] >>> aqutils = [(name, comp) ... for name, comp in utils if IAcquirer.providedBy(comp)] >>> len(aqutils) 1 >>> name, comp = aqutils[0] >>> Acquisition.aq_parent(comp) is site True And then getAllUtilitiesRegisteredFor(): >>> utils = [x for x in ... sitemanager.getAllUtilitiesRegisteredFor(ITestUtility)] >>> len(utils) 3 >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)] >>> len(nonaqutils) 2 >>> [Acquisition.aq_parent(comp) is None for comp in nonaqutils] [True, True] >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)] >>> len(aqutils) 1 >>> comp = aqutils[0] >>> Acquisition.aq_parent(comp) is site True And registeredUtilities(): >>> sorted(sitemanager.registeredUtilities(), key=lambda x:x.name) [UtilityRegistration(, ITestUtility, u'aq_wrapped', , None, u''), UtilityRegistration(, ITestUtility, u'factory_test', , , u''), UtilityRegistration(, ITestUtility, u'hello_world', , None, u'')] >>> utils = [ r.component for r in sitemanager.registeredUtilities() ] >>> len(utils) 3 >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)] >>> len(nonaqutils) 2 >>> [Acquisition.aq_parent(comp) is None for comp in nonaqutils] [True, True] >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)] >>> len(aqutils) 1 >>> comp = aqutils[0] >>> Acquisition.aq_parent(comp) is site True And finally unregisterUtility(): >>> sitemanager.registerUtility(AQTestUtility('aq_to_remove'), ... name=u'aq_to_remove', ... provided=ITestUtility) >>> utility = sitemanager.getUtility(ITestUtility, name='aq_to_remove') >>> utility >>> sitemanager.unregisterUtility(utility, ... provided=ITestUtility, ... name='aq_to_remove') True >>> sitemanager.getUtility(ITestUtility, name='aq_to_remove') Traceback (most recent call last): ... ComponentLookupError: (, 'aq_to_remove') Absolute Path ~~~~~~~~~~~~~ The approach to return utilities with an 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 the original code 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) >>> setSite(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 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=u'with_aq_chain') >>> si_util >>> si_util.getPhysicalPath() ('', 'a', 'b', 'si_util') >>> si_util.aq_chain [, , , ] >>> si_util.absolute_url() 'a/b/si_util' >>> zope.component.getUtility(ITestUtility, name=u'with_aq_chain') 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=u'with_aq_chain') And just to mix things up a bit. Getting back multiple utilities should allow us to test Acquisition, non-Acquisition based components and components having an absolute Acquisition path. First we have to register Acquisition and non-Acquisition based components in our registry (a component having an absolute Acquisition 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', ), (u'with_aq_chain', )] 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 >>> sorted(aqutils, key=lambda x:x.id) [, ] And registeredUtilities(): >>> sorted(sitemanager_a.registeredUtilities(), key=lambda x:x.name) [UtilityRegistration(, ITestUtility, u'aq_wrapped', , None, u''), UtilityRegistration(, ITestUtility, u'hello_world', , None, u''), UtilityRegistration(, ITestUtility, u'with_aq_chain', si_util_cped, None, u'')] >>> 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 >>> sorted(aqutils, key=lambda x:x.id) [, ] And unregisterUtility(): >>> si_to_remove = self.app.a.b._setObject('si_to_remove', ... SITestUtility('si_to_remove')) >>> sitemanager_a.registerUtility(self.app.a.b.si_to_remove, ... name=u'with_aq_chain_to_remove', ... provided=ITestUtility) >>> utility = sitemanager_a.getUtility(ITestUtility, ... name=u'with_aq_chain_to_remove') >>> utility >>> sitemanager_a.unregisterUtility(utility, ... provided=ITestUtility, ... name=u'with_aq_chain_to_remove') True >>> sitemanager_a.getUtility(ITestUtility, ... name=u'with_aq_chain_to_remove') Traceback (most recent call last): ... ComponentLookupError: (, u'with_aq_chain_to_remove') Nested Sites ------------ Whenever a component is queried using the component registry, the active component registry (i.e. site manager) needs to be smart enough to check all *parent* component registries as well. Implementation-wise this means that each component registry needs to have an appropriate ``__bases__`` attribute configured that is aware of containment and (in the case of Zope 2) Acquisition to some respect. Start by creating some nested sites. >>> from five.localsitemanager import update_sitemanager_bases >>> folder1 = Folder('folder1') >>> make_objectmanager_site(folder1) >>> update_sitemanager_bases(folder1) >>> folder1_1 = Folder('folder1_1') >>> make_objectmanager_site(folder1_1) >>> ignored = folder1._setObject('folder1_1', folder1_1) >>> folder1_1 = folder1['folder1_1'] >>> update_sitemanager_bases(folder1_1) Now we check the actual next-site-lookup logic to make sure it's working. >>> from five.localsitemanager import find_next_sitemanager, get_parent Needed to implement our own get_parent that is Acquisition aware. >>> get_parent(folder1) Traceback (most recent call last): ... TypeError: ('Not enough context... >>> get_parent(folder1_1) Any logic that sets up a site manager's ``__bases__`` will use the ``find_next_sitemanager`` function to figure out the next closest place to look. >>> find_next_sitemanager(folder1) is None True >>> find_next_sitemanager(folder1_1) Now we make sure that that the ``__bases__`` have been setup appropriately. >>> folder1.getSiteManager().__bases__ (,) >>> folder1_1.getSiteManager().__bases__ (,) Acquisition Context with Nested Sites ------------------------------------- >>> from zope.component import queryUtility Register a utility with both of the nested site managers: >>> sm1 = folder1.getSiteManager() >>> sm1.registerUtility(AQTestUtility('util1'), ... name=u'util1', ... provided=ITestUtility) >>> sm1_1 = folder1_1.getSiteManager() >>> sm1_1.registerUtility(AQTestUtility('util1_1'), ... name=u'util1_1', ... provided=ITestUtility) >>> folder1_1.getSiteManager().__bases__ (,) Lookup both utilities in the context of the first site manager: >>> setSite(folder1) >>> util1 = queryUtility(ITestUtility, name=u'util1') >>> util1 >>> util1.aq_chain [, ] The second utility isn't available in the first site manager: >>> queryUtility(ITestUtility, name=u'util_1') is None True Lookup both utilities in the context of the second site manager: >>> setSite(folder1_1) >>> util1 = queryUtility(ITestUtility, name=u'util1') >>> util1 We expect to get wrapped in the context of the site manager the utility is registered with: >>> util1.aq_chain [, ] >>> util1_1 = queryUtility(ITestUtility, name=u'util1_1') >>> util1_1 >>> util1_1.aq_chain [, , ] 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') Acquisition Context of Global Utilities --------------------------------------- >>> from zope.component import getGlobalSiteManager Register a utility with both of the nested site managers: >>> gsm = getGlobalSiteManager() >>> gsm.registerUtility(AQTestUtility('globalutil1'), ... name=u'globalutil1', ... provided=ITestUtility) >>> sm1 = folder1.getSiteManager() >>> sm1.registerUtility(AQTestUtility('localutil1'), ... name=u'localutil1', ... provided=ITestUtility) Lookup both utilities in the context of the global site manager: >>> setSite() >>> globalutil1 = queryUtility(ITestUtility, name=u'globalutil1') >>> globalutil1 >>> getattr(globalutil1, 'aq_chain', None) is None True The local utility isn't available in the global site manager: >>> queryUtility(ITestUtility, name=u'localutil1') is None True Lookup both utilities in the context of the local site manager: >>> setSite(folder1) >>> globalutil1 = queryUtility(ITestUtility, name=u'globalutil1') >>> globalutil1 We expect the global utility to get no Acquisition context: >>> getattr(globalutil1, 'aq_chain', None) is None True For the local utility we expect to get wrapped in the context of the local site manager: >>> localutil1 = queryUtility(ITestUtility, name=u'localutil1') >>> localutil1 >>> localutil1.aq_chain [, ] Clean up -------- >>> from zope.testing.cleanup import cleanUp >>> setSite() >>> cleanUp()