Pluggable Traversers And URL handling utilities
Project description
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
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'
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for z3c.traverser-2.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 990348bbdf948a502211a7f217e021d16b856f9ad4c8586bc9feddc947a52960 |
|
MD5 | 750b68f49287ee200def101d850ba48b |
|
BLAKE2b-256 | ebafc61c7edd5169ad42884cf9ec0b0d0507612151695a1e1d229c76137bc6e4 |