summaryrefslogtreecommitdiff
path: root/repoze/bfg/traversal.py
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-05-18 07:07:12 +0000
committerChris McDonough <chrism@agendaless.com>2009-05-18 07:07:12 +0000
commit916f88578ad68470a35a4b7afd223e9dbf5fd20d (patch)
tree52913d78e5876ca2612da56c6d066227001d9160 /repoze/bfg/traversal.py
parent8e2f6eaae104df8bf13678a67f4690294f982e2d (diff)
downloadpyramid-916f88578ad68470a35a4b7afd223e9dbf5fd20d.tar.gz
pyramid-916f88578ad68470a35a4b7afd223e9dbf5fd20d.tar.bz2
pyramid-916f88578ad68470a35a4b7afd223e9dbf5fd20d.zip
Features
-------- - Added a ``traverse`` function to the ``repoze.bfg.traversal`` module. This function may be used to retrieve certain values computed during path resolution. See the Traversal API chapter of the documentation for more information about this function. Deprecations ------------ - Internal: ``ITraverser`` callables should now return a dictionary rather than a tuple. Up until 0.7.0, all ITraversers were assumed to return a 3-tuple. In 0.7.1, ITraversers were assumed to return a 6-tuple. As (by evidence) it's likely we'll need to add further information to the return value of an ITraverser callable, 0.8 assumes that an ITraverser return a dictionary with certain elements in it. See the ``repoze.bfg.interfaces.ITraverser`` interface for the list of keys that should be present in the dictionary. ``ITraversers`` which return tuples will still work, although a deprecation warning will be issued. Backwards Incompatibilities --------------------------- - If your code used the ITraverser interface directly (not via an API function such as ``find_model``) via an adapter lookup, you'll need to change your code to expect a dictionary rather than a 3- or 6-tuple if your code ever gets return values from the default ModelGraphTraverser or RoutesModelTraverser adapters.
Diffstat (limited to 'repoze/bfg/traversal.py')
-rw-r--r--repoze/bfg/traversal.py213
1 files changed, 174 insertions, 39 deletions
diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py
index 49017a93b..f21f27a39 100644
--- a/repoze/bfg/traversal.py
+++ b/repoze/bfg/traversal.py
@@ -2,7 +2,6 @@ import re
import urllib
from zope.component import getMultiAdapter
-from zope.component import queryUtility
from zope.deprecation import deprecated
@@ -17,7 +16,6 @@ from repoze.bfg.interfaces import IContextURL
from repoze.bfg.interfaces import ITraverser
from repoze.bfg.interfaces import ITraverserFactory
from repoze.bfg.interfaces import VH_ROOT_KEY
-from repoze.bfg.interfaces import ILogger
def find_root(model):
""" Find the root node in the graph to which ``model``
@@ -74,39 +72,12 @@ def find_model(model, path):
object representing a model name). Model path tuples generated by
``model_path_tuple`` can always be resolved by ``find_model``.
"""
-
- if hasattr(path, '__iter__'): # it's a tuple or some other iterable
- # the traverser factory expects PATH_INFO to be a string, not
- # unicode and it expects path segments to be utf-8 and
- # urlencoded (it's the same traverser which accepts PATH_INFO
- # from user agents; user agents always send strings).
- path = [quote_path_segment(name) for name in path]
- if path:
- path = '/'.join(path) or '/'
- else:
- path = ''
-
- if path and path[0] == '/':
- model = find_root(model)
-
- traverser = ITraverserFactory(model)
- val = traverser({'PATH_INFO':path})
- try:
- ob, name, _, _, _, _ = val
- except ValueError:
- # b/w compat for three-value-returning ITraverser (prior to 0.7.1)
- logger = queryUtility(ILogger, 'repoze.bfg.debug')
- logger and logger.warn(
- '%s is an pre-0.7.1-style ITraverser returning only '
- '3 arguments; please update it to the new '
- '6-argument-returning interface for improved '
- 'functionality. See the repoze.bfg.interfaces module '
- 'for the new ITraverser interface '
- 'definition' % traverser)
- ob, name, _ = val
- if name:
- raise KeyError('%r has no subelement %s' % (ob, name))
- return ob
+ D = traverse(model, path)
+ view_name = D['view_name']
+ context = D['context']
+ if view_name:
+ raise KeyError('%r has no subelement %s' % (context, view_name))
+ return context
def find_interface(model, interface):
"""
@@ -162,6 +133,162 @@ def model_path(model, *elements):
path = _model_path_list(model, *elements)
return path and '/'.join([quote_path_segment(x) for x in path]) or '/'
+def traverse(model, path):
+ """Given a model object as ``model` and a string or tuple
+ representing a path as ``path`` (such as the return value of
+ ``repoze.bfg.traversal.model_path`` or
+ ``repoze.bfg.traversal.model_path_tuple`` or the value of
+ ``request.environ['PATH_INFO']``), return a dictionary with the
+ keys ``context``, ``root``, ``view_name``, ``subpath``,
+ ``traversed``, ``virtual_root``, and ``virtual_root_path``.
+
+ A definition of each value in the returned dictionary:
+
+ - ``context``: The context (a 'model' object) found via traversal
+ or url dispatch. If the ``path`` passed in is the empty string,
+ the value of the ``model`` argument passed to this function is
+ returned.
+
+ - ``root``: The root model object found via traversal or url
+ dispatch. If the ``model`` passed in was found via url
+ dispatch, the value of the ``model`` argument passed to this
+ function is returned.
+
+ - ``view_name``: The 'view name' found during traversal or url
+ dispatch; if the ``model`` was found via traversal, this is
+ usually a representation of the path segment which directly
+ follows the path to the ``context`` in the ``path``. The
+ ``view_name`` will be a Unicode object or the empty string. The
+ ``view_name`` will be the empty string if there is no element
+ which follows the ``context`` path. An example: if the path
+ passed is ``/foo/bar``, and a context object is found at
+ ``/foo`` (but not at ``/foo/bar``), the 'view name' will be
+ ``u'bar'``. If the ``model`` was found via urldispatch, the
+ view_name will be the name the route found was registered with.
+
+ - ``subpath``: For a ``model`` found via traversal, this is a
+ sequence of path segments found in the ``path`` that follow the
+ ``view_name`` (if any). Each of these items is a Unicode
+ object. If no path segments follow the ``view_name``, the
+ subpath will be the empty list. An example: if the path passed
+ is ``/foo/bar/baz/buz``, and a context object is found at
+ ``/foo`` (but not ``/foo/bar``), the 'view name' will be
+ ``u'bar'`` and the subpath will be ``[u'baz', u'buz']``. For a
+ ``model`` found via url dispatch, the subpath will be a sequence
+ of values discerned from ``*subpath`` in the route pattern
+ matched or the empty sequence.
+
+ - ``traversed``: The sequence of path elements traversed to find
+ the ``context`` object. Each of these items is a Unicode
+ object. If no path segments were traversed to find the
+ ``context`` object (e.g. if the ``path`` provided is the empty
+ string), the ``traversed`` value will be the empty list. If the
+ ``model`` is a model found via urldispatch, traversed will be None.
+
+ - ``virtual_root``: A model object representing the 'virtual' root
+ of the object graph being traversed. See
+ :ref:`vhosting_chapter` for a definition of the virtual root
+ object. If no virtual hosting is in effect or if the ``model``
+ passed in was found via URL dispatch, the ``virtual_root`` will
+ always be the physical root of the object graph (the object at
+ which traversal begins).
+
+ - ``virtual_root_path`` -- If traversal was used to find the
+ ``model``, this will be the sequence of path elements traversed
+ to find the ``virtual_root`` object. Each of these items is a
+ Unicode object. If no path segments were traversed to find the
+ ``virtual_root`` object (e.g. if virtual hosting is not in
+ effect), the ``traversed`` value will be the empty list. If url
+ dispatch was used to find the ``model``, this will be None.
+
+ If the path cannot be resolved, a KeyError will be raised.
+
+ Rules for passing a *string* as the ``path`` argument: if the
+ first character in the path string is the with the ``/``
+ character, the path will considered absolute and the graph
+ traversal will start at the root object. If the first character
+ of the path string is *not* the ``/`` character, the path is
+ considered relative and graph traversal will begin at the model
+ object supplied to the function as the ``model`` argument. If an
+ empty string is passed as ``path``, the ``model`` passed in will
+ be returned. Model path strings must be escaped in the following
+ manner: each Unicode path segment must be encoded as UTF-8 and as
+ each path segment must escaped via Python's ``urllib.quote``. For
+ example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
+ ``to%20the/La%20Pe%C3%B1a`` (relative). The ``model_path``
+ function generates strings which follow these rules (albeit only
+ absolute ones).
+
+ Rules for passing a *tuple* as the ``path`` argument: if the first
+ element in the path tuple is the empty string (for example ``('',
+ 'a', 'b', 'c')``, the path is considered absolute and the graph
+ traversal will start at the graph root object. If the first
+ element in the path tuple is not the empty string (for example
+ ``('a', 'b', 'c')``), the path is considered relative and graph
+ traversal will begin at the model object supplied to the function
+ as the ``model`` argument. If an empty sequence is passed as
+ ``path``, the ``model`` passed in itself will be returned. No
+ URL-quoting or UTF-8-encoding of individual path segments within
+ the tuple is required (each segment may be any string or unicode
+ object representing a model name). Model path tuples generated by
+ ``model_path_tuple`` can always be resolved by ``find_model``.
+
+ Explanation of the conversion of ``path`` segment values to
+ Unicode during traversal: Each segment is URL-unquoted, and
+ decoded into Unicode. Each segment is assumed to be encoded using
+ the UTF-8 encoding (or a subset, such as ASCII); a TypeError is
+ raised if a segment cannot be decoded. If a segment name is empty
+ or if it is ``.``, it is ignored. If a segment name is ``..``,
+ the previous segment is deleted, and the ``..`` is ignored. As a
+ result of this process, the return values ``view_name``, each
+ element in the ``subpath``, each element in ``traversed``, and
+ each element in the ``virtual_root_path`` will be Unicode as
+ opposed to a string, and will be URL-decoded.
+ """
+ if hasattr(path, '__iter__'): # it's a tuple or some other iterable
+ # the traverser factory expects PATH_INFO to be a string, not
+ # unicode and it expects path segments to be utf-8 and
+ # urlencoded (it's the same traverser which accepts PATH_INFO
+ # from user agents; user agents always send strings).
+ path = [quote_path_segment(name) for name in path]
+ if path:
+ path = '/'.join(path) or '/'
+ else:
+ path = ''
+
+ if path and path[0] == '/':
+ model = find_root(model)
+
+ return _traverse(model, {'PATH_INFO':path})
+
+def _traverse(model, environ):
+ traverser = ITraverserFactory(model)
+ result = traverser(environ)
+ deprecation_warning = None
+ if not isinstance(result, dict):
+ try:
+ # b/w compat for 6-arg returning ITraversers (0.7.1 til 0.8a7)
+ ctx, view_name, subpath, traversed, vroot, vroot_path = result
+ except ValueError:
+ # b/w compat for 3-arg returning ITraversers (0.7.0-style
+ # and below)
+ ctx, view_name, subpath = result
+ traversed = None
+ vroot = None
+ vroot_path = None
+ deprecation_warning = (
+ '%r is an pre-0.8-style ITraverser returning a %s-argument tuple; '
+ 'please update it to the new ITraverser interface which returns '
+ 'a dictionary for improved functionality. See the '
+ '"repoze.bfg.interfaces" module for the new ITraverser interface '
+ 'definition.' % (traverser.__class__, len(result)))
+
+ result = {'context':ctx, 'view_name':view_name, 'subpath':subpath,
+ 'traversed':traversed, 'virtual_root':vroot,
+ 'virtual_root_path':vroot_path, 'root':None,
+ '_deprecation_warning':deprecation_warning}
+ return result
+
def model_path_tuple(model, *elements):
"""
Return a tuple representing the absolute physical path of the
@@ -394,22 +521,30 @@ class ModelGraphTraverser(object):
for segment in path:
if segment[:2] =='@@':
- return ob, segment[2:], path[i:], traversed, vroot, vroot_path
+ return dict(context=ob, view_name=segment[2:], subpath=path[i:],
+ traversed=traversed, virtual_root=vroot,
+ virtual_root_path=vroot_path, root=self.root)
try:
getitem = ob.__getitem__
except AttributeError:
- return ob, segment, path[i:], traversed, vroot, vroot_path
+ return dict(context=ob, view_name=segment, subpath=path[i:],
+ traversed=traversed, virtual_root=vroot,
+ virtual_root_path=vroot_path, root=self.root)
try:
next = getitem(segment)
except KeyError:
- return ob, segment, path[i:], traversed, vroot, vroot_path
+ return dict(context=ob, view_name=segment, subpath=path[i:],
+ traversed=traversed, virtual_root=vroot,
+ virtual_root_path=vroot_path, root=self.root)
if vroot_idx == i-1:
vroot = ob
traversed.append(segment)
ob = next
i += 1
- return ob, '', [], traversed, vroot, vroot_path
+ return dict(context=ob, view_name=u'', subpath=[], traversed=traversed,
+ virtual_root=vroot, virtual_root_path=vroot_path,
+ root=self.root)
class TraversalContextURL(object):
""" The IContextURL adapter used to generate URLs for a context