diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-05-18 07:07:12 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-05-18 07:07:12 +0000 |
| commit | 916f88578ad68470a35a4b7afd223e9dbf5fd20d (patch) | |
| tree | 52913d78e5876ca2612da56c6d066227001d9160 /repoze/bfg/traversal.py | |
| parent | 8e2f6eaae104df8bf13678a67f4690294f982e2d (diff) | |
| download | pyramid-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.py | 213 |
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 |
