[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