-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This pacakge was originally developed for SchoolTool to satisfy the gods of SchoolTool (i.e. POV), which hate the '++namespace++' notation. :-) From the README: Traversers are Zope's mechanism to convert URI paths to an object of the application. They provide an extremly flexible mechanism to make decisions based on the policies of the application. Unfortunately the default traverser implementation is not flexible enough to deal with arbitrary extensions (via adapters) of objects that also wish to participate in the traversal decision process. The pluggable traverser allows developers, especially third-party developers, to add new traversers to an object without altering the original traversal implementation.
- Loading branch information
0 parents
commit 4ea2941
Showing
8 changed files
with
504 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Make a package. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
zope.app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
==================== | ||
Pluggable Traversers | ||
==================== | ||
|
||
Traversers are Zope's mechanism to convert URI paths to an object of the | ||
application. They provide an extremly flexible mechanism to make decisions | ||
based on the policies of the application. Unfortunately the default traverser | ||
implementation is not flexible enough to deal with arbitrary extensions (via | ||
adapters) of objects that also wish to participate in the traversal decision | ||
process. | ||
|
||
The pluggable traverser allows developers, especially third-party developers, | ||
to add new traversers to an object without altering the original traversal | ||
implementation. | ||
|
||
>>> from z3c.traverser.traverser import PluggableTraverser | ||
|
||
Let's say that we have an object | ||
|
||
>>> from zope.interface import Interface, implements | ||
>>> class IContent(Interface): | ||
... pass | ||
|
||
>>> class Content(object): | ||
... implements(IContent) | ||
... var = True | ||
|
||
>>> content = Content() | ||
|
||
that we wish to traverse to. Since traversers are presentation-type specific, | ||
they are implemented as views and must thus be initiated using a request: | ||
|
||
>>> from zope.publisher.base import TestRequest | ||
>>> request = TestRequest('') | ||
>>> traverser = PluggableTraverser(content, request) | ||
|
||
We can now try to lookup the variable: | ||
|
||
>>> traverser.publishTraverse(request, 'var') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <Content object at ...>, name: 'var' | ||
|
||
But it failed. Why? Because we have not registered a plugin traverser yet that | ||
knows how to lookup attributes. This package provides such a traverser | ||
already, so we just have to register it: | ||
|
||
>>> from zope.component import provideSubscriptionAdapter | ||
>>> from zope.publisher.interfaces import IPublisherRequest | ||
>>> from z3c.traverser.traverser import AttributeTraverserPlugin | ||
|
||
>>> provideSubscriptionAdapter(AttributeTraverserPlugin, | ||
... (IContent, IPublisherRequest)) | ||
|
||
If we now try to lookup the attribute, we the value: | ||
|
||
>>> traverser.publishTraverse(request, 'var') | ||
True | ||
|
||
However, an incorrect variable name will still return a ``NotFound`` error: | ||
|
||
>>> traverser.publishTraverse(request, 'bad') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <Content object at ...>, name: 'bad' | ||
|
||
Every traverser should also make sure that the passed in name is not a | ||
view. (This allows us to not specify the ``@@`` in front of a view.) So let's | ||
register one: | ||
|
||
>>> class View(object): | ||
... def __init__(self, context, request): | ||
... pass | ||
|
||
>>> from zope.component import provideAdapter | ||
>>> from zope.publisher.interfaces import IPublisherRequest | ||
>>> provideAdapter(View, | ||
... adapts=(IContent, IPublisherRequest), | ||
... provides=Interface, | ||
... name='view.html') | ||
|
||
Now we can lookup the view as well: | ||
|
||
>>> traverser.publishTraverse(request, 'view.html') | ||
<View object at ...> | ||
|
||
|
||
Advanced Uses | ||
------------- | ||
|
||
A more interesting case to consider is a traverser for a container. If you | ||
really dislike the Zope 3 traversal namespace notation ``++namespace++`` and | ||
you can control the names in the container, then the pluggable traverser will | ||
also provide a viable solution. Let's say we have a container | ||
|
||
>>> from zope.app.container.interfaces import IContainer | ||
>>> class IMyContainer(IContainer): | ||
... pass | ||
|
||
>>> from zope.app.container.btree import BTreeContainer | ||
>>> class MyContainer(BTreeContainer): | ||
... implements(IMyContainer) | ||
... foo = True | ||
... bar = False | ||
|
||
>>> myContainer = MyContainer() | ||
>>> myContainer['blah'] = 123 | ||
|
||
and we would like to be able to traverse | ||
|
||
* all items of the container, as well as | ||
|
||
>>> from z3c.traverser.traverser import ContainerTraverserPlugin | ||
>>> from z3c.traverser.interfaces import ITraverserPlugin | ||
|
||
>>> provideSubscriptionAdapter(ContainerTraverserPlugin, | ||
... (IMyContainer, IPublisherRequest), | ||
... ITraverserPlugin) | ||
|
||
* the ``foo`` attribute. Luckily we also have a predeveloped traverser for | ||
this: | ||
|
||
>>> from z3c.traverser.traverser import \ | ||
... SingleAttributeTraverserPlugin | ||
>>> provideSubscriptionAdapter(SingleAttributeTraverserPlugin('foo'), | ||
... (IMyContainer, IPublisherRequest)) | ||
|
||
We can now use the pluggable traverser | ||
|
||
>>> traverser = PluggableTraverser(myContainer, request) | ||
|
||
to look up items | ||
|
||
>>> traverser.publishTraverse(request, 'blah') | ||
123 | ||
|
||
and the ``foo`` attribute: | ||
|
||
>>> traverser.publishTraverse(request, 'foo') | ||
True | ||
|
||
However, we cannot lookup the ``bar`` attribute or any other non-existent | ||
item: | ||
|
||
>>> traverser.publishTraverse(request, 'bar') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <MyContainer object at ...>, name: 'bar' | ||
|
||
>>> traverser.publishTraverse(request, 'bad') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <MyContainer object at ...>, name: 'bad' | ||
|
||
You can also add traversers that return an adapted object. For example, let's | ||
take the following adapter: | ||
|
||
>>> class ISomeAdapter(Interface): | ||
... pass | ||
|
||
>>> from zope.component import adapts | ||
>>> class SomeAdapter(object): | ||
... implements(ISomeAdapter) | ||
... adapts(IMyContainer) | ||
... | ||
... def __init__(self, context): | ||
... pass | ||
|
||
>>> from zope.component import adapts, provideAdapter | ||
>>> provideAdapter(SomeAdapter) | ||
|
||
Now we register this adapter under the traversal name ``some``: | ||
|
||
>>> from z3c.traverser.traverser import AdapterTraverserPlugin | ||
>>> provideSubscriptionAdapter( | ||
... AdapterTraverserPlugin('some', ISomeAdapter), | ||
... (IMyContainer, IPublisherRequest)) | ||
|
||
So here is the result: | ||
|
||
>>> traverser.publishTraverse(request, 'some') | ||
<SomeAdapter object at ...> | ||
|
||
|
||
Traverser Plugins | ||
----------------- | ||
|
||
The `traverser` package comes with several default traverser plugins; three of | ||
them were already introduced above: `SingleAttributeTraverserPlugin`, | ||
`AdapterTraverserPlugin`, and `ContainerTraverserPlugin`. Another plugin is | ||
the the `NullTraverserPlugin`, which always just returns the object itself: | ||
|
||
>>> from z3c.traverser.traverser import NullTraverserPlugin | ||
>>> SomethingPlugin = NullTraverserPlugin('something') | ||
|
||
>>> plugin = SomethingPlugin(content, request) | ||
>>> plugin.publishTraverse(request, 'something') | ||
<Content object at ...> | ||
|
||
>>> plugin.publishTraverse(request, 'something else') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <Content object at ...>, name: 'something else' | ||
|
||
All of the above traversers with exception of the `ContainerTraverserPlugin` | ||
are realizations of the abstract `NameTraverserPlugin` class. Name traversers | ||
are traversers that can resolve one particular name. By using the abstract | ||
`NameTraverserPlugin` class, all of the traverser boilerplate can be | ||
avoided. Here is a simple example that always returns a specific value for a | ||
traversed name: | ||
|
||
>>> from z3c.traverser.traverser import NameTraverserPlugin | ||
>>> class TrueTraverserPlugin(NameTraverserPlugin): | ||
... traversalName = 'true' | ||
... def _traverse(self, request, name): | ||
... return True | ||
|
||
As you can see realized name traversers must implement the ``_traverse()`` | ||
method, which is only responsible for returning the result. Of course it can | ||
also raise the `NotFound` error if something goes wrong during the | ||
computation. LEt's check it out: | ||
|
||
>>> plugin = TrueTraverserPlugin(content, request) | ||
>>> plugin.publishTraverse(request, 'true') | ||
True | ||
|
||
>>> plugin.publishTraverse(request, 'false') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <Content object at ...>, name: 'false' | ||
|
||
A final traverser that is offered by the package is the | ||
`AttributeTraverserPlugin``, which simply allows one to traverse all | ||
accessible attributes of an object: | ||
|
||
>>> from z3c.traverser.traverser import AttributeTraverserPlugin | ||
|
||
>>> plugin = AttributeTraverserPlugin(myContainer, request) | ||
>>> plugin.publishTraverse(request, 'foo') | ||
True | ||
>>> plugin.publishTraverse(request, 'bar') | ||
False | ||
>>> plugin.publishTraverse(request, 'blah') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <MyContainer object at ...>, name: 'blah' | ||
>>> plugin.publishTraverse(request, 'some') | ||
Traceback (most recent call last): | ||
... | ||
NotFound: Object: <MyContainer object at ...>, name: 'some' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Make a package. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
############################################################################## | ||
# | ||
# Copyright (c) 2006 Zope Foundation and Contributors. | ||
# All Rights Reserved. | ||
# | ||
# This software is subject to the provisions of the Zope Public License, | ||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
# FOR A PARTICULAR PURPOSE. | ||
# | ||
############################################################################## | ||
"""Pluggable Browser Traverser | ||
$Id$ | ||
""" | ||
__docformat__ = "reStructuredText" | ||
from zope.app import zapi | ||
from z3c.traverser.traverser import PluggableTraverser | ||
|
||
|
||
class PluggableBrowserTraverser(PluggableTraverser): | ||
|
||
def browserDefault(self, request): | ||
"""See zope.publisher.browser.interfaces.IBrowserPublisher""" | ||
view_name = zapi.getDefaultViewName(self.context, request) | ||
view_uri = "@@%s" %view_name | ||
return self.context, (view_uri,) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
############################################################################## | ||
# | ||
# Copyright (c) 2006 Zope Foundation and Contributors. | ||
# All Rights Reserved. | ||
# | ||
# This software is subject to the provisions of the Zope Public License, | ||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
# FOR A PARTICULAR PURPOSE. | ||
# | ||
############################################################################## | ||
"""Pluggable Traverser Interfaces | ||
This implementation is independent of the presentation type. Sub-interfaces | ||
must be written for every specific presentation type. | ||
$Id$ | ||
""" | ||
__docformat__ = "reStructuredText" | ||
from zope.publisher.interfaces import IPublishTraverse | ||
|
||
|
||
class IPluggableTraverser(IPublishTraverse): | ||
"""A pluggable traverser. | ||
This traverser traverses a name by utilizing helper traversers that are | ||
registered as ``ITraverserPlugin`` subscribers. | ||
""" | ||
|
||
|
||
class ITraverserPlugin(IPublishTraverse): | ||
"""A plugin for the pluggable traverser.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
############################################################################## | ||
# | ||
# Copyright (c) 2006 Zope Foundation and Contributors. | ||
# All Rights Reserved. | ||
# | ||
# This software is subject to the provisions of the Zope Public License, | ||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
# FOR A PARTICULAR PURPOSE. | ||
# | ||
############################################################################## | ||
"""Pluggable Traverser Tests | ||
$Id$ | ||
""" | ||
__docformat__ = "reStructuredText" | ||
import unittest | ||
|
||
from zope.testing import doctest | ||
from zope.component import testing | ||
|
||
|
||
def test_suite(): | ||
return unittest.TestSuite(( | ||
doctest.DocFileSuite( | ||
'README.txt', | ||
setUp=testing.setUp, tearDown=testing.tearDown, | ||
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS), | ||
)) | ||
|
||
if __name__ == '__main__': | ||
unittest.main(default='test_suite') |
Oops, something went wrong.