Package: z3c.traverser
This package provides the pluggable traverser mechanism allowing developers
to add new traversers to an object without altering the original traversal
implementation.In addition to the pluggable traversers, this package contains two more
subpackages:viewlet - provides a way to traverse to viewlets using namespacesstackinfo - provides a way to consume parts of url and store them
as attributes of the “consumer” object. Useful for urls like:
/blog/2009/02/02/hello-worldContentsCHANGES2.0 (2023-02-09)1.0.0 (2015-11-09)1.0.0a2 (2013-03-03)1.0.0a1 (2013-03-03)0.3.0 (2010-11-01)0.2.5 (2009-03-13)0.2.4 (2009-02-02)0.2.3 (2008-07-14)0.2.2 (2008-03-06)0.2.1 (2007-11-92)0.2.0 (2007-10-31)0.2.0b2 (2007-10-26)0.2.0b1 (2007-09-21)0.1.3 (2007-06-03)0.1.1 (2007-03-22)Pluggable TraversersAdvanced UsesTraverser PluginsBrowser traverserAdditional NamespacesTraversing ViewletsViewlet TraversingCaveatsExtracting Information from the Traversal StackVirtual HostURL HandlingExtracting Information from the Traversal StackCHANGES2.0 (2023-02-09)Drop support for Python 2.7, 3.3, 3.4.Add support for Python 3.7, 3.8, 3.9, 3.10, 3.11.Drop support to run the tests usingpython setup.py test.1.0.0 (2015-11-09)Standardize namespace __init__.Claim support for Python 3.4.1.0.0a2 (2013-03-03)Added Trove classifiers to specify supported Python versions.1.0.0a1 (2013-03-03)Added support for Python 3.3.Replaced deprecatedzope.interface.implementsusage with equivalentzope.interface.implementerdecorator.Dropped support for Python 2.4 and 2.5.Switched fromzope.testbrowsertoWebTestfor browser testing, since
testbrowser is not yet ported.Modernized API to use latest packages and component paths.Reduced test dependencies to the smallest set possible.0.3.0 (2010-11-01)Updated test set up to run with ZTK 1.0.Using Python’sdoctestmodule instead of depreactedzope.testing.doctest[unit].0.2.5 (2009-03-13)Adapt to the move of IDefaultViewName from zope.component to zope.publisher.0.2.4 (2009-02-02)MakePluggableBrowserTraverserimplementIBrowserPublisherinterface.Fix tests and deprecation warnings.Improve test coverage.Get rid of zope.app.zapi dependency by replacing its uses with direct
calls.Change package’s mailing list address to zope-dev at zope.org,
because zope3-dev at zope.org is now retired.Change “cheeseshop” to “pypi” in the package’s url.0.2.3 (2008-07-14)Bugfix: In z3c.traverser.stackinfo the traversal stack got messed up
when using the VirtualHost namespace with more than one thread.0.2.2 (2008-03-06)Restructuring: Separated pluggable traverser functionality into two classes
for better code reuse.0.2.1 (2007-11-92)Bugfix: if viewlet and managers get nested a viewlet was not found if
the depth reaches 3 because the context was set to the page and not
to the context object.Bugfix: replaced call to_getContextNamebecause it has been removed
fromabsoluteURL.0.2.0 (2007-10-31)Update package meta-data.ResolveZopeSecurityPolicydeprecation warning.0.2.0b2 (2007-10-26)Use onlyabsolute_urladapters in unconsumed URL caclulations, to
make it work for traversable viewlets or other special cases too.0.2.0b1 (2007-09-21)added a generic stack consumer handler which can be registered for
BeforeTraverse events.0.1.3 (2007-06-03)Added principal namespace, seenamespace.rstFireBeforeUpdateEventin viewlet view0.1.1 (2007-03-22)First egg releasePluggable TraversersTraversers 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 PluggableTraverserLet’s say that we have an object>>> from zope.interface import Interface, implementer
>>> class IContent(Interface):
...     pass>>> @implementer(IContent)
... class Content(object):
...     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')
TrueHowever, an incorrect variable name will still return aNotFounderror:>>> 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 UsesA 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.container.interfaces import IContainer
>>> class IMyContainer(IContainer):
...     pass>>> from zope.container.btree import BTreeContainer
>>> @implementer(IMyContainer)
... class MyContainer(BTreeContainer):
...     foo = True
...     bar = False>>> myContainer = MyContainer()
>>> myContainer['blah'] = 123and we would like to be able to traverseall items of the container, as well as>>> from z3c.traverser.traverser import ContainerTraverserPlugin
>>> from z3c.traverser.interfaces import ITraverserPlugin>>> provideSubscriptionAdapter(ContainerTraverserPlugin,
...                            (IMyContainer, IPublisherRequest),
...                            ITraverserPlugin)thefooattribute. 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')
123and thefooattribute:>>> traverser.publishTraverse(request, 'foo')
TrueHowever, we cannot lookup thebarattribute 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
>>> @implementer(ISomeAdapter)
... class SomeAdapter(object):
...     adapts(IMyContainer)
...
...     def __init__(self, context):
...         pass>>> from zope.component import adapts, provideAdapter
>>> provideAdapter(SomeAdapter)Now we register this adapter under the traversal namesome:>>> from z3c.traverser.traverser import AdapterTraverserPlugin
>>> provideSubscriptionAdapter(
...     AdapterTraverserPlugin('some', ISomeAdapter),
...     (IMyContainer, IPublisherRequest))So here is the result:>>> traverser.publishTraverse(request, 'some')
<SomeAdapter object at ...>If the object is not adaptable, we’ll get NotFound. Let’s register a
plugin that tries to query a named adapter for ISomeAdapter. The third
argument for AdapterTraverserPlugin is used to specify the adapter name.>>> provideSubscriptionAdapter(
...     AdapterTraverserPlugin('badadapter', ISomeAdapter, 'other'),
...     (IMyContainer, IPublisherRequest))>>> traverser.publishTraverse(request, 'badadapter')
Traceback (most recent call last):
...
NotFound: Object: <MyContainer object at ...>, name: 'badadapter'Traverser PluginsThetraverserpackage comes with several default traverser plugins; three of
them were already introduced above:SingleAttributeTraverserPlugin,AdapterTraverserPlugin, andContainerTraverserPlugin. Another plugin is
the theNullTraverserPlugin, 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 theContainerTraverserPluginare implementation of the abstractNameTraverserPluginclass. Name traversers
are traversers that can resolve one particular name. By using the abstractNameTraverserPluginclass, 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 TrueAs 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 theNotFounderror 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 theAttributeTraverserPlugin`, 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'Browser traverserThere’s also a special subclass of the PluggableTraverser that
implements theIBrowserPublisherinterface, thus providing thebrowserDefaultmethod that returns a default object and a view
name to traverse and use if there’s no more steps to traverse.Let’s provide a view name registered as an IDefaultView adapter. This
is usually done by zope.publisher’s browser:defaultView directive.>>> from zope.publisher.interfaces import IDefaultViewName
>>> provideAdapter('view.html', (IContent, Interface), IDefaultViewName)>>> from z3c.traverser.browser import PluggableBrowserTraverser
>>> traverser = PluggableBrowserTraverser(content, request)
>>> traverser.browserDefault(request)
(<Content object at 0x...>, ('@@view.html',))Additional NamespacesPrincipalTheprincipalnamespace allows to differentiate between usernames
in the url. This is usefull for caching on a per principal basis. The
namespace itself doesn’t change anything. It just checks if the
principal is the one that is logged in.>>> from z3c.traverser import namespace
>>> from zope.publisher.browser import TestRequest
>>> class Request(TestRequest):
...     principal = None
...
...     def shiftNameToApplication(self):
...         pass>>> class Principal(object):
...     def __init__(self, id):
...         self.id = id>>> pid = 'something'
>>> r = Request()
>>> r.principal = Principal('anonymous')If we have the wrong principal we get an Unauthorized exception.>>> ns = namespace.principal(object(), r)
>>> ns.traverse('another', None) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
Unauthorized: ++principal++anotherOtherwise not>>> ns.traverse('anonymous', None)
<object object at ...>Traversing ViewletsThis package allows to traverse viewlets and viewletmanagers. It also
provides absolute url views for those objects which are described in
this file, for traversers see BROWSER.rst.>>> from z3c.traverser.viewlet import browserLet us define some test classes.>>> import zope.component
>>> from zope.viewlet import manager
>>> from zope.viewlet import interfaces
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> import zope.interface
>>> class ILeftColumn(interfaces.IViewletManager):
...     """Viewlet manager located in the left column."""
>>> LeftColumn = manager.ViewletManager('left', ILeftColumn)
>>> zope.component.provideAdapter(
...     LeftColumn,
...     (zope.interface.Interface,
...     IDefaultBrowserLayer, zope.interface.Interface),
...     interfaces.IViewletManager, name='left')You can then create a viewlet manager using this interface now:>>> from zope.viewlet import viewlet
>>> from zope.container.contained import Contained>>> class Content(Contained):
...     pass
>>> root['content'] = Content()
>>> content = root['content']
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> from zope.publisher.interfaces.browser import IBrowserView
>>> from zope.publisher.browser import BrowserView
>>> class View(BrowserView):
...     passWe have to set the name, this is normally done in zcml.>>> view = View(content, request)
>>> view.__name__ = 'test.html'
>>> leftColumn = LeftColumn(content, request, view)Let us create a simple viewlet. Note that we need a __name__ attribute
in order to make the viewlet traversable. Normally you don’t have to
take care of this, because the zcml directive sets the name upon
registration.>>> class MyViewlet(viewlet.ViewletBase):
...     __name__ = 'myViewlet'
...     def render(self):
...         return u'<div>My Viewlet</div>'
>>> from zope.security.checker import NamesChecker, defineChecker
>>> viewletChecker = NamesChecker(('update', 'render'))
>>> defineChecker(MyViewlet, viewletChecker)>>> zope.component.provideAdapter(
...     MyViewlet,
...     (zope.interface.Interface, IDefaultBrowserLayer,
...     IBrowserView, ILeftColumn),
...     interfaces.IViewlet, name='myViewlet')We should now be able to get the absolute url of the viewlet and the
manager. We have to register the adapter for the test.>>> from zope.traversing.browser.interfaces import IAbsoluteURL
>>> from zope.traversing.browser import absoluteurl>>> zope.component.provideAdapter(
...     browser.ViewletAbsoluteURL,
...     (interfaces.IViewlet, IDefaultBrowserLayer),
...     IAbsoluteURL)
>>> zope.component.provideAdapter(
...     browser.ViewletManagerAbsoluteURL,
...     (interfaces.IViewletManager, IDefaultBrowserLayer),
...     IAbsoluteURL, name="absolute_url")
>>> zope.component.provideAdapter(
...     browser.ViewletManagerAbsoluteURL,
...     (interfaces.IViewletManager, IDefaultBrowserLayer),
...     IAbsoluteURL)
>>> myViewlet = MyViewlet(content, request, view, leftColumn)
>>> absoluteurl.absoluteURL(leftColumn, request)
'http://127.0.0.1/content/test.html/++manager++left'
>>> absoluteurl.absoluteURL(myViewlet, request)
'.../content/test.html/++manager++left/++viewlet++myViewlet'Viewlet TraversingTraversing to viewlets is done via namespaces.>>> from webtest.app import TestApp
>>> browser = TestApp(wsgi_app)
>>> res = browser.get('http://localhost/@@test.html')We have a test page registered that contains our viewlet. The viewlet
itself just renders a link to its location (this is just for testing).>>> print(res.html)
<html>
  <body>
     <div><div><a
     href="http://localhost/test.html/++manager++IMyManager/++viewlet++MyViewlet">My
     Viewlet</a></div></div>
  </body>
</html>Let’s follow the link to traverse the viewlet directly.>>> res = res.click('My Viewlet')
>>> res.request.url
'http://localhost/test.html/++manager++IMyManager/++viewlet++MyViewlet'
>>> print(res.body.decode())
<div><a href="http://localhost/test.html/++manager++IMyManager/++viewlet++MyViewlet">My Viewlet</a></div>What happens if a viewlet managers is nested into another viewlet? To test
this we will create another manager and another viewlet:>>> res = browser.get('http://localhost/@@nested.html')
>>> print(res.html)
<html>
  <body>
    <div><div><a href="http://localhost/nested.html/++manager++IOuterManager/++viewlet++OuterViewlet/++manager++IInnerManager/++viewlet++InnerViewlet/++manager++IMostInnerManager/++viewlet++MostInnerViewlet">Most inner viewlet</a></div></div>
  </body>
</html>Let’s follow the link to traverse the viewlet directly.>>> res = res.click('Most inner viewlet')
>>> res.request.url
'http://localhost/nested.html/++manager++IOuterManager/++viewlet++OuterViewlet/++manager++IInnerManager/++viewlet++InnerViewlet/++manager++IMostInnerManager/++viewlet++MostInnerViewlet'>>> print(res.body.decode())
<div><a href="http://localhost/nested.html/++manager++IOuterManager/++viewlet++OuterViewlet/++manager++IInnerManager/++viewlet++InnerViewlet/++manager++IMostInnerManager/++viewlet++MostInnerViewlet">Most inner viewlet</a></div>CaveatsUpdate of the manager is not called, because this may be too expensive
and normally the managers update just collects viewlets.Extracting Information from the Traversal StackThis package allows to define virtual traversal paths for collecting
arbitrary information from the traversal stack instead of, for
example, query strings.In contrast to the common way of defining custom Traversers, this
implementation does not require to go through the whole traversal
process step by step. The traversal information needed is taken from
the traversalstack directly and the used parts of the stack are
consumed. This way one don’t have to define proxy classes just for
traversal.This implementation does not work in tales because it requires the
traversalstack of the request.For each name in the traversal stack a named multiadapter is looked up
for ITraversalStackConsumer, if found the item gets removed from the
stack and the adapter is added to the request annotation.>>> from z3c.traverser.stackinfo import traversing
>>> from z3c.traverser.stackinfo import interfacesIf there are no adapters defined, the traversalstack is kept as is. To
show this behaviour we define some sample classes.>>> from zope import interface
>>> class IContent(interface.Interface):
...     pass>>> from zope.site.folder import Folder
>>> @interface.implementer(IContent)
... class Content(Folder):
...     passThere is a convinience function which returns an iterator which
iterates over tuples of adapterName, adapter. Additionally the
traversal stack of the request is consumed if needed.>>> from zope.publisher.browser import TestRequest
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> request = TestRequest()We set the traversal stack manually for testing here.>>> request.setTraversalStack(['index.html', 'path', 'some'])
>>> content = Content()So if no ITraversalStackConsumer adapters are found the stack is left
untouched.>>> list(traversing.getStackConsumers(content, request))
[]
>>> request.getTraversalStack()
['index.html', 'path', 'some']There is a base class for consumer implementations which implements
the ITraversalStackConsumer interface.>>> from z3c.traverser.stackinfo import consumer
>>> from zope.interface.verify import verifyObject
>>> o = consumer.BaseConsumer(None, None)
>>> verifyObject(interfaces.ITraversalStackConsumer,o)
TrueLet us define a custom consumer.>>> from zope import component
>>> class DummyConsumer(consumer.BaseConsumer):
...     component.adapts(IContent, IBrowserRequest)
>>> component.provideAdapter(DummyConsumer, name='some')Now we will find the newly registered consumer and the ‘some’ part of
the stack is consumed.>>> consumers = list(traversing.getStackConsumers(content, request))
>>> consumers
[('some', <DummyConsumer named 'some'>)]
>>> request.getTraversalStack()
['index.html', 'path']Each consumer at least has to consume one element, which is always
the name under which the adapter was registered under.>>> name, cons = consumers[0]
>>> cons.__name__
'some'Let us provide another adapter, to demonstrate that the adpaters
always have the reverse order of the traversal stack. This is actually
the order in the url.>>> component.provideAdapter(DummyConsumer, name='other')
>>> stack = ['index.html', 'path', 'some', 'other']
>>> request.setTraversalStack(stack)
>>> consumers = list(traversing.getStackConsumers(content, request))
>>> consumers
[('other', <DummyConsumer named 'other'>),
 ('some', <DummyConsumer named 'some'>)]>>> [c.__name__ for name, c in consumers]
['other', 'some']The arguments attribute of the consumer class defines how many
arguments are consumed/needed from the stack. Let us create a KeyValue
consumer, that should extract key value pairs from the stack.>>> class KeyValueConsumer(DummyConsumer):
...     arguments=('key', 'value')
>>> component.provideAdapter(KeyValueConsumer, name='kv')
>>> stack = ['index.html', 'value', 'key', 'kv']
>>> request.setTraversalStack(stack)
>>> consumers = list(traversing.getStackConsumers(content, request))
>>> consumers
[('kv', <KeyValueConsumer named 'kv'>)]
>>> request.getTraversalStack()
['index.html']
>>> name, cons = consumers[0]
>>> cons.key
'key'
>>> cons.value
'value'We can of course use multiple consumers of the same type.>>> stack = ['index.html', 'v2', 'k2', 'kv', 'v1', 'k1', 'kv']
>>> request.setTraversalStack(stack)
>>> consumers = list(traversing.getStackConsumers(content, request))
>>> [(c.__name__, c.key, c.value) for name, c in consumers]
[('kv', 'k1', 'v1'), ('kv', 'k2', 'v2')]If we have too less arguments a NotFound exception.>>> stack = ['k2', 'kv', 'v1', 'k1', 'kv']
>>> request.setTraversalStack(stack)
>>> consumers = list(traversing.getStackConsumers(content, request))
Traceback (most recent call last):
  ...
NotFound: Object: <Content object at ...>, name: 'kv'In order to actually use the stack consumers to retrieve information,
there is another convinience function which stores the consumers in
the requests annotations. This should noramlly be called on
BeforeTraverseEvents.>>> stack = ['index.html', 'v2', 'k2', 'kv', 'v1', 'k1', 'kv']
>>> request.setTraversalStack(stack)
>>> traversing.applyStackConsumers(content, request)
>>> request.annotations[traversing.CONSUMERS_ANNOTATION_KEY]
[<KeyValueConsumer named 'kv'>,
 <KeyValueConsumer named 'kv'>]Instead of messing with the annotations one just can adapt the request
to ITraversalStackInfo.>>> component.provideAdapter(consumer.requestTraversalStackInfo)
>>> ti = interfaces.ITraversalStackInfo(request)
>>> ti
(<KeyValueConsumer named 'kv'>, <KeyValueConsumer named 'kv'>)>>> len(ti)
2The adapter always returs an empty TraversalStackInfoObject if there
is no traversalstack information.>>> request = TestRequest()
>>> ti = interfaces.ITraversalStackInfo(request)
>>> len(ti)
0Virtual HostIf virtual hosts are used the traversal stack contains aditional information
for the virtual host which will interfere which the stack consumer.>>> stack = ['index.html', 'value', 'key',
...          'kv', '++', 'inside vh', '++vh++something']
>>> request.setTraversalStack(stack)
>>> consumers = list(traversing.getStackConsumers(content, request))
>>> consumers
[('kv', <KeyValueConsumer named 'kv'>)]
>>> request.getTraversalStack()
['index.html', '++', 'inside vh', '++vh++something']URL HandlingLet us try these things with a real url, in our test the root is the site.>>> from zope.traversing.browser.absoluteurl import absoluteURL
>>> absoluteURL(root, request)
'http://127.0.0.1'There is an unconsumedURL function which returns the url of an object
with the traversal information, which is normally omitted.>>> request = TestRequest()
>>> root['content'] = content
>>> absoluteURL(root['content'], request)
'http://127.0.0.1/content'
>>> stack = ['index.html', 'v2 space', 'k2', 'kv', 'v1', 'k1', 'kv']
>>> request.setTraversalStack(stack)
>>> traversing.applyStackConsumers(root['content'], request)
>>> traversing.unconsumedURL(root['content'], request)
'http://127.0.0.1/content/kv/k1/v1/kv/k2/v2%20space'Let us have more than one content object>>> under = content['under'] = Content()
>>> request = TestRequest()
>>> traversing.unconsumedURL(under, request)
'http://127.0.0.1/content/under'We add some consumers to the above object>>> request = TestRequest()
>>> stack = ['index.html', 'value1', 'key1', 'kv']
>>> request.setTraversalStack(stack)
>>> traversing.applyStackConsumers(root['content'], request)
>>> traversing.unconsumedURL(root['content'], request)
'http://127.0.0.1/content/kv/key1/value1'
>>> traversing.unconsumedURL(under, request)
'http://127.0.0.1/content/kv/key1/value1/under'And now to the object below too.>>> request = TestRequest()
>>> stack = ['index.html', 'value1', 'key1', 'kv']
>>> request.setTraversalStack(stack)
>>> traversing.applyStackConsumers(root['content'], request)
>>> stack = ['index.html', 'value2', 'key2', 'kv']
>>> request.setTraversalStack(stack)
>>> traversing.applyStackConsumers(under, request)
>>> traversing.unconsumedURL(root['content'], request)
'http://127.0.0.1/content/kv/key1/value1'
>>> traversing.unconsumedURL(under, request)
'http://127.0.0.1/content/kv/key1/value1/under/kv/key2/value2'Or only the object below.>>> request = TestRequest()
>>> traversing.applyStackConsumers(root['content'], request)
>>> stack = ['index.html', 'value2', 'key2', 'kv']
>>> request.setTraversalStack(stack)
>>> traversing.applyStackConsumers(under, request)
>>> traversing.unconsumedURL(root['content'], request)
'http://127.0.0.1/content'
>>> traversing.unconsumedURL(under, request)
'http://127.0.0.1/content/under/kv/key2/value2'The unconsumedURL function is also available as a view, namedunconsumed_url, similar toabsolute_urlone.>>> from zope.component import getMultiAdapter
>>> url = getMultiAdapter((under, request), name='unconsumed_url')>>> str(url)
'http://127.0.0.1/content/under/kv/key2/value2'>>> url()
'http://127.0.0.1/content/under/kv/key2/value2'Extracting Information from the Traversal StackThis is a simple example to demonstrate the usage of this
package. Please take a look into the testing directory to see how
things should be set up.>>> from webtest.app import TestApp
>>> browser = TestApp(wsgi_app,
...     extra_environ={'wsgi.handleErrors': False,
...                    'paste.throw_errors': True,
...                    'x-wsgiorg.throw_errors': True})
>>> res = browser.get('http://localhost/@@stackinfo.html')So basically we have no stack info.>>> print(res.body.decode())
Stack Info from object at http://localhost/stackinfo.html:Let us try to set foo to bar.>>> res = browser.get('http://localhost/kv/foo/bar/@@stackinfo.html')
>>> print(res.body.decode())
Stack Info from object at http://localhost/stackinfo.html:
consumer kv:
key = 'foo'
value = 'bar'Two consumers.>>> res = browser.get(
...     'http://localhost/kv/foo/bar/kv/time/late/@@stackinfo.html')
>>> print(res.body.decode())
Stack Info from object at http://localhost/stackinfo.html:
consumer kv:
key = 'foo'
value = 'bar'
consumer kv:
key = 'time'
value = 'late'Invalid url:>>> browser.get('http://localhost/kv/foo/bar/kv/@@stackinfo.html') \
...     # doctes: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
NotFound: Object: <...Folder object at ...>, name: 'kv'