Skip to content

Commit

Permalink
Pluggable Traversers
Browse files Browse the repository at this point in the history
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
strichter committed Aug 14, 2006
0 parents commit 4ea2941
Show file tree
Hide file tree
Showing 8 changed files with 504 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/z3c/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Make a package.
1 change: 1 addition & 0 deletions src/z3c/traverser/DEPENDENCIES.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
zope.app
251 changes: 251 additions & 0 deletions src/z3c/traverser/README.txt
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'

1 change: 1 addition & 0 deletions src/z3c/traverser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Make a package.
29 changes: 29 additions & 0 deletions src/z3c/traverser/browser.py
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,)
34 changes: 34 additions & 0 deletions src/z3c/traverser/interfaces.py
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."""
34 changes: 34 additions & 0 deletions src/z3c/traverser/tests.py
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')

0 comments on commit 4ea2941

Please sign in to comment.