[Checkins] SVN: z3c.traverser/ Pluggable Traversers

Stephan Richter srichter at cosmos.phy.tufts.edu
Mon Aug 14 12:05:11 EDT 2006


Log message for revision 69480:
  Pluggable Traversers
  
  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.
  
  

Changed:
  A   z3c.traverser/
  A   z3c.traverser/branches/
  A   z3c.traverser/tags/
  A   z3c.traverser/trunk/
  A   z3c.traverser/trunk/src/
  A   z3c.traverser/trunk/src/z3c/
  A   z3c.traverser/trunk/src/z3c/__init__.py
  A   z3c.traverser/trunk/src/z3c/traverser/
  A   z3c.traverser/trunk/src/z3c/traverser/DEPENDENCIES.cfg
  A   z3c.traverser/trunk/src/z3c/traverser/README.txt
  A   z3c.traverser/trunk/src/z3c/traverser/__init__.py
  A   z3c.traverser/trunk/src/z3c/traverser/browser.py
  A   z3c.traverser/trunk/src/z3c/traverser/interfaces.py
  A   z3c.traverser/trunk/src/z3c/traverser/tests.py
  A   z3c.traverser/trunk/src/z3c/traverser/traverser.py

-=-
Added: z3c.traverser/trunk/src/z3c/__init__.py
===================================================================
--- z3c.traverser/trunk/src/z3c/__init__.py	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/__init__.py	2006-08-14 16:05:09 UTC (rev 69480)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: z3c.traverser/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.traverser/trunk/src/z3c/traverser/DEPENDENCIES.cfg
===================================================================
--- z3c.traverser/trunk/src/z3c/traverser/DEPENDENCIES.cfg	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/traverser/DEPENDENCIES.cfg	2006-08-14 16:05:09 UTC (rev 69480)
@@ -0,0 +1 @@
+zope.app

Added: z3c.traverser/trunk/src/z3c/traverser/README.txt
===================================================================
--- z3c.traverser/trunk/src/z3c/traverser/README.txt	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/traverser/README.txt	2006-08-14 16:05:09 UTC (rev 69480)
@@ -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'
+


Property changes on: z3c.traverser/trunk/src/z3c/traverser/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.traverser/trunk/src/z3c/traverser/__init__.py
===================================================================
--- z3c.traverser/trunk/src/z3c/traverser/__init__.py	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/traverser/__init__.py	2006-08-14 16:05:09 UTC (rev 69480)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: z3c.traverser/trunk/src/z3c/traverser/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.traverser/trunk/src/z3c/traverser/browser.py
===================================================================
--- z3c.traverser/trunk/src/z3c/traverser/browser.py	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/traverser/browser.py	2006-08-14 16:05:09 UTC (rev 69480)
@@ -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,)


Property changes on: z3c.traverser/trunk/src/z3c/traverser/browser.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.traverser/trunk/src/z3c/traverser/interfaces.py
===================================================================
--- z3c.traverser/trunk/src/z3c/traverser/interfaces.py	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/traverser/interfaces.py	2006-08-14 16:05:09 UTC (rev 69480)
@@ -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."""


Property changes on: z3c.traverser/trunk/src/z3c/traverser/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.traverser/trunk/src/z3c/traverser/tests.py
===================================================================
--- z3c.traverser/trunk/src/z3c/traverser/tests.py	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/traverser/tests.py	2006-08-14 16:05:09 UTC (rev 69480)
@@ -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')


Property changes on: z3c.traverser/trunk/src/z3c/traverser/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.traverser/trunk/src/z3c/traverser/traverser.py
===================================================================
--- z3c.traverser/trunk/src/z3c/traverser/traverser.py	2006-08-14 15:56:27 UTC (rev 69479)
+++ z3c.traverser/trunk/src/z3c/traverser/traverser.py	2006-08-14 16:05:09 UTC (rev 69480)
@@ -0,0 +1,153 @@
+##############################################################################
+#
+# 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 Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from zope.interface import implements
+from zope.component import subscribers, queryAdapter, queryMultiAdapter
+from zope.publisher.interfaces import NotFound
+
+from z3c.traverser import interfaces
+
+_marker = object()
+
+class PluggableTraverser(object):
+    """Generic Pluggable Traverser."""
+
+    implements(interfaces.IPluggableTraverser)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def publishTraverse(self, request, name):
+        # 1. Look at all the traverser plugins, whether they have an answer.
+        for traverser in subscribers((self.context, request),
+                                     interfaces.ITraverserPlugin):
+            try:
+                return traverser.publishTraverse(request, name)
+            except NotFound:
+                pass
+
+        # 2. The traversers did not have an answer, so let's see whether it is
+        #    a view.
+        view = queryMultiAdapter((self.context, request), name=name)
+        if view is not None:
+            return view
+
+        raise NotFound(self.context, name, request)
+
+
+class NameTraverserPlugin(object):
+    """Abstract class that traverses an object by name."""
+    implements(interfaces.ITraverserPlugin)
+
+    traversalName = None
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def publishTraverse(self, request, name):
+        if name == self.traversalName:
+            return self._traverse(request, name)
+        raise NotFound(self.context, name, request)
+
+    def _traverse(self, request, name):
+        raise NotImplemented, 'Method must be implemented by subclasses.'
+
+
+class NullTraverserPluginTemplate(NameTraverserPlugin):
+    """Traverse to an adapter by name."""
+
+    def _traverse(self, request, name):
+        return self.context
+
+
+def NullTraverserPlugin(traversalName):
+    return type('NullTraverserPlugin', (NullTraverserPluginTemplate,),
+                {'traversalName': traversalName})
+
+
+class SingleAttributeTraverserPluginTemplate(NameTraverserPlugin):
+    """Allow only a single attribute to be traversed."""
+
+    def _traverse(self, request, name):
+        return getattr(self.context, name)
+
+
+def SingleAttributeTraverserPlugin(name):
+    return type('SingleAttributeTraverserPlugin',
+                (SingleAttributeTraverserPluginTemplate,),
+                {'traversalName': name})
+
+
+class AdapterTraverserPluginTemplate(NameTraverserPlugin):
+    """Traverse to an adapter by name."""
+    interface = None
+    adapterName = ''
+
+    def _traverse(self, request, name):
+        adapter = queryAdapter(self.context, self.interface,
+                               name=self.adapterName)
+        if adapter is None:
+            raise NotFound(self.context, name, request)
+
+        return adapter
+
+
+def AdapterTraverserPlugin(traversalName, interface, adapterName=''):
+    return type('AdapterTraverserPlugin',
+                (AdapterTraverserPluginTemplate,),
+                {'traversalName': traversalName,
+                 'adapterName': adapterName,
+                 'interface': interface})
+
+
+class ContainerTraverserPlugin(object):
+    """A traverser that knows how to look up objects by name in a container."""
+
+    implements(interfaces.ITraverserPlugin)
+
+    def __init__(self, container, request):
+        self.context = container
+        self.request = request
+
+    def publishTraverse(self, request, name):
+        """See zope.publisher.interfaces.IPublishTraverse"""
+        subob = self.context.get(name, None)
+        if subob is None:
+            raise NotFound(self.context, name, request)
+
+        return subob
+
+
+class AttributeTraverserPlugin(object):
+    """A simple traverser plugin that traverses an attribute by name"""
+
+    implements(interfaces.ITraverserPlugin)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def publishTraverse(self, request, name):
+        try:
+            obj = getattr(self.context, name)
+        except AttributeError:
+            raise NotFound(self.context, name, request)
+        else:
+            return obj


Property changes on: z3c.traverser/trunk/src/z3c/traverser/traverser.py
___________________________________________________________________
Name: svn:keywords
   + Id



More information about the Checkins mailing list