===============
zope.site
.. image:: https://img.shields.io/pypi/v/zope.site.svg
:target: https://pypi.python.org/pypi/zope.site/
:alt: Latest release
.. image:: https://img.shields.io/pypi/pyversions/zope.site.svg
:target: https://pypi.org/project/zope.site/
:alt: Supported Python versions
.. image:: https://github.com/zopefoundation/zope.site/workflows/tests/badge.svg
:target: https://github.com/zopefoundation/zope.site/actions?query=workflow%3Atests
.. image:: https://coveralls.io/repos/github/zopefoundation/zope.site/badge.svg?branch=master
:target: https://coveralls.io/github/zopefoundation/zope.site?branch=master
.. image:: https://readthedocs.org/projects/zopesite/badge/?version=latest
:target: httpl://zopesite.readthedocs.io/en/latest/
:alt: Documentation Status
This package provides a local and persistent site manager
implementation, so that one can register local utilities and
adapters. It uses local adapter registries for its adapter and utility
registry. The module also provides some facilities to organize the
local software and ensures the correct behavior inside the ZODB.
Documentation is hosted at https://zopesite.readthedocs.io
Sites and Local Site Managers
This is an introduction of location-based component architecture.
Creating and Accessing Sites
Sites are used to provide custom component setups for parts of your
application or web site. Every folder:
from zope.site import folder
myfolder = folder.rootFolder()
has the potential to become a site:
from zope.component.interfaces import ISite, IPossibleSite
IPossibleSite.providedBy(myfolder)
True
but is not yet one:
ISite.providedBy(myfolder)
False
If you would like your custom content component to be able to become a site,
you can use the SiteManagerContainer
mix-in class:
from zope import site
class MyContentComponent(site.SiteManagerContainer):
... pass
myContent = MyContentComponent()
IPossibleSite.providedBy(myContent)
True
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)
ISite.providedBy(myfolder)
True
myfolder.getSiteManager() is sm
True
Note that an event is generated when a local site manager is created:
from zope.component.eventtesting import getEvents
from zope.site.interfaces import INewLocalSite
[event] = getEvents(INewLocalSite)
event.manager is sm
True
If one tries to set a bogus site manager, a ValueError
will be raised:
myfolder2 = folder.Folder()
myfolder2.setSiteManager(object)
Traceback (most recent call last):
...
ValueError: setSiteManager requires an IComponentLookup
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:
myfolder['mysubfolder'] = folder.Folder()
import zope.interface.interfaces
zope.interface.interfaces.IComponentLookup(myfolder['mysubfolder']) is sm
True
If the location passed is a site, the site manager of that site is returned:
zope.interface.interfaces.IComponentLookup(myfolder) is sm
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.site.site.SiteManagementFolder'>
However, you can tell not to create the default site manager folder on
LocalSiteManager creation:
nodefault = site.LocalSiteManager(myfolder, default_folder=False)
'default' in nodefault
False
Also, note that when creating LocalSiteManager, its parent is set to
site that was passed to constructor and the name is set to ++etc++site.
nodefault.parent is myfolder
True
nodefault.name == '++etc++site'
True
You can easily create a new site management folder:
sm['mySMF'] = site.SiteManagementFolder()
sm['mySMF'].class
<class 'zope.site.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 (we define it
in a __module__
that can be pickled):
import zope.interface
name = 'zope.site.tests'
class IMyUtility(zope.interface.Interface):
... pass
import persistent
from zope.container.contained import Contained
@zope.interface.implementer(IMyUtility)
... class MyUtility(persistent.Persistent, Contained):
... def init(self, title):
... self.title = title
... def repr(self):
... return "%s('%s')" %(self.class.name, self.title)
Now we can create an instance of our utility and put it in the site
management folder and register it:
myutil = MyUtility('My custom utility')
default['myutil'] = myutil
sm.registerUtility(myutil, IMyUtility, 'u1')
Now we can ask the site manager for the utility:
sm.queryUtility(IMyUtility, 'u1')
MyUtility('My custom utility')
Of course, the local site manager has also access to the global component
registrations:
gutil = MyUtility('Global Utility')
from zope.component import getGlobalSiteManager
gsm = getGlobalSiteManager()
gsm.registerUtility(gutil, IMyUtility, 'gutil')
sm.queryUtility(IMyUtility, 'gutil')
MyUtility('Global Utility')
Next let's see whether we can also successfully register an adapter as
well. Here the adapter will provide the size of a file:
class IFile(zope.interface.Interface):
... pass
class ISized(zope.interface.Interface):
... pass
@zope.interface.implementer(IFile)
... class File(object):
... pass
@zope.interface.implementer(ISized)
... class FileSize(object):
... def init(self, context):
... self.context = context
Now that we have the adapter we need to register it:
sm.registerAdapter(FileSize, [IFile])
Finally, we can get the adapter for a file:
file = File()
size = sm.queryAdapter(file, ISized, name='')
isinstance(size, FileSize)
True
size.context is file
True
By the way, once you set a site
from zope.component import hooks
hooks.setSite(myfolder)
you can simply use the zope.component's getSiteManager()
method to get
the nearest site manager:
from zope.component import getSiteManager
getSiteManager() is sm
True
This also means that you can simply use zope.component to look up your utility
from zope.component import getUtility
getUtility(IMyUtility, 'gutil')
MyUtility('Global Utility')
or the adapter via the interface's __call__
method:
size = ISized(file)
isinstance(size, FileSize)
True
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.
This behaviour uses the notion of location, therefore we need to configure the
zope.location package first:
import zope.configuration.xmlconfig
_ = zope.configuration.xmlconfig.string("""
...
...
...
...
... """)
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.bases == (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']
myutil11 = MyUtility('Utility, uno & uno')
default11['myutil'] = myutil11
sm11.registerUtility(myutil11, IMyUtility, 'u1')
then it will will be available in the second site manager
sm11.queryUtility(IMyUtility, 'u1')
MyUtility('Utility, uno & uno')
but not in the first one:
sm.queryUtility(IMyUtility, 'u1')
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 _____
/ \
myfolder 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:
import zope.lifecycleevent.interfaces
gsm.registerHandler(
... site.changeSiteConfigurationAfterMove,
... (ISite, zope.lifecycleevent.interfaces.IObjectMovedEvent),
... )
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().bases == (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().bases == (myfolder2.getSiteManager(), )
True
myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager()
True
myfolder.getSiteManager().subs
()
Make sure that our interfaces and classes are picklable:
import sys
sys.modules['zope.site.tests'].IMyUtility = IMyUtility
sys.modules['zope.site.tests'].MyUtility = MyUtility
from pickle import dumps, loads
data = dumps(myfolder2['myfolder21'])
myfolder['myfolder11'] = loads(data)
myfolder11 = myfolder['myfolder11']
myfolder11.getSiteManager().bases == (myfolder.getSiteManager(), )
True
myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager()
True
myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager()
True
Finally, let's check that everything works fine when our folder is moved
to the folder that doesn't contain any site manager. Our folder's
sitemanager's bases should be set to global site manager.
myfolder11.getSiteManager().bases == (myfolder.getSiteManager(), )
True
nosm = folder.Folder()
nosm['root'] = myfolder11
myfolder11.getSiteManager().bases == (gsm, )
True
Deleting a site unregisters its site manger from its parent site manager:
del myfolder2['myfolder21']
myfolder2.getSiteManager().subs
()
The removed site manager now has no bases:
myfolder21.getSiteManager().bases
()
=========
Changes
5.0 (2023-06-30)
-
Drop support for Python 2.7, 3.5, 3.6.
-
Add support for Python 3.11.
4.6.1 (2022-09-02)
- Fix more deprecation warnings.
4.6 (2022-08-23)
4.5.0 (2021-03-04)
-
Fix the interface definition of IRootFolder
to give IRoot
higher priority than the folder and container interfaces. This is
what is usually expected, but not what the code defined. Commonly,
in the past, this problem was hidden because the factory function
rootFolder()
re-arranged the interfaces to put IRoot
at the
front. Under zope.interface 5's C3 resolution order, however, this
rearrangement was not taking place; thus, looking up adapters for a
rootFolder()
object was likely to find adapters for
IItemContainer
instead of adapters for IRoot
as intended.
With this change, users of rootFolder()
should notice no changes
compared with zope.interface 4. Code that has classes defined to
implement IRootFolder
directly, though, may notice a different
resolution order on those objects (consistent with what
rootFolder()
generates).
See issue 17 <https://github.com/zopefoundation/zope.site/issues/17>
_.
4.4.0 (2020-09-10)
- On removal of a site, clear the bases of its site manager. This fixes a reference leak
from a parent site manager. See
issue 1 <https://github.com/zopefoundation/zope.site/issues/1>
_.
4.3.0 (2020-04-01)
-
Add support for Python 3.8.
-
Drop support for Python 3.4.
-
Drop support for the deprecated python setup.py test
command.
-
Fix tests with zope.interface 5.0. See issue 12 <https://github.com/zopefoundation/zope.site/issues/12>
_.
4.2.2 (2018-10-19)
- Fix more
DeprecationWarnings
. See issue 10 <https://github.com/zopefoundation/zope.site/issues/10>
_.
4.2.1 (2018-10-11)
- Use current import location for
UtilityRegistration
and IUtilityRegistration
classes to avoid DeprecationWarning
.
4.2.0 (2018-10-09)
- Add support for Python 3.7.
4.1.0 (2017-08-08)
-
Add support for Python 3.5 and 3.6.
-
Drop support for Python 2.6 and 3.3.
-
Deprecate zope.site.hooks.*
, zope.site.site.setSite
,
zope.site.next.getNextUtility
and zope.site.next.queryNextUtility
with zope.deprecation
. These will be removed in version 5.0.
They all have replacements in zope.component
.
-
Added implementation for p_repr in LocalSiteManager. For further
information see issue 8 <https://github.com/zopefoundation/zope.site/issues/8>
.
-
Reach 100% test coverage and ensure we remain there.
4.0.0 (2014-12-24)
4.0.0a1 (2013-02-20)
-
Added support for Python 3.3.
-
Replaced deprecated zope.interface.implements
usage with equivalent
zope.interface.implementer
decorator.
-
Dropped support for Python 2.4 and 2.5.
-
Include zcml dependencies in configure.zcml, added tests for zcml.
3.9.2 (2010-09-25)
- Added not declared, but needed test dependency on
zope.testing
.
3.9.1 (2010-04-30)
3.9.0 (2009-12-29)
- Avoid a test dependency on zope.copypastemove by testing the correct
persistent behavior of a site manager using the normal pickle module.
3.8.0 (2009-12-15)
- Removed functional testing setup and dependency on zope.app.testing.
3.7.1 (2009-11-18)
-
Moved the zope.site.hooks functionality to zope.component.hooks as it isn't
actually dealing with zope.site's concept of a site.
-
Import ISite and IPossibleSite from zope.component after they were moved
there from zope.location.
3.7.0 (2009-09-29)
-
Cleaned up the undeclared dependency on zope.app.publication by moving the
two relevant subscriber registrations and their tests to that package.
-
Dropped the dependency on zope.traversing which was only used to access
zope.location functionality. Configure zope.location for some tests.
-
Demoted zope.configuration to a testing dependency.
3.6.4 (2009-09-01)
-
Set parent and name in the LocalSiteManager's constructor
after calling constructor of its superclasses, so name doesn't
get overwritten with empty string by the Components constructor.
-
Don't set parent and name attributes of site manager in
SiteManagerContainer's setSiteManager
method, as they're
already set for LocalSiteManager. Other site manager implementations
are not required to have those attributes at all, so we're not
adding them anymore.
3.6.3 (2009-07-27)
- Propagate an ObjectRemovedEvent to the SiteManager upon removal of a
SiteManagerContainer.
3.6.2 (2009-07-24)
-
Fixed tests to pass with latest packages.
-
Removed failing test of persistent interfaces, since it did not test
anything in this package and used the deprecated zodbcode
module.
-
Fix NameError when calling zope.site.testing.siteSetUp(site=True)
.
-
The getNextUtility
and queryNextUtility
functions was moved to
zope.component
. While backward-compatibility imports are provided, it's
strongly recommended to update your imports.
3.6.1 (2009-02-28)
-
Import symbols moved from zope.traversing to zope.location from the new
location.
-
Don't fail when changing component registry bases while moving ISite
object to non-ISite object.
-
Allow specify whether to create 'default' SiteManagementFolder on
initializing LocalSiteManager. Use the default_folder
argument.
-
Add a containment constraint to the SiteManagementFolder that makes
it only available to be contained in ILocalSiteManagers and other
ISiteManagementFolders.
-
Change package's mailing list address to zope-dev at zope.org, as
zope3-dev at zope.org is now retired.
-
Remove old unused code. Update package description.
3.6.0 (2009-01-31)
- Use zope.container instead of zope.app.container.
3.5.1 (2009-01-27)
- Extracted from zope.app.component (trunk, 3.5.1 under development)
as part of an effort to clean up dependencies between Zope packages.