z3c.traverser


Namez3c.traverser JSON
Version 2.0 PyPI version JSON
download
home_pagehttps://github.com/zopefoundation/z3c.traverser
SummaryPluggable Traversers And URL handling utilities
upload_time2023-02-09 11:10:59
maintainer
docs_urlNone
authorZope Foundation and Contributors
requires_python>=3.7
licenseZPL 2.1
keywords zope3 traverser pluggable plugin viewlet
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            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 namespaces
 
 * stackinfo - 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-world


.. contents::

=======
CHANGES
=======

2.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 using ``python 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 deprecated ``zope.interface.implements`` usage with equivalent
  ``zope.interface.implementer`` decorator.

- Dropped support for Python 2.4 and 2.5.

- Switched from ``zope.testbrowser`` to ``WebTest`` for 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's ``doctest`` module instead of depreacted
  ``zope.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)
------------------

- Make ``PluggableBrowserTraverser`` implement ``IBrowserPublisher``
  interface.
- 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 ``_getContextName`` because it has been removed
  from ``absoluteURL``.


0.2.0 (2007-10-31)
------------------

- Update package meta-data.

- Resolve ``ZopeSecurityPolicy`` deprecation warning.


0.2.0b2 (2007-10-26)
--------------------

- Use only ``absolute_url`` adapters 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, see ``namespace.rst``

- Fire ``BeforeUpdateEvent`` in viewlet view


0.1.1 (2007-03-22)
------------------

- First egg release




====================
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, 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')
    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.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'] = 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
    >>> @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 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 ...>

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 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 implementation 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'


Browser traverser
-----------------

There's also a special subclass of the PluggableTraverser that
implements the ``IBrowserPublisher`` interface, thus providing the
``browserDefault`` method 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 Namespaces
=====================

Principal
---------

The ``principal`` namespace 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++another

Otherwise not

    >>> ns.traverse('anonymous', None)
    <object object at ...>


===================
Traversing Viewlets
===================

This 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 browser

Let 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):
  ...     pass

We 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 Traversing
====================

Traversing 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>


Caveats
-------

Update 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 Stack
===============================================

This 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 interfaces

If 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):
  ...     pass

There 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)
  True

Let 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)
  2

The adapter always returs an empty TraversalStackInfoObject if there
is no traversalstack information.

  >>> request = TestRequest()
  >>> ti = interfaces.ITraversalStackInfo(request)
  >>> len(ti)
  0


Virtual Host
------------

If 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 Handling
------------

Let 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, named
``unconsumed_url``, similar to ``absolute_url`` one.

  >>> 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 Stack
===============================================

This 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'



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/zopefoundation/z3c.traverser",
    "name": "z3c.traverser",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "zope3 traverser pluggable plugin viewlet",
    "author": "Zope Foundation and Contributors",
    "author_email": "zope-dev@zope.dev",
    "download_url": "https://files.pythonhosted.org/packages/48/05/9aff783cfe80678ee8c736b0438c689ac90f2c7e0ba588667db30a95b10d/z3c.traverser-2.0.tar.gz",
    "platform": null,
    "description": "This package provides the pluggable traverser mechanism allowing developers\nto add new traversers to an object without altering the original traversal\nimplementation.\n\nIn addition to the pluggable traversers, this package contains two more\nsubpackages:\n\n * viewlet - provides a way to traverse to viewlets using namespaces\n \n * stackinfo - provides a way to consume parts of url and store them\n   as attributes of the \"consumer\" object. Useful for urls like:\n   /blog/2009/02/02/hello-world\n\n\n.. contents::\n\n=======\nCHANGES\n=======\n\n2.0 (2023-02-09)\n----------------\n\n- Drop support for Python 2.7, 3.3, 3.4.\n\n- Add support for Python 3.7, 3.8, 3.9, 3.10, 3.11.\n\n- Drop support to run the tests using ``python setup.py test``.\n\n\n1.0.0 (2015-11-09)\n------------------\n\n- Standardize namespace __init__.\n\n- Claim support for Python 3.4.\n\n\n1.0.0a2 (2013-03-03)\n--------------------\n\n- Added Trove classifiers to specify supported Python versions.\n\n\n1.0.0a1 (2013-03-03)\n--------------------\n\n- Added support for Python 3.3.\n\n- Replaced deprecated ``zope.interface.implements`` usage with equivalent\n  ``zope.interface.implementer`` decorator.\n\n- Dropped support for Python 2.4 and 2.5.\n\n- Switched from ``zope.testbrowser`` to ``WebTest`` for browser testing, since\n  testbrowser is not yet ported.\n\n- Modernized API to use latest packages and component paths.\n\n- Reduced test dependencies to the smallest set possible.\n\n\n0.3.0 (2010-11-01)\n------------------\n\n- Updated test set up to run with ZTK 1.0.\n\n- Using Python's ``doctest`` module instead of depreacted\n  ``zope.testing.doctest[unit]``.\n\n\n0.2.5 (2009-03-13)\n------------------\n\n- Adapt to the move of IDefaultViewName from zope.component to zope.publisher.\n\n0.2.4 (2009-02-02)\n------------------\n\n- Make ``PluggableBrowserTraverser`` implement ``IBrowserPublisher``\n  interface.\n- Fix tests and deprecation warnings.\n- Improve test coverage.\n- Get rid of zope.app.zapi dependency by replacing its uses with direct\n  calls.\n- Change package's mailing list address to zope-dev at zope.org,\n  because zope3-dev at zope.org is now retired.\n- Change \"cheeseshop\" to \"pypi\" in the package's url.\n\n0.2.3 (2008-07-14)\n------------------\n\n- Bugfix: In z3c.traverser.stackinfo the traversal stack got messed up\n  when using the VirtualHost namespace with more than one thread.\n\n0.2.2 (2008-03-06)\n------------------\n\n- Restructuring: Separated pluggable traverser functionality into two classes\n  for better code reuse.\n\n\n0.2.1 (2007-11-92)\n------------------\n\n- Bugfix: if viewlet and managers get nested a viewlet was not found if\n  the depth reaches 3 because the context was set to the page and not\n  to the context object.\n\n- Bugfix: replaced call to ``_getContextName`` because it has been removed\n  from ``absoluteURL``.\n\n\n0.2.0 (2007-10-31)\n------------------\n\n- Update package meta-data.\n\n- Resolve ``ZopeSecurityPolicy`` deprecation warning.\n\n\n0.2.0b2 (2007-10-26)\n--------------------\n\n- Use only ``absolute_url`` adapters in unconsumed URL caclulations, to\n  make it work for traversable viewlets or other special cases too.\n\n\n0.2.0b1 (2007-09-21)\n--------------------\n\n- added a generic stack consumer handler which can be registered for\n  BeforeTraverse events.\n\n\n0.1.3 (2007-06-03)\n------------------\n\n- Added principal namespace, see ``namespace.rst``\n\n- Fire ``BeforeUpdateEvent`` in viewlet view\n\n\n0.1.1 (2007-03-22)\n------------------\n\n- First egg release\n\n\n\n\n====================\nPluggable Traversers\n====================\n\nTraversers are Zope's mechanism to convert URI paths to an object of the\napplication. They provide an extremly flexible mechanism to make decisions\nbased on the policies of the application. Unfortunately the default traverser\nimplementation is not flexible enough to deal with arbitrary extensions (via\nadapters) of objects that also wish to participate in the traversal decision\nprocess.\n\nThe pluggable traverser allows developers, especially third-party developers,\nto add new traversers to an object without altering the original traversal\nimplementation.\n\n    >>> from z3c.traverser.traverser import PluggableTraverser\n\nLet's say that we have an object\n\n    >>> from zope.interface import Interface, implementer\n    >>> class IContent(Interface):\n    ...     pass\n\n    >>> @implementer(IContent)\n    ... class Content(object):\n    ...     var = True\n\n    >>> content = Content()\n\nthat we wish to traverse to. Since traversers are presentation-type specific,\nthey are implemented as views and must thus be initiated using a request:\n\n    >>> from zope.publisher.base import TestRequest\n    >>> request = TestRequest('')\n    >>> traverser = PluggableTraverser(content, request)\n\nWe can now try to lookup the variable:\n\n    >>> traverser.publishTraverse(request, 'var')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <Content object at ...>, name: 'var'\n\nBut it failed. Why? Because we have not registered a plugin traverser yet that\nknows how to lookup attributes. This package provides such a traverser\nalready, so we just have to register it:\n\n    >>> from zope.component import provideSubscriptionAdapter\n    >>> from zope.publisher.interfaces import IPublisherRequest\n    >>> from z3c.traverser.traverser import AttributeTraverserPlugin\n\n    >>> provideSubscriptionAdapter(AttributeTraverserPlugin,\n    ...                            (IContent, IPublisherRequest))\n\nIf we now try to lookup the attribute, we the value:\n\n    >>> traverser.publishTraverse(request, 'var')\n    True\n\nHowever, an incorrect variable name will still return a ``NotFound`` error:\n\n    >>> traverser.publishTraverse(request, 'bad')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <Content object at ...>, name: 'bad'\n\nEvery traverser should also make sure that the passed in name is not a\nview. (This allows us to not specify the ``@@`` in front of a view.) So let's\nregister one:\n\n    >>> class View(object):\n    ...     def __init__(self, context, request):\n    ...         pass\n\n    >>> from zope.component import provideAdapter\n    >>> from zope.publisher.interfaces import IPublisherRequest\n    >>> provideAdapter(View,\n    ...                adapts=(IContent, IPublisherRequest),\n    ...                provides=Interface,\n    ...                name='view.html')\n\nNow we can lookup the view as well:\n\n    >>> traverser.publishTraverse(request, 'view.html')\n    <View object at ...>\n\n\nAdvanced Uses\n-------------\n\nA more interesting case to consider is a traverser for a container. If you\nreally dislike the Zope 3 traversal namespace notation ``++namespace++`` and\nyou can control the names in the container, then the pluggable traverser will\nalso provide a viable solution. Let's say we have a container\n\n    >>> from zope.container.interfaces import IContainer\n    >>> class IMyContainer(IContainer):\n    ...     pass\n\n    >>> from zope.container.btree import BTreeContainer\n    >>> @implementer(IMyContainer)\n    ... class MyContainer(BTreeContainer):\n    ...     foo = True\n    ...     bar = False\n\n    >>> myContainer = MyContainer()\n    >>> myContainer['blah'] = 123\n\nand we would like to be able to traverse\n\n  * all items of the container, as well as\n\n    >>> from z3c.traverser.traverser import ContainerTraverserPlugin\n    >>> from z3c.traverser.interfaces import ITraverserPlugin\n\n    >>> provideSubscriptionAdapter(ContainerTraverserPlugin,\n    ...                            (IMyContainer, IPublisherRequest),\n    ...                            ITraverserPlugin)\n\n  * the ``foo`` attribute. Luckily we also have a predeveloped traverser for\n    this:\n\n    >>> from z3c.traverser.traverser import \\\n    ...     SingleAttributeTraverserPlugin\n    >>> provideSubscriptionAdapter(SingleAttributeTraverserPlugin('foo'),\n    ...                            (IMyContainer, IPublisherRequest))\n\nWe can now use the pluggable traverser\n\n    >>> traverser = PluggableTraverser(myContainer, request)\n\nto look up items\n\n    >>> traverser.publishTraverse(request, 'blah')\n    123\n\nand the ``foo`` attribute:\n\n    >>> traverser.publishTraverse(request, 'foo')\n    True\n\nHowever, we cannot lookup the ``bar`` attribute or any other non-existent\nitem:\n\n    >>> traverser.publishTraverse(request, 'bar')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <MyContainer object at ...>, name: 'bar'\n\n    >>> traverser.publishTraverse(request, 'bad')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <MyContainer object at ...>, name: 'bad'\n\nYou can also add traversers that return an adapted object. For example, let's\ntake the following adapter:\n\n    >>> class ISomeAdapter(Interface):\n    ...     pass\n\n    >>> from zope.component import adapts\n    >>> @implementer(ISomeAdapter)\n    ... class SomeAdapter(object):\n    ...     adapts(IMyContainer)\n    ...\n    ...     def __init__(self, context):\n    ...         pass\n\n    >>> from zope.component import adapts, provideAdapter\n    >>> provideAdapter(SomeAdapter)\n\nNow we register this adapter under the traversal name ``some``:\n\n    >>> from z3c.traverser.traverser import AdapterTraverserPlugin\n    >>> provideSubscriptionAdapter(\n    ...     AdapterTraverserPlugin('some', ISomeAdapter),\n    ...     (IMyContainer, IPublisherRequest))\n\nSo here is the result:\n\n    >>> traverser.publishTraverse(request, 'some')\n    <SomeAdapter object at ...>\n\nIf the object is not adaptable, we'll get NotFound. Let's register a\nplugin that tries to query a named adapter for ISomeAdapter. The third\nargument for AdapterTraverserPlugin is used to specify the adapter name.\n\n    >>> provideSubscriptionAdapter(\n    ...     AdapterTraverserPlugin('badadapter', ISomeAdapter, 'other'),\n    ...     (IMyContainer, IPublisherRequest))\n\n    >>> traverser.publishTraverse(request, 'badadapter')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <MyContainer object at ...>, name: 'badadapter'\n\nTraverser Plugins\n-----------------\n\nThe `traverser` package comes with several default traverser plugins; three of\nthem were already introduced above: `SingleAttributeTraverserPlugin`,\n`AdapterTraverserPlugin`, and `ContainerTraverserPlugin`. Another plugin is\nthe the `NullTraverserPlugin`, which always just returns the object itself:\n\n    >>> from z3c.traverser.traverser import NullTraverserPlugin\n    >>> SomethingPlugin = NullTraverserPlugin('something')\n\n    >>> plugin = SomethingPlugin(content, request)\n    >>> plugin.publishTraverse(request, 'something')\n    <Content object at ...>\n\n    >>> plugin.publishTraverse(request, 'something else')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <Content object at ...>, name: 'something else'\n\nAll of the above traversers with exception of the `ContainerTraverserPlugin`\nare implementation of the abstract `NameTraverserPlugin` class. Name traversers\nare traversers that can resolve one particular name. By using the abstract\n`NameTraverserPlugin` class, all of the traverser boilerplate can be\navoided. Here is a simple example that always returns a specific value for a\ntraversed name:\n\n    >>> from z3c.traverser.traverser import NameTraverserPlugin\n    >>> class TrueTraverserPlugin(NameTraverserPlugin):\n    ...     traversalName = 'true'\n    ...     def _traverse(self, request, name):\n    ...         return True\n\nAs you can see realized name traversers must implement the ``_traverse()``\nmethod, which is only responsible for returning the result. Of course it can\nalso raise the `NotFound` error if something goes wrong during the\ncomputation. LEt's check it out:\n\n    >>> plugin = TrueTraverserPlugin(content, request)\n    >>> plugin.publishTraverse(request, 'true')\n    True\n\n    >>> plugin.publishTraverse(request, 'false')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <Content object at ...>, name: 'false'\n\nA final traverser that is offered by the package is the\n`AttributeTraverserPlugin``, which simply allows one to traverse all\naccessible attributes of an object:\n\n    >>> from z3c.traverser.traverser import AttributeTraverserPlugin\n\n    >>> plugin = AttributeTraverserPlugin(myContainer, request)\n    >>> plugin.publishTraverse(request, 'foo')\n    True\n    >>> plugin.publishTraverse(request, 'bar')\n    False\n    >>> plugin.publishTraverse(request, 'blah')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <MyContainer object at ...>, name: 'blah'\n    >>> plugin.publishTraverse(request, 'some')\n    Traceback (most recent call last):\n    ...\n    NotFound: Object: <MyContainer object at ...>, name: 'some'\n\n\nBrowser traverser\n-----------------\n\nThere's also a special subclass of the PluggableTraverser that\nimplements the ``IBrowserPublisher`` interface, thus providing the\n``browserDefault`` method that returns a default object and a view\nname to traverse and use if there's no more steps to traverse.\n\nLet's provide a view name registered as an IDefaultView adapter. This\nis usually done by zope.publisher's browser:defaultView directive.\n\n    >>> from zope.publisher.interfaces import IDefaultViewName\n    >>> provideAdapter('view.html', (IContent, Interface), IDefaultViewName)\n\n    >>> from z3c.traverser.browser import PluggableBrowserTraverser\n    >>> traverser = PluggableBrowserTraverser(content, request)\n    >>> traverser.browserDefault(request)\n    (<Content object at 0x...>, ('@@view.html',))\n\n\n=====================\nAdditional Namespaces\n=====================\n\nPrincipal\n---------\n\nThe ``principal`` namespace allows to differentiate between usernames\nin the url. This is usefull for caching on a per principal basis. The\nnamespace itself doesn't change anything. It just checks if the\nprincipal is the one that is logged in.\n\n    >>> from z3c.traverser import namespace\n    >>> from zope.publisher.browser import TestRequest\n    >>> class Request(TestRequest):\n    ...     principal = None\n    ...\n    ...     def shiftNameToApplication(self):\n    ...         pass\n\n    >>> class Principal(object):\n    ...     def __init__(self, id):\n    ...         self.id = id\n\n    >>> pid = 'something'\n    >>> r = Request()\n    >>> r.principal = Principal('anonymous')\n\nIf we have the wrong principal we get an Unauthorized exception.\n\n    >>> ns = namespace.principal(object(), r)\n    >>> ns.traverse('another', None) # doctest: +IGNORE_EXCEPTION_DETAIL\n    Traceback (most recent call last):\n    ...\n    Unauthorized: ++principal++another\n\nOtherwise not\n\n    >>> ns.traverse('anonymous', None)\n    <object object at ...>\n\n\n===================\nTraversing Viewlets\n===================\n\nThis package allows to traverse viewlets and viewletmanagers. It also\nprovides absolute url views for those objects which are described in\nthis file, for traversers see BROWSER.rst.\n\n  >>> from z3c.traverser.viewlet import browser\n\nLet us define some test classes.\n\n  >>> import zope.component\n  >>> from zope.viewlet import manager\n  >>> from zope.viewlet import interfaces\n  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer\n  >>> import zope.interface\n  >>> class ILeftColumn(interfaces.IViewletManager):\n  ...     \"\"\"Viewlet manager located in the left column.\"\"\"\n  >>> LeftColumn = manager.ViewletManager('left', ILeftColumn)\n  >>> zope.component.provideAdapter(\n  ...     LeftColumn,\n  ...     (zope.interface.Interface,\n  ...     IDefaultBrowserLayer, zope.interface.Interface),\n  ...     interfaces.IViewletManager, name='left')\n\nYou can then create a viewlet manager using this interface now:\n\n\n  >>> from zope.viewlet import viewlet\n  >>> from zope.container.contained import Contained\n\n  >>> class Content(Contained):\n  ...     pass\n  >>> root['content'] = Content()\n  >>> content = root['content']\n  >>> from zope.publisher.browser import TestRequest\n  >>> request = TestRequest()\n  >>> from zope.publisher.interfaces.browser import IBrowserView\n  >>> from zope.publisher.browser import BrowserView\n  >>> class View(BrowserView):\n  ...     pass\n\nWe have to set the name, this is normally done in zcml.\n\n  >>> view = View(content, request)\n  >>> view.__name__ = 'test.html'\n  >>> leftColumn = LeftColumn(content, request, view)\n\nLet us create a simple viewlet. Note that we need a __name__ attribute\nin order to make the viewlet traversable. Normally you don't have to\ntake care of this, because the zcml directive sets the name upon\nregistration.\n\n  >>> class MyViewlet(viewlet.ViewletBase):\n  ...     __name__ = 'myViewlet'\n  ...     def render(self):\n  ...         return u'<div>My Viewlet</div>'\n  >>> from zope.security.checker import NamesChecker, defineChecker\n  >>> viewletChecker = NamesChecker(('update', 'render'))\n  >>> defineChecker(MyViewlet, viewletChecker)\n\n  >>> zope.component.provideAdapter(\n  ...     MyViewlet,\n  ...     (zope.interface.Interface, IDefaultBrowserLayer,\n  ...     IBrowserView, ILeftColumn),\n  ...     interfaces.IViewlet, name='myViewlet')\n\nWe should now be able to get the absolute url of the viewlet and the\nmanager. We have to register the adapter for the test.\n\n  >>> from zope.traversing.browser.interfaces import IAbsoluteURL\n  >>> from zope.traversing.browser import absoluteurl\n\n  >>> zope.component.provideAdapter(\n  ...     browser.ViewletAbsoluteURL,\n  ...     (interfaces.IViewlet, IDefaultBrowserLayer),\n  ...     IAbsoluteURL)\n  >>> zope.component.provideAdapter(\n  ...     browser.ViewletManagerAbsoluteURL,\n  ...     (interfaces.IViewletManager, IDefaultBrowserLayer),\n  ...     IAbsoluteURL, name=\"absolute_url\")\n  >>> zope.component.provideAdapter(\n  ...     browser.ViewletManagerAbsoluteURL,\n  ...     (interfaces.IViewletManager, IDefaultBrowserLayer),\n  ...     IAbsoluteURL)\n  >>> myViewlet = MyViewlet(content, request, view, leftColumn)\n  >>> absoluteurl.absoluteURL(leftColumn, request)\n  'http://127.0.0.1/content/test.html/++manager++left'\n  >>> absoluteurl.absoluteURL(myViewlet, request)\n  '.../content/test.html/++manager++left/++viewlet++myViewlet'\n\n\n\n====================\n Viewlet Traversing\n====================\n\nTraversing to viewlets is done via namespaces.\n\n  >>> from webtest.app import TestApp\n  >>> browser = TestApp(wsgi_app)\n  >>> res = browser.get('http://localhost/@@test.html')\n\nWe have a test page registered that contains our viewlet. The viewlet\nitself just renders a link to its location (this is just for testing).\n\n  >>> print(res.html)\n  <html>\n    <body>\n       <div><div><a\n       href=\"http://localhost/test.html/++manager++IMyManager/++viewlet++MyViewlet\">My\n       Viewlet</a></div></div>\n    </body>\n  </html>\n\nLet's follow the link to traverse the viewlet directly.\n\n  >>> res = res.click('My Viewlet')\n  >>> res.request.url\n  'http://localhost/test.html/++manager++IMyManager/++viewlet++MyViewlet'\n  >>> print(res.body.decode())\n  <div><a href=\"http://localhost/test.html/++manager++IMyManager/++viewlet++MyViewlet\">My Viewlet</a></div>\n\nWhat happens if a viewlet managers is nested into another viewlet? To test\nthis we will create another manager and another viewlet::\n\n  >>> res = browser.get('http://localhost/@@nested.html')\n  >>> print(res.html)\n  <html>\n    <body>\n      <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>\n    </body>\n  </html>\n\nLet's follow the link to traverse the viewlet directly.\n\n  >>> res = res.click('Most inner viewlet')\n  >>> res.request.url\n  'http://localhost/nested.html/++manager++IOuterManager/++viewlet++OuterViewlet/++manager++IInnerManager/++viewlet++InnerViewlet/++manager++IMostInnerManager/++viewlet++MostInnerViewlet'\n\n  >>> print(res.body.decode())\n  <div><a href=\"http://localhost/nested.html/++manager++IOuterManager/++viewlet++OuterViewlet/++manager++IInnerManager/++viewlet++InnerViewlet/++manager++IMostInnerManager/++viewlet++MostInnerViewlet\">Most inner viewlet</a></div>\n\n\nCaveats\n-------\n\nUpdate of the manager is not called, because this may be too expensive\nand normally the managers update just collects viewlets.\n\n\n===============================================\nExtracting Information from the Traversal Stack\n===============================================\n\nThis package allows to define virtual traversal paths for collecting\narbitrary information from the traversal stack instead of, for\nexample, query strings.\n\nIn contrast to the common way of defining custom Traversers, this\nimplementation does not require to go through the whole traversal\nprocess step by step. The traversal information needed is taken from\nthe traversalstack directly and the used parts of the stack are\nconsumed. This way one don't have to define proxy classes just for\ntraversal.\n\nThis implementation does not work in tales because it requires the\ntraversalstack of the request.\n\nFor each name in the traversal stack a named multiadapter is looked up\nfor ITraversalStackConsumer, if found the item gets removed from the\nstack and the adapter is added to the request annotation.\n\n  >>> from z3c.traverser.stackinfo import traversing\n  >>> from z3c.traverser.stackinfo import interfaces\n\nIf there are no adapters defined, the traversalstack is kept as is. To\nshow this behaviour we define some sample classes.\n\n  >>> from zope import interface\n  >>> class IContent(interface.Interface):\n  ...     pass\n\n  >>> from zope.site.folder import Folder\n  >>> @interface.implementer(IContent)\n  ... class Content(Folder):\n  ...     pass\n\nThere is a convinience function which returns an iterator which\niterates over tuples of adapterName, adapter. Additionally the\ntraversal stack of the request is consumed if needed.\n\n  >>> from zope.publisher.browser import TestRequest\n  >>> from zope.publisher.interfaces.browser import IBrowserRequest\n  >>> request = TestRequest()\n\nWe set the traversal stack manually for testing here.\n\n  >>> request.setTraversalStack(['index.html', 'path', 'some'])\n  >>> content = Content()\n\nSo if no ITraversalStackConsumer adapters are found the stack is left\nuntouched.\n\n  >>> list(traversing.getStackConsumers(content, request))\n  []\n  >>> request.getTraversalStack()\n  ['index.html', 'path', 'some']\n\nThere is a base class for consumer implementations which implements\nthe ITraversalStackConsumer interface.\n\n  >>> from z3c.traverser.stackinfo import consumer\n  >>> from zope.interface.verify import verifyObject\n  >>> o = consumer.BaseConsumer(None, None)\n  >>> verifyObject(interfaces.ITraversalStackConsumer,o)\n  True\n\nLet us define a custom consumer.\n\n  >>> from zope import component\n  >>> class DummyConsumer(consumer.BaseConsumer):\n  ...     component.adapts(IContent, IBrowserRequest)\n  >>> component.provideAdapter(DummyConsumer, name='some')\n\nNow we will find the newly registered consumer and the 'some' part of\nthe stack is consumed.\n\n  >>> consumers = list(traversing.getStackConsumers(content, request))\n  >>> consumers\n  [('some', <DummyConsumer named 'some'>)]\n  >>> request.getTraversalStack()\n  ['index.html', 'path']\n\nEach consumer at least has to consume one element, which is always\nthe name under which the adapter was registered under.\n\n  >>> name, cons = consumers[0]\n  >>> cons.__name__\n  'some'\n\nLet us provide another adapter, to demonstrate that the adpaters\nalways have the reverse order of the traversal stack. This is actually\nthe order in the url.\n\n  >>> component.provideAdapter(DummyConsumer, name='other')\n  >>> stack = ['index.html', 'path', 'some', 'other']\n  >>> request.setTraversalStack(stack)\n  >>> consumers = list(traversing.getStackConsumers(content, request))\n  >>> consumers\n  [('other', <DummyConsumer named 'other'>),\n   ('some', <DummyConsumer named 'some'>)]\n\n  >>> [c.__name__ for name, c in consumers]\n  ['other', 'some']\n\nThe arguments attribute of the consumer class defines how many\narguments are consumed/needed from the stack. Let us create a KeyValue\nconsumer, that should extract key value pairs from the stack.\n\n  >>> class KeyValueConsumer(DummyConsumer):\n  ...     arguments=('key', 'value')\n  >>> component.provideAdapter(KeyValueConsumer, name='kv')\n  >>> stack = ['index.html', 'value', 'key', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> consumers = list(traversing.getStackConsumers(content, request))\n  >>> consumers\n  [('kv', <KeyValueConsumer named 'kv'>)]\n  >>> request.getTraversalStack()\n  ['index.html']\n  >>> name, cons = consumers[0]\n  >>> cons.key\n  'key'\n  >>> cons.value\n  'value'\n\nWe can of course use multiple consumers of the same type.\n\n  >>> stack = ['index.html', 'v2', 'k2', 'kv', 'v1', 'k1', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> consumers = list(traversing.getStackConsumers(content, request))\n  >>> [(c.__name__, c.key, c.value) for name, c in consumers]\n  [('kv', 'k1', 'v1'), ('kv', 'k2', 'v2')]\n\nIf we have too less arguments a NotFound exception.\n\n  >>> stack = ['k2', 'kv', 'v1', 'k1', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> consumers = list(traversing.getStackConsumers(content, request))\n  Traceback (most recent call last):\n    ...\n  NotFound: Object: <Content object at ...>, name: 'kv'\n\n\nIn order to actually use the stack consumers to retrieve information,\nthere is another convinience function which stores the consumers in\nthe requests annotations. This should noramlly be called on\nBeforeTraverseEvents.\n\n  >>> stack = ['index.html', 'v2', 'k2', 'kv', 'v1', 'k1', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> traversing.applyStackConsumers(content, request)\n  >>> request.annotations[traversing.CONSUMERS_ANNOTATION_KEY]\n  [<KeyValueConsumer named 'kv'>,\n   <KeyValueConsumer named 'kv'>]\n\nInstead of messing with the annotations one just can adapt the request\nto ITraversalStackInfo.\n\n  >>> component.provideAdapter(consumer.requestTraversalStackInfo)\n  >>> ti = interfaces.ITraversalStackInfo(request)\n  >>> ti\n  (<KeyValueConsumer named 'kv'>, <KeyValueConsumer named 'kv'>)\n\n  >>> len(ti)\n  2\n\nThe adapter always returs an empty TraversalStackInfoObject if there\nis no traversalstack information.\n\n  >>> request = TestRequest()\n  >>> ti = interfaces.ITraversalStackInfo(request)\n  >>> len(ti)\n  0\n\n\nVirtual Host\n------------\n\nIf virtual hosts are used the traversal stack contains aditional information\nfor the virtual host which will interfere which the stack consumer.\n\n  >>> stack = ['index.html', 'value', 'key',\n  ...          'kv', '++', 'inside vh', '++vh++something']\n  >>> request.setTraversalStack(stack)\n  >>> consumers = list(traversing.getStackConsumers(content, request))\n  >>> consumers\n  [('kv', <KeyValueConsumer named 'kv'>)]\n  >>> request.getTraversalStack()\n  ['index.html', '++', 'inside vh', '++vh++something']\n\n\nURL Handling\n------------\n\nLet us try these things with a real url, in our test the root is the site.\n\n  >>> from zope.traversing.browser.absoluteurl import absoluteURL\n  >>> absoluteURL(root, request)\n  'http://127.0.0.1'\n\nThere is an unconsumedURL function which returns the url of an object\nwith the traversal information, which is normally omitted.\n\n  >>> request = TestRequest()\n  >>> root['content'] = content\n  >>> absoluteURL(root['content'], request)\n  'http://127.0.0.1/content'\n  >>> stack = ['index.html', 'v2 space', 'k2', 'kv', 'v1', 'k1', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> traversing.applyStackConsumers(root['content'], request)\n  >>> traversing.unconsumedURL(root['content'], request)\n  'http://127.0.0.1/content/kv/k1/v1/kv/k2/v2%20space'\n\nLet us have more than one content object\n\n  >>> under = content['under'] = Content()\n  >>> request = TestRequest()\n  >>> traversing.unconsumedURL(under, request)\n  'http://127.0.0.1/content/under'\n\nWe add some consumers to the above object\n\n  >>> request = TestRequest()\n  >>> stack = ['index.html', 'value1', 'key1', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> traversing.applyStackConsumers(root['content'], request)\n  >>> traversing.unconsumedURL(root['content'], request)\n  'http://127.0.0.1/content/kv/key1/value1'\n  >>> traversing.unconsumedURL(under, request)\n  'http://127.0.0.1/content/kv/key1/value1/under'\n\nAnd now to the object below too.\n\n  >>> request = TestRequest()\n  >>> stack = ['index.html', 'value1', 'key1', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> traversing.applyStackConsumers(root['content'], request)\n  >>> stack = ['index.html', 'value2', 'key2', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> traversing.applyStackConsumers(under, request)\n  >>> traversing.unconsumedURL(root['content'], request)\n  'http://127.0.0.1/content/kv/key1/value1'\n  >>> traversing.unconsumedURL(under, request)\n  'http://127.0.0.1/content/kv/key1/value1/under/kv/key2/value2'\n\nOr only the object below.\n\n  >>> request = TestRequest()\n  >>> traversing.applyStackConsumers(root['content'], request)\n  >>> stack = ['index.html', 'value2', 'key2', 'kv']\n  >>> request.setTraversalStack(stack)\n  >>> traversing.applyStackConsumers(under, request)\n  >>> traversing.unconsumedURL(root['content'], request)\n  'http://127.0.0.1/content'\n  >>> traversing.unconsumedURL(under, request)\n  'http://127.0.0.1/content/under/kv/key2/value2'\n\nThe unconsumedURL function is also available as a view, named\n``unconsumed_url``, similar to ``absolute_url`` one.\n\n  >>> from zope.component import getMultiAdapter\n  >>> url = getMultiAdapter((under, request), name='unconsumed_url')\n\n  >>> str(url)\n  'http://127.0.0.1/content/under/kv/key2/value2'\n\n  >>> url()\n  'http://127.0.0.1/content/under/kv/key2/value2'\n\n\n===============================================\nExtracting Information from the Traversal Stack\n===============================================\n\nThis is a simple example to demonstrate the usage of this\npackage. Please take a look into the testing directory to see how\nthings should be set up.\n\n  >>> from webtest.app import TestApp\n  >>> browser = TestApp(wsgi_app,\n  ...     extra_environ={'wsgi.handleErrors': False,\n  ...                    'paste.throw_errors': True,\n  ...                    'x-wsgiorg.throw_errors': True})\n  >>> res = browser.get('http://localhost/@@stackinfo.html')\n\nSo basically we have no stack info.\n\n  >>> print(res.body.decode())\n  Stack Info from object at http://localhost/stackinfo.html:\n\nLet us try to set foo to bar.\n\n  >>> res = browser.get('http://localhost/kv/foo/bar/@@stackinfo.html')\n  >>> print(res.body.decode())\n  Stack Info from object at http://localhost/stackinfo.html:\n  consumer kv:\n  key = 'foo'\n  value = 'bar'\n\nTwo consumers.\n\n  >>> res = browser.get(\n  ...     'http://localhost/kv/foo/bar/kv/time/late/@@stackinfo.html')\n  >>> print(res.body.decode())\n  Stack Info from object at http://localhost/stackinfo.html:\n  consumer kv:\n  key = 'foo'\n  value = 'bar'\n  consumer kv:\n  key = 'time'\n  value = 'late'\n\nInvalid url:\n\n  >>> browser.get('http://localhost/kv/foo/bar/kv/@@stackinfo.html') \\\n  ...     # doctes: +IGNORE_EXCEPTION_DETAIL\n  Traceback (most recent call last):\n  ...\n  NotFound: Object: <...Folder object at ...>, name: 'kv'\n\n\n",
    "bugtrack_url": null,
    "license": "ZPL 2.1",
    "summary": "Pluggable Traversers And URL handling utilities",
    "version": "2.0",
    "split_keywords": [
        "zope3",
        "traverser",
        "pluggable",
        "plugin",
        "viewlet"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ebafc61c7edd5169ad42884cf9ec0b0d0507612151695a1e1d229c76137bc6e4",
                "md5": "750b68f49287ee200def101d850ba48b",
                "sha256": "990348bbdf948a502211a7f217e021d16b856f9ad4c8586bc9feddc947a52960"
            },
            "downloads": -1,
            "filename": "z3c.traverser-2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "750b68f49287ee200def101d850ba48b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 40287,
            "upload_time": "2023-02-09T11:10:56",
            "upload_time_iso_8601": "2023-02-09T11:10:56.923474Z",
            "url": "https://files.pythonhosted.org/packages/eb/af/c61c7edd5169ad42884cf9ec0b0d0507612151695a1e1d229c76137bc6e4/z3c.traverser-2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "48059aff783cfe80678ee8c736b0438c689ac90f2c7e0ba588667db30a95b10d",
                "md5": "5a22aaa10d8268699d04cd702f6204a6",
                "sha256": "1ef5a0ee392ccb8c5da199a7cc0a5bacf7624bd04606afe354742f0659722809"
            },
            "downloads": -1,
            "filename": "z3c.traverser-2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "5a22aaa10d8268699d04cd702f6204a6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 36654,
            "upload_time": "2023-02-09T11:10:59",
            "upload_time_iso_8601": "2023-02-09T11:10:59.483298Z",
            "url": "https://files.pythonhosted.org/packages/48/05/9aff783cfe80678ee8c736b0438c689ac90f2c7e0ba588667db30a95b10d/z3c.traverser-2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-02-09 11:10:59",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "zopefoundation",
    "github_project": "z3c.traverser",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "z3c.traverser"
}
        
Elapsed time: 0.04874s