[Zope3-checkins]
SVN: Zope3/branches/srichter-blow-services/src/zope/app/component/
Made the local site manager work, including correct hooks settings,
Stephan Richter
srichter at cosmos.phy.tufts.edu
Thu Jan 6 19:12:02 EST 2005
Log message for revision 28757:
Made the local site manager work, including correct hooks settings,
copy/paste and "acquisition".
Changed:
U Zope3/branches/srichter-blow-services/src/zope/app/component/adapter.py
U Zope3/branches/srichter-blow-services/src/zope/app/component/hooks.py
U Zope3/branches/srichter-blow-services/src/zope/app/component/interfaces/__init__.py
U Zope3/branches/srichter-blow-services/src/zope/app/component/site.py
U Zope3/branches/srichter-blow-services/src/zope/app/component/site.txt
-=-
Modified: Zope3/branches/srichter-blow-services/src/zope/app/component/adapter.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/adapter.py 2005-01-07 00:10:21 UTC (rev 28756)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/adapter.py 2005-01-07 00:12:01 UTC (rev 28757)
@@ -26,6 +26,7 @@
import zope.app.component.localservice
import zope.app.container.contained
import zope.app.site.interfaces
+from zope.app import zapi
from zope.app.component import registration
from zope.app.component import interfaces
from zope.app.i18n import ZopeMessageIDFactory as _
@@ -120,7 +121,7 @@
return self._registrations
def __getstate__(self):
- state = super(LocalAdapterRegistry, self).__getstate__().copy()
+ state = persistent.Persistent.__getstate__(self).copy()
for name in ('_default', '_null', 'adapter_hook',
'lookup', 'lookup1', 'queryAdapter', 'get',
@@ -130,8 +131,8 @@
return state
def __setstate__(self, state):
- super(LocalAdapterRegistry, self).__setstate__(state)
- super(LocalAdapterRegistry, self).__init__()
+ persistent.Persistent.__setstate__(self, state)
+ zope.interface.adapter.AdapterRegistry.__init__(self)
def baseFor(self, spec):
"""Used by LocalSurrogate"""
Modified: Zope3/branches/srichter-blow-services/src/zope/app/component/hooks.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/hooks.py 2005-01-07 00:10:21 UTC (rev 28756)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/hooks.py 2005-01-07 00:12:01 UTC (rev 28757)
@@ -69,6 +69,27 @@
def getSite():
return siteinfo.site
+
+def getSiteManager(context=None):
+ """A special hook for getting the site manager.
+
+ Here we take the currently set site into account to find the appropriate
+ site manager.
+ """
+ if context is None:
+ return siteinfo.sm
+
+ # We remove the security proxy because there's no way for
+ # untrusted code to get at it without it being proxied again.
+
+ # We should really look look at this again though, especially
+ # once site managers do less. There's probably no good reason why
+ # they can't be proxied. Well, except maybe for performance.
+ sm = zope.component.interfaces.ISiteManager(
+ context, zope.component.getGlobalSiteManager())
+ return zope.security.proxy.removeSecurityProxy(sm)
+
+
def adapter_hook(interface, object, name='', default=None):
try:
return siteinfo.adapter_hook(interface, object, name, default)
@@ -77,10 +98,11 @@
def setHooks():
- # Hook up a new implementation of looking up views.
zope.component.adapter_hook.sethook(adapter_hook)
+ zope.component.getSiteManager.sethook(getSiteManager)
def resetHooks():
# Reset hookable functions to original implementation.
zope.component.adapter_hook.reset()
+ zope.component.getSiteManager.reset()
Modified: Zope3/branches/srichter-blow-services/src/zope/app/component/interfaces/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/interfaces/__init__.py 2005-01-07 00:10:21 UTC (rev 28756)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/interfaces/__init__.py 2005-01-07 00:12:01 UTC (rev 28757)
@@ -17,7 +17,7 @@
"""
import zope.interface
import zope.schema
-from zope.component.interfaces import ISiteManager
+import zope.component
from zope.app.container.interfaces import IContainer
from zope.app.container.constraints import ContainerTypesConstraint
from zope.app.container.constraints import ItemTypePrecondition
@@ -36,30 +36,6 @@
This should only happen during testing
"""
-class IComponentManager(zope.interface.Interface):
-
- def queryComponent(type=None, filter=None, all=True):
- """Return all components that match the given type and filter
-
- The arguments are:
-
- type -- An argument is the interface a returned component must
- provide.
-
- filter -- A Python expression that must evaluate to `True` for any
- returned component; `None` means that no filter has been
- specified.
-
- all -- A flag indicating whether all component managers in
- this place should be queried, or just the local one.
-
- The objects are returned a sequence of mapping objects with keys:
-
- path -- The component path
-
- component -- The component
- """
-
class IBindingAware(zope.interface.Interface):
def bound(name):
@@ -91,50 +67,26 @@
"""
class ISite(IPossibleSite):
- """Marker interface to indicate that we have a site
- """
+ """Marker interface to indicate that we have a site"""
-class ILocalSiteManager(ISiteManager, IComponentManager,
+class ILocalSiteManager(zope.component.interfaces.ISiteManager,
registration.ILocatedRegistry,
registration.IRegistry):
"""Site Managers act as containers for registerable components.
- If a Site Manager is asked for an adapter or utility, it checks for those it
- contains before using a context-based lookup to find another site
- manager to delegate to. If no other service manager is found they defer
- to the ComponentArchitecture ServiceManager which contains file based
- services.
+ If a Site Manager is asked for an adapter or utility, it checks for those
+ it contains before using a context-based lookup to find another site
+ manager to delegate to. If no other site manager is found they defer to
+ the global site manager which contains file based utilities and adapters.
"""
- def findModule(name):
- """Find the module of the given name.
- If the module can be find in the folder or a parent folder
- (within the site manager), then return it, otherwise, delegate
- to the module service.
-
- This must return None when the module is not found.
-
- """
-
- def resolve(name):
- """Resolve a dotted object name.
-
- A dotted object name is a dotted module name and an object
- name within the module.
-
- TODO: We really should switch to using some other character than
- a dot for the delimiter between the module and the object
- name.
-
- """
-
class ISiteManagementFolder(registration.IRegisterableContainer,
IContainer):
"""Component and component registration containers."""
__parent__ = zope.schema.Field(
constraint = ContainerTypesConstraint(
- ISiteManager,
+ ILocalSiteManager,
registration.IRegisterableContainer,
),
)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/component/site.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/site.py 2005-01-07 00:10:21 UTC (rev 28756)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/site.py 2005-01-07 00:12:01 UTC (rev 28757)
@@ -25,7 +25,6 @@
$Id$
"""
import sys
-import zodbcode.module
import zope.event
import zope.interface
@@ -93,23 +92,19 @@
zope.interface.directlyProvidedBy(self))
-def findNextSiteManager(site):
- next = None
- while next is None:
+def _findNextSiteManager(site):
+ while True:
if IContainmentRoot.providedBy(site):
- # we're the root site, use the global sm
- next = zapi.getGlobalSiteManager()
+ # we're the root site, return None
+ return None
site = zapi.getParent(site)
if interfaces.ISite.providedBy(site):
- next = site.getSiteManager()
-
- return next
+ return site.getSiteManager()
class LocalSiteManager(BTreeContainer,
- zodbcode.module.PersistentModuleRegistry,
zope.component.site.SiteManager):
"""Local Site Manager implementation"""
zope.interface.implements(
@@ -128,19 +123,16 @@
# Make sure everything is setup correctly
BTreeContainer.__init__(self)
- zodbcode.module.PersistentModuleRegistry.__init__(self)
- # Setup located registry attributes
- self.base = zapi.getGlobalSiteManager()
- next = findNextSiteManager(site)
- if not zope.component.site.IGlobalSiteManager.providedBy(next):
- self.setNext(next)
-
# Set up adapter registries
gsm = zapi.getGlobalSiteManager()
self.adapters = adapter.LocalAdapterRegistry(gsm.adapters)
self.utilities = adapter.LocalAdapterRegistry(gsm.utilities)
+ # Setup located registry attributes
+ next = _findNextSiteManager(site)
+ self.setNext(next)
+
# Setup default site management folder
folder = SiteManagementFolder()
zope.event.notify(objectevent.ObjectCreatedEvent(folder))
@@ -158,17 +150,19 @@
def setNext(self, next, base=None):
"""See interfaces.registration.ILocatedRegistry"""
- if base is not None:
- self.base = base
if self.next is not None:
self.next.removeSub(self)
if next is not None:
next.addSub(self)
self.next = next
- self.adapaters.setNext(next.adapters)
- self.utilities.setNext(next.adapters)
+ if next is not None:
+ self.adapters.setNext(next.adapters)
+ self.utilities.setNext(next.utilities)
+ else:
+ self.adapters.setNext(None)
+ self.utilities.setNext(None)
- def __getRegistry(registration):
+ def __getRegistry(self, registration):
"""Determine the correct registry for the registration."""
if interfaces.IUtilityRegistration.providedBy(registration):
return self.utilities
@@ -181,12 +175,12 @@
def register(self, registration):
"""See zope.app.component.interfaces.registration.IRegistry"""
- registry = self.__getRegistry()
+ registry = self.__getRegistry(registration)
registry.register(registration)
def unregister(self, registration):
"""See zope.app.component.interfaces.registration.IRegistry"""
- registry = self.__getRegistry()
+ registry = self.__getRegistry(registration)
registry.unregister(registration)
def registered(self, registration):
@@ -201,73 +195,7 @@
for reg in self.utilities.registrations():
yield reg
- def queryComponent(self, type=None, filter=None, all=True):
- """See zope.app.component.interfaces.IComponentManager"""
- local = []
- path = zapi.getPath(self)
- for pkg_name in self:
- package = self[pkg_name]
- for name in package:
- component = package[name]
- if type is not None and not type.providedBy(component):
- continue
- if filter is not None and not filter(component):
- continue
- local.append({'path': "%s/%s/%s" %(path, pkg_name, name),
- 'component': component,
- })
- if all:
- next_service_manager = self.next
- if IComponentManager.providedBy(next_service_manager):
- next_service_manager.queryComponent(type, filter, all)
-
- local += list(all)
-
- return local
-
- def findModule(self, name):
- """See zodbcode.interfaces.IPersistentModuleImportRegistry"""
- # override to pass call up to next service manager
- mod = super(SiteManager, self).findModule(name)
- if mod is not None:
- return mod
-
- sm = self.next
- try:
- findModule = sm.findModule
- except AttributeError:
- # The only service manager that doesn't implement this
- # interface is the global service manager. There is no
- # direct way to ask if sm is the global service manager.
- return None
- return findModule(name)
-
-
- def __import(self, name):
- # Look for a .py file first:
- manager = self.get(name+'.py')
- if manager is not None:
- # found an item with that name, make sure it's a module(manager):
- if IModuleManager.providedBy(manager):
- return manager.getModule()
-
- # Look for the module in this folder:
- manager = self.get(name)
- if manager is not None:
- # found an item with that name, make sure it's a module(manager):
- if IModuleManager.providedBy(manager):
- return manager.getModule()
-
- raise ImportError(name)
-
-
- def resolve(self, name):
- l = name.rfind('.')
- mod = self.findModule(name[:l])
- return getattr(mod, name[l+1:])
-
-
class AdapterRegistration(registration.ComponentRegistration):
"""Adapter component registration for persistent components
@@ -290,8 +218,10 @@
self.permission = permission
def component(self):
- folder = self.__parent__.__parent__
- factory = folder.resolve(self.factoryName)
+ # Import here, so that we only have a soft dependence on
+ # zope.app.module
+ from zope.app.module import resolve
+ factory = resolve(self.factoryName, self)
return factory
component = property(component)
@@ -319,7 +249,7 @@
self.provided = provided
def getRegistry(self):
- return self.getSiteManager()
+ return zapi.getSiteManager(self)
def threadSiteSubscriber(event):
@@ -375,3 +305,12 @@
Cleans up the site thread global after the request is processed.
"""
clearSite()
+
+
+def changeSiteConfigurationAfterMove(site, event):
+ """After a site is moved, its site manager links have to be updated."""
+ next = None
+ if event.newParent is not None:
+ next = _findNextSiteManager(site)
+ site.getSiteManager().setNext(next)
+
Modified: Zope3/branches/srichter-blow-services/src/zope/app/component/site.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/site.txt 2005-01-07 00:10:21 UTC (rev 28756)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/site.txt 2005-01-07 00:12:01 UTC (rev 28757)
@@ -2,16 +2,20 @@
Sites and Local Site Managers
=============================
+This chapter is an introduction of the location-based component
+architecture. This code uses the registration framework introduced in
+`registration.txt` and local adapter registries described in
+`adapterregistry.txt`.
-Creating Sites
---------------
+Creating and Accessing Sites
+----------------------------
*Sites* are used to provide custom component setups for parts of your
application or Web site. Every folder,
>>> from zope.app.folder import folder
- >>> myfolder = folder.Folder()
+ >>> myfolder = folder.rootFolder()
has the potential to become a site
@@ -37,42 +41,307 @@
>>> interfaces.ISite.providedBy(myContent)
False
+To convert a possible site to a real site, we have to provide a site manager:
+ >>> sm = site.LocalSiteManager(myfolder)
+ >>> myfolder.setSiteManager(sm)
+ >>> interfaces.ISite.providedBy(myfolder)
+ True
+ >>> myfolder.getSiteManager() is sm
+ True
+If one tries to set a bogus site manager, a `ValueError` will be raised:
- def test_get_and_set(self):
- smc = self.makeTestObject()
- self.failIf(ISite.providedBy(smc))
- sm = ServiceManager()
- smc.setSiteManager(sm)
- self.failUnless(ISite.providedBy(smc))
- self.failUnless(smc.getSiteManager() is sm)
- verifyObject(ISite, smc)
+ >>> myfolder2 = folder.Folder()
+ >>> myfolder2.setSiteManager(object)
+ Traceback (most recent call last):
+ ...
+ ValueError: setSiteManager requires an ISiteManager
- def test_set_w_bogus_value(self):
- smc=self.makeTestObject()
- self.assertRaises(Exception, smc.setSiteManager, self)
+Also, if the possible site has been changed to a site already, a `TypeError`
+is raised, when one attempts to add a new site manager:
+ >>> myfolder.setSiteManager(site.LocalSiteManager(myfolder))
+ Traceback (most recent call last):
+ ...
+ TypeError: Already a site
+There is also an adapter you can use to get the next site manager from any
+location:
-class Test(BaseTestServiceManagerContainer, TestCase):
- def makeTestObject(self):
- from zope.app.site.servicecontainer import ServiceManagerContainer
- return ServiceManagerContainer()
+ >>> myfolder['mysubfolder'] = folder.Folder()
+ >>> import zope.component
+ >>> zope.component.interfaces.ISiteManager(myfolder['mysubfolder']) is sm
+ True
+If the location passed is a site, the site manager of that site is returned:
-SiteManagerAdapter
+ >>> zope.component.interfaces.ISiteManager(myfolder) is sm
+ True
+During traversal,
+ >>> from zope.app import publication
+ >>> request = object()
+ >>> ev = publication.interfaces.BeforeTraverseEvent(myfolder, request)
+ >>> site.threadSiteSubscriber(ev)
-The Registry
-++++++++++++
+the nearest site is also recorded in a thread-global variable:
-register
-unregister
-registrations
-registered
+ >>> from zope.app.component import hooks
+ >>> hooks.getSite() is myfolder
+ True
-setNext
-next
-subs
+After a request is completed, the site setting is cleared again:
+
+ >>> ev = publication.interfaces.EndRequestEvent(myfolder, request)
+ >>> site.clearThreadSiteSubscriber(ev)
+ >>> hooks.getSite() is None
+ True
+
+
+Using the Site Manager
+----------------------
+
+A site manager contains several *site management folders*, which are used to
+logically organize the software. When a site manager is initialized, a default
+site management folder is created:
+
+ >>> sm = myfolder.getSiteManager()
+ >>> default = sm['default']
+ >>> default.__class__
+ <class 'zope.app.component.site.SiteManagementFolder'>
+
+You can easily create a new site management folder:
+
+ >>> sm['mySMF'] = site.SiteManagementFolder()
+ >>> sm['mySMF'].__class__
+ <class 'zope.app.component.site.SiteManagementFolder'>
+
+Once you have your site management folder -- let's use the default one -- we
+can register some components. Let's start with a utility
+
+ >>> import zope.interface
+ >>> class IMyUtility(zope.interface.Interface):
+ ... pass
+
+ >>> import persistent
+ >>> from zope.app.container.contained import Contained
+ >>> class MyUtility(persistent.Persistent, Contained):
+ ... zope.interface.implements(IMyUtility,
+ ... interfaces.ILocalUtility)
+ ... def __init__(self, title):
+ ... self.title = title
+ ... def __repr__(self):
+ ... return "%s('%s')" %(self.__class__.__name__, self.title)
+
+which we first put in the site management folder:
+
+ >>> default['myutil'] = MyUtility('My custom utility')
+ >>> myutil = default['myutil']
+
+Then we have to create a registration for the utility and activate it:
+
+ >>> reg = site.UtilityRegistration('myutil', IMyUtility, myutil)
+ >>> default.registrationManager.addRegistration(reg)
+ 'UtilityRegistration'
+ >>> reg.status = interfaces.registration.ActiveStatus
+
+Note that you can only change the status *after* you have added the
+registration to the registration manager, since registration component uses
+its location to determine the correct registry to add the component to.
+
+Now we can ask the site manager for the utility:
+
+ >>> sm.queryUtility(IMyUtility, 'myutil')
+ MyUtility('My custom utility')
+
+Of course, the local site manager has also access to the global component
+registrations:
+
+ >>> gutil = MyUtility('Global Utility')
+ >>> from zope.app import zapi
+ >>> gsm = zapi.getGlobalSiteManager()
+ >>> gsm.provideUtility(IMyUtility, gutil, 'gutil')
+
+ >>> sm.queryUtility(IMyUtility, 'gutil')
+ MyUtility('Global Utility')
+
+Next let's see whether we can also successfully register an adapter as
+well. Here the adpater will provide the size of a file:
+
+ >>> class IFile(zope.interface.Interface):
+ ... pass
+
+ >>> class ISized(zope.interface.Interface):
+ ... pass
+
+ >>> class File(object):
+ ... zope.interface.implements(IFile)
+
+ >>> class FileSize(object):
+ ... zope.interface.implements(ISized)
+ ... def __init__(self, context):
+ ... self.context = context
+
+We place the adapter in a particular module, so that adapter registration will
+be able to look it up by name.
+
+ >>> import sys
+ >>> sys.modules['zope.app.component.tests'].FileSize = FileSize
+
+Now that we the adapter we need to register it:
+
+ >>> areg = site.AdapterRegistration(IFile, ISized,
+ ... 'zope.app.component.tests.FileSize')
+ >>> default.registrationManager.addRegistration(areg)
+ 'AdapterRegistration'
+ >>> areg.status = interfaces.registration.ActiveStatus
+
+Finally, we can get the adapter for a file:
+
+ >>> file = File()
+ >>> size = sm.queryAdapter(file, ISized, name='')
+ >>> size.__class__
+ <class 'FileSize'>
+ >>> size.context is file
+ True
+
+By the way, once you set a site
+
+ >>> hooks.setSite(myfolder)
+
+you can simply use the ZAPI's `getSiteManager()` method to get the nearest
+site manager:
+
+ >>> zapi.getSiteManager() is sm
+ True
+
+This also means that you can simply use ZAPI to look up your utility
+
+ >>> zapi.getUtility(IMyUtility, 'gutil')
+ MyUtility('Global Utility')
+
+or the adapter via the interface's `__call__` method:
+
+ >>> size = ISized(file)
+ >>> size.__class__
+ <class 'FileSize'>
+ >>> size.context is file
+ True
+
+
+Multiple Sites
+--------------
+
+Until now we have only dealt with one local and the global site. But things
+really become interesting, once we have multiple sites. We can override other
+local configuration.
+
+Let's now create a new folder called `folder11`, add it to `myfolder` and make
+it a site:
+
+ >>> myfolder11 = folder.Folder()
+ >>> myfolder['myfolder11'] = myfolder11
+ >>> myfolder11.setSiteManager(site.LocalSiteManager(myfolder11))
+ >>> sm11 = myfolder11.getSiteManager()
+
+If we ask the second site manager for its next, we get
+
+ >>> sm11.next is sm
+ True
+
+and the first site manager should have the folling sub manager:
+
+ >>> sm.subs == (sm11,)
+ True
+
+If we now register a second utility with the same name and interface with the
+new site manager folder,
+
+ >>> default11 = sm11['default']
+ >>> default11['myutil'] = MyUtility('Utility, uno & uno')
+ >>> myutil11 = default11['myutil']
+
+ >>> reg11 = site.UtilityRegistration('myutil', IMyUtility, myutil11)
+ >>> default11.registrationManager.addRegistration(reg11)
+ 'UtilityRegistration'
+ >>> reg11.status = interfaces.registration.ActiveStatus
+
+then it will will be available in the second site manager
+
+ >>> sm11.queryUtility(IMyUtility, 'myutil')
+ MyUtility('Utility, uno & uno')
+
+but not in the first one:
+
+ >>> sm.queryUtility(IMyUtility, 'myutil')
+ MyUtility('My custom utility')
+
+It is also interesting to look at the use cases of moving and copying a
+site. To do that we create a second root folder and make it a site, so that
+site hierarchy is as follows:
+
+ _____ global site _____
+ / \
+ myfolder1 myfolder2
+ |
+ myfolder11
+
+ >>> myfolder2 = folder.rootFolder()
+ >>> myfolder2.setSiteManager(site.LocalSiteManager(myfolder2))
+
+Before we can move or copy sites, we need to register two event subscribers
+that manage the wiring of site managers after moving or copying:
+
+ >>> from zope.app import container
+ >>> gsm.subscribe((interfaces.ISite, container.interfaces.IObjectMovedEvent),
+ ... None, site.changeSiteConfigurationAfterMove)
+
+We only have to register one event listener, since the copy action causes an
+`IObjectAddedEvent` to be created, which is just a special type of
+`IObjectMovedEvent`.
+
+First, make sure that everything is setup correctly in the first place:
+
+ >>> myfolder11.getSiteManager().next is myfolder.getSiteManager()
+ True
+ >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager()
+ True
+ >>> myfolder2.getSiteManager().subs
+ ()
+
+Let's now move `myfolder11` from `myfolder` to `myfolder2`:
+
+ >>> myfolder2['myfolder21'] = myfolder11
+ >>> del myfolder['myfolder11']
+
+Now the next site manager for `myfolder11`'s site manager should have changed:
+
+ >>> myfolder21 = myfolder11
+ >>> myfolder21.getSiteManager().next is myfolder2.getSiteManager()
+ True
+ >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager()
+ True
+ >>> myfolder.getSiteManager().subs
+ ()
+
+Finally, we make a copy of `myfolder21` and add it as `myfolder11` to
+`myfolder`:
+
+ # Make sure that our interfaces and classes are pickable.
+ >>> sys.modules['zope.app.component.tests'].IMyUtility = IMyUtility
+ >>> IMyUtility.__module__ = 'zope.app.component.tests'
+ >>> sys.modules['zope.app.component.tests'].MyUtility = MyUtility
+ >>> MyUtility.__module__ = 'zope.app.component.tests'
+
+ >>> from zope.app.location.pickling import locationCopy
+ >>> myfolder['myfolder11'] = locationCopy(myfolder2['myfolder21'])
+
+ >>> myfolder11 = myfolder['myfolder11']
+ >>> myfolder11.getSiteManager().next is myfolder.getSiteManager()
+ True
+ >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager()
+ True
+ >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager()
+ True
More information about the Zope3-Checkins
mailing list