diff options
Diffstat (limited to 'src/zope/traversing/namespace.py')
-rw-r--r-- | src/zope/traversing/namespace.py | 311 |
1 files changed, 218 insertions, 93 deletions
diff --git a/src/zope/traversing/namespace.py b/src/zope/traversing/namespace.py index 7ef07a4..ef296e3 100644 --- a/src/zope/traversing/namespace.py +++ b/src/zope/traversing/namespace.py @@ -11,7 +11,48 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -"""URL Namespace Implementations +""" +URL Namespace Implementations + +A URL Namespace is usually a path segment that looks like ``++ns++name``. +(It can also look like ``@@name``, which is a +shortcut for ``++view++name``. See :func:`nsParse` for details.) + +``ns`` is the name of the namespace (a named, registered adapter that +implements `ITraversable`) and ``name`` is the name to traverse to in +that namespace. + +The function :func:`namespaceLookup` handles this process. + +If you configure this package by loading its ``configure.zcml`` using +:mod:`zope.configuration.xmlconfig`, several namespaces are registered. They +are registered both as single adapters for any object, and as +multi-adapters (views) for any object together with a +`zope.publisher.interfaces.IRequest`. Those namespaces are: + +etc + Implemented in `etc` +attribute + Implemented in `attr` +adapter + Implemented in `adapter` +item + Implemented in `item` +acquire + Implemented in `acquire` +view + Implemented in `view` +resource + Implemented in `resource` +lang + Implemented in `lang` +skin + Implemented in `skin` +vh + Implemented in `vh` +debug + Implemented in `debug` (only if the ZCML feature ``devmode`` is enabled) + and only registered as a multi-adapter. """ __docformat__ = 'restructuredtext' @@ -41,68 +82,71 @@ class ExcessiveDepth(LocationError): def namespaceLookup(ns, name, object, request=None): - """Lookup a value from a namespace + """ + Lookup a value from a namespace. - We look up a value using a view or an adapter, depending on - whether a request is passed. + We look up a value by getting an adapter from the *object* to + :class:`~zope.traversing.interfaces.ITraversable` named *ns*. If + the *request* is passed, we get a multi-adapter on the *object* + and *request* (sometimes this is called a "view"). - Let's start with adapter-based transersal: + Let's start with adapter-based traversal:: - >>> class I(zope.interface.Interface): - ... 'Test interface' - >>> @zope.interface.implementer(I) - ... class C(object): - ... pass + >>> class I(zope.interface.Interface): + ... 'Test interface' + >>> @zope.interface.implementer(I) + ... class C(object): + ... pass - We'll register a simple testing adapter: + We'll register a simple testing adapter:: - >>> class Adapter(object): - ... def __init__(self, context): - ... self.context = context - ... def traverse(self, name, remaining): - ... return name+'42' + >>> class Adapter(object): + ... def __init__(self, context): + ... self.context = context + ... def traverse(self, name, remaining): + ... return name+'42' - >>> zope.component.provideAdapter(Adapter, (I,), ITraversable, 'foo') + >>> zope.component.provideAdapter(Adapter, (I,), ITraversable, 'foo') Then given an object, we can traverse it with a - namespace-qualified name: + namespace-qualified name:: - >>> namespaceLookup('foo', 'bar', C()) - 'bar42' + >>> namespaceLookup('foo', 'bar', C()) + 'bar42' - If we give an invalid namespace, we'll get a not found error: + If we give an invalid namespace, we'll get a not found error:: - >>> namespaceLookup('fiz', 'bar', C()) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - LocationError: (<zope.traversing.namespace.C object at 0x...>, '++fiz++bar') + >>> namespaceLookup('fiz', 'bar', C()) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + LocationError: (<zope.traversing.namespace.C object at 0x...>, '++fiz++bar') - We'll get the same thing if we provide a request: + We'll get the same thing if we provide a request:: - >>> from zope.publisher.browser import TestRequest - >>> request = TestRequest() - >>> namespaceLookup('foo', 'bar', C(), request) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - LocationError: (<zope.traversing.namespace.C object at 0x...>, '++foo++bar') + >>> from zope.publisher.browser import TestRequest + >>> request = TestRequest() + >>> namespaceLookup('foo', 'bar', C(), request) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + LocationError: (<zope.traversing.namespace.C object at 0x...>, '++foo++bar') - We need to provide a view: + We need to provide a view:: - >>> class View(object): - ... def __init__(self, context, request): - ... pass - ... def traverse(self, name, remaining): - ... return name+'fromview' - >>> from zope.traversing.testing import browserView - >>> browserView(I, 'foo', View, providing=ITraversable) + >>> class View(object): + ... def __init__(self, context, request): + ... pass + ... def traverse(self, name, remaining): + ... return name+'fromview' + >>> from zope.traversing.testing import browserView + >>> browserView(I, 'foo', View, providing=ITraversable) - >>> namespaceLookup('foo', 'bar', C(), request) - 'barfromview' + >>> namespaceLookup('foo', 'bar', C(), request) + 'barfromview' - Clean up: + Clean up:: - >>> from zope.testing.cleanup import cleanUp - >>> cleanUp() + >>> from zope.testing.cleanup import cleanUp + >>> cleanUp() """ if request is not None: traverser = zope.component.queryMultiAdapter((object, request), @@ -120,32 +164,33 @@ namespace_pattern = re.compile('[+][+]([a-zA-Z0-9_]+)[+][+]') def nsParse(name): - """Parse a namespace-qualified name into a namespace name and a - name. Returns the namespace name and a name. - - A namespace-qualified name is usually of the form ++ns++name, as in: + """ + Parse a namespace-qualified name into a namespace name and a name. + Returns the namespace name and a name. - >>> nsParse('++acquire++foo') - ('acquire', 'foo') + A namespace-qualified name is usually of the form ++ns++name, as + in:: - The part inside the +s must be an identifier, so: + >>> nsParse('++acquire++foo') + ('acquire', 'foo') - >>> nsParse('++hello world++foo') - ('', '++hello world++foo') - >>> nsParse('+++acquire+++foo') - ('', '+++acquire+++foo') + The part inside the +s must be an identifier, so:: - But it may also be a @@foo, which implies the view namespace: + >>> nsParse('++hello world++foo') + ('', '++hello world++foo') + >>> nsParse('+++acquire+++foo') + ('', '+++acquire+++foo') - >>> nsParse('@@foo') - ('view', 'foo') + But it may also be a @@foo, which implies the view namespace:: - >>> nsParse('@@@foo') - ('view', '@foo') + >>> nsParse('@@foo') + ('view', 'foo') - >>> nsParse('@foo') - ('', '@foo') + >>> nsParse('@@@foo') + ('view', '@foo') + >>> nsParse('@foo') + ('', '@foo') """ ns = '' if name.startswith('@@'): @@ -160,14 +205,14 @@ def nsParse(name): return ns, name -def getResource(site, name, request): - resource = queryResource(site, name, request) +def getResource(context, name, request): + resource = queryResource(context, name, request) if resource is None: - raise LocationError(site, name) + raise LocationError(context, name) return resource -def queryResource(site, name, request, default=None): +def queryResource(context, name, request, default=None): resource = zope.component.queryAdapter(request, name=name) if resource is None: return default @@ -176,7 +221,7 @@ def queryResource(site, name, request, default=None): # resource to do this. We still return the proxied resource. r = removeSecurityProxy(resource) - r.__parent__ = site + r.__parent__ = context r.__name__ = name return resource @@ -188,28 +233,28 @@ def queryResource(site, name, request, default=None): class SimpleHandler(object): def __init__(self, context, request=None): - """Simple handlers can be used as adapters or views - - They ignore their second constructor arg and store the first - one in their context attr: - - >>> SimpleHandler(42).context - 42 - - >>> SimpleHandler(42, 43).context - 42 + """ + It ignores its second constructor arg and stores the first + one in its ``context`` attr. """ self.context = context class acquire(SimpleHandler): - """Traversal adapter for the acquire namespace + """ + Traversal adapter for the ``acquire`` namespace. + + This namespace tries to traverse to the given *name* + starting with the context object. If it cannot be found, + it proceeds to look at each ``__parent__`` all the way + up the tree until it is found. """ def traverse(self, name, remaining): - """Acquire a name + """ + Acquire a name - Let's set up some example data: + Let's set up some example data:: >>> @zope.interface.implementer(ITraversable) ... class testcontent(object): @@ -272,6 +317,12 @@ class acquire(SimpleHandler): class attr(SimpleHandler): + """ + Traversal adapter for the ``attribute`` namespace. + + This namespace simply looks for an attribute of the given + *name*. + """ def traverse(self, name, ignored): """Attribute traversal adapter @@ -288,6 +339,12 @@ class attr(SimpleHandler): class item(SimpleHandler): + """ + Traversal adapter for the ``item`` namespace. + + This namespace simply uses ``__getitem__`` to find a + value of the given *name*. + """ def traverse(self, name, ignored): """Item traversal adapter @@ -303,6 +360,17 @@ class item(SimpleHandler): class etc(SimpleHandler): + """ + Traversal adapter for the ``etc`` namespace. + + This namespace provides for a layer of indirection. The given + **name** is used to find a utility of that name that implements + `zope.traversing.interfaces.IEtcNamespace`. + + As a special case, if there is no such utility, and the name is + "site", then we will attempt to call a method named ``getSiteManager`` + on the *context* object. + """ def traverse(self, name, ignored): utility = zope.component.queryUtility(IEtcNamespace, name) @@ -327,6 +395,15 @@ class etc(SimpleHandler): @zope.interface.implementer(ITraversable) class view(object): + """ + Traversal adapter for the ``view`` (``@@``) namespace. + + This looks for the default multi-adapter from the *context* and + *request* of the given *name*. + + :raises zope.location.interfaces.LocationError: If no such + adapter can be found. + """ def __init__(self, context, request): self.context = context @@ -342,6 +419,14 @@ class view(object): class resource(view): + """ + Traversal adapter for the ``resource`` namespace. + + Resources are default adapters of the given *name* for the + *request* (**not** the *context*). The returned object will have + its ``__parent__`` set to the *context* and its ``__name__`` will + match the *name* we traversed. + """ def traverse(self, name, ignored): # The context is important here, since it becomes the parent of the @@ -350,6 +435,17 @@ class resource(view): class lang(view): + """ + Traversal adapter for the ``lang`` namespace. + + Traversing to *name* means to adapt the request to + :class:`zope.i18n.interfaces.IModifiableUserPreferredLanguages` + and set the *name* as the only preferred language. + + This needs the *request* to support + :class:`zope.publisher.interfaces.http.IVirtualHostRequest` because + it shifts the language name to the application. + """ def traverse(self, name, ignored): self.request.shiftNameToApplication() @@ -359,6 +455,18 @@ class lang(view): class skin(view): + """ + Traversal adapter for the ``skin`` namespace. + + Traversing to *name* looks for the + :class:`zope.publisher.interfaces.browser.IBrowserSkinType` + utility having the given name, and then applies it to the + *request* with :func:`.applySkin`. + + This needs the *request* to support + :class:`zope.publisher.interfaces.http.IVirtualHostRequest` + because it shifts the skin name to the application. + """ def traverse(self, name, ignored): self.request.shiftNameToApplication() @@ -371,6 +479,16 @@ class skin(view): class vh(view): + """ + Traversal adapter for the ``vh`` namespace. + + Traversing to *name*, which must be of the form + ``protocol:host:port`` causes a call to + :meth:`zope.publisher.interfaces.http.IVirtualHostRequest.setApplicationServer`. + Segments in the request's traversal stack up to a prior ``++`` are + collected and become the application names given to + :meth:`zope.publisher.interfaces.http.IVirtualHostRequest.setVirtualHostRoot`. + """ def traverse(self, name, ignored): @@ -410,13 +528,17 @@ class vh(view): class adapter(SimpleHandler): + """ + Traversal adapter for the ``adapter`` namespace. - def traverse(self, name, ignored): - """Adapter traversal adapter + This adapter provides traversal to named adapters for the + *context* registered to provide + `zope.traversing.interfaces.IPathAdapter`. + """"" - This adapter provides traversal to named adapters registered - to provide IPathAdapter. + def traverse(self, name, ignored): + """ To demonstrate this, we need to register some adapters: >>> def adapter1(ob): @@ -454,15 +576,18 @@ class adapter(SimpleHandler): class debug(view): + """ + Traversal adapter for the ``debug`` namespace. - enable_debug = __debug__ + This adapter allows debugging flags to be set in the request. - def traverse(self, name, ignored): - """Debug traversal adapter + .. seealso:: :class:`zope.publisher.interfaces.IDebugFlags` + """ - This adapter allows debugging flags to be set in the request. - See IDebugFlags. + enable_debug = __debug__ + def traverse(self, name, ignored): + """ Setup for demonstration: >>> from zope.publisher.browser import TestRequest @@ -470,7 +595,7 @@ class debug(view): >>> ob = object() >>> adapter = debug(ob, request) - in debug mode, ++debug++source enables source annotations + in debug mode, ``++debug++source`` enables source annotations >>> request.debug.sourceAnnotations False @@ -479,7 +604,7 @@ class debug(view): >>> request.debug.sourceAnnotations True - ++debug++tal enables TAL markup in output + ``++debug++tal`` enables TAL markup in output >>> request.debug.showTAL False @@ -488,7 +613,7 @@ class debug(view): >>> request.debug.showTAL True - ++debug++errors enables tracebacks (by switching to debug skin) + ``++debug++errors`` enables tracebacks (by switching to debug skin) >>> from zope.publisher.interfaces.browser import IBrowserRequest |