import urllib
|
import warnings
|
|
from zope.interface import implements
|
from zope.interface.interfaces import IInterface
|
|
from repoze.lru import lru_cache
|
|
from pyramid.interfaces import IContextURL
|
from pyramid.interfaces import IRequestFactory
|
from pyramid.interfaces import ITraverser
|
from pyramid.interfaces import VH_ROOT_KEY
|
|
from pyramid.encode import url_quote
|
from pyramid.exceptions import URLDecodeError
|
from pyramid.location import lineage
|
from pyramid.threadlocal import get_current_registry
|
|
def find_root(resource):
|
""" Find the root node in the resource tree to which ``resource``
|
belongs. Note that ``resource`` should be :term:`location`-aware.
|
Note that the root resource is available in the request object by
|
accessing the ``request.root`` attribute.
|
"""
|
for location in lineage(resource):
|
if location.__parent__ is None:
|
resource = location
|
break
|
return resource
|
|
def find_resource(resource, path):
|
""" Given a resource object and a string or tuple representing a path
|
(such as the return value of :func:`pyramid.traversal.resource_path` or
|
:func:`pyramid.traversal.resource_path_tuple`), return a resource in this
|
application's resource tree at the specified path. The resource passed
|
in *must* be :term:`location`-aware. If the path cannot be resolved (if
|
the respective node in the resource tree does not exist), a
|
:exc:`KeyError` will be raised.
|
|
This function is the logical inverse of
|
:func:`pyramid.traversal.resource_path` and
|
:func:`pyramid.traversal.resource_path_tuple`; it can resolve any
|
path string or tuple generated by either of those functions.
|
|
Rules for passing a *string* as the ``path`` argument: if the
|
first character in the path string is the ``/``
|
character, the path is considered absolute and the resource tree
|
traversal will start at the root resource. If the first character
|
of the path string is *not* the ``/`` character, the path is
|
considered relative and resource tree traversal will begin at the resource
|
object supplied to the function as the ``resource`` argument. If an
|
empty string is passed as ``path``, the ``resource`` passed in will
|
be returned. Resource 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 :mod:`urllib.quote`.
|
For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
|
``to%20the/La%20Pe%C3%B1a`` (relative). The
|
:func:`pyramid.traversal.resource_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 resource tree
|
traversal will start at the resource tree 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 resource tree
|
traversal will begin at the resource object supplied to the function
|
as the ``resource`` argument. If an empty sequence is passed as
|
``path``, the ``resource`` 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 resource name). Resource path tuples generated by
|
:func:`pyramid.traversal.resource_path_tuple` can always be
|
resolved by ``find_resource``.
|
|
.. note:: For backwards compatibility purposes, this function can also
|
be imported as :func:`pyramid.traversal.find_model`, although doing so
|
will emit a deprecation warning.
|
"""
|
D = traverse(resource, path)
|
view_name = D['view_name']
|
context = D['context']
|
if view_name:
|
raise KeyError('%r has no subelement %s' % (context, view_name))
|
return context
|
|
find_model = find_resource # b/w compat (forever)
|
|
def find_interface(resource, class_or_interface):
|
"""
|
Return the first resource found in the :term:`lineage` of ``resource``
|
which, a) if ``class_or_interface`` is a Python class object, is an
|
instance of the class or any subclass of that class or b) if
|
``class_or_interface`` is a :term:`interface`, provides the specified
|
interface. Return ``None`` if no resource providing ``interface_or_class``
|
can be found in the lineage. The ``resource`` passed in *must* be
|
:term:`location`-aware.
|
"""
|
if IInterface.providedBy(class_or_interface):
|
test = class_or_interface.providedBy
|
else:
|
test = lambda arg: isinstance(arg, class_or_interface)
|
for location in lineage(resource):
|
if test(location):
|
return location
|
|
def resource_path(resource, *elements):
|
""" Return a string object representing the absolute physical path of the
|
resource object based on its position in the resource tree, e.g
|
``/foo/bar``. Any positional arguments passed in as ``elements`` will be
|
appended as path segments to the end of the resource path. For instance,
|
if the resource's path is ``/foo/bar`` and ``elements`` equals ``('a',
|
'b')``, the returned string will be ``/foo/bar/a/b``. The first
|
character in the string will always be the ``/`` character (a leading
|
``/`` character in a path string represents that the path is absolute).
|
|
Resource path strings returned will be escaped in the following
|
manner: each unicode path segment will be encoded as UTF-8 and
|
each path segment will be escaped via Python's :mod:`urllib.quote`.
|
For example, ``/path/to%20the/La%20Pe%C3%B1a``.
|
|
This function is a logical inverse of
|
:mod:`pyramid.traversal.find_resource`: it can be used to generate
|
path references that can later be resolved via that function.
|
|
The ``resource`` passed in *must* be :term:`location`-aware.
|
|
.. note:: Each segment in the path string returned will use the
|
``__name__`` attribute of the resource it represents within
|
the resource tree. Each of these segments *should* be a unicode
|
or string object (as per the contract of
|
:term:`location`-awareness). However, no conversion or
|
safety checking of resource names is performed. For
|
instance, if one of the resources in your tree has a
|
``__name__`` which (by error) is a dictionary, the
|
:func:`pyramid.traversal.resource_path` function will
|
attempt to append it to a string and it will cause a
|
:exc:`pyramid.exceptions.URLDecodeError`.
|
|
.. note:: The :term:`root` resource *must* have a ``__name__``
|
attribute with a value of either ``None`` or the empty
|
string for paths to be generated properly. If the root
|
resource has a non-null ``__name__`` attribute, its name
|
will be prepended to the generated path rather than a
|
single leading '/' character.
|
|
.. note:: For backwards compatibility purposes, this function can also
|
be imported as ``model_path``, although doing so will cause
|
a deprecation warning to be emitted.
|
"""
|
# joining strings is a bit expensive so we delegate to a function
|
# which caches the joined result for us
|
return _join_path_tuple(resource_path_tuple(resource, *elements))
|
|
model_path = resource_path # b/w compat (forever)
|
|
def traverse(resource, path):
|
"""Given a resource object as ``resource`` and a string or tuple
|
representing a path as ``path`` (such as the return value of
|
:func:`pyramid.traversal.resource_path` or
|
:func:`pyramid.traversal.resource_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 :term:`context` (a :term:`resource` object) found
|
via traversal or url dispatch. If the ``path`` passed in is the
|
empty string, the value of the ``resource`` argument passed to this
|
function is returned.
|
|
- ``root``: The resource object at which :term:`traversal` begins.
|
If the ``resource`` passed in was found via url dispatch or if the
|
``path`` passed in was relative (non-absolute), the value of the
|
``resource`` argument passed to this function is returned.
|
|
- ``view_name``: The :term:`view name` found during
|
:term:`traversal` or :term:`url dispatch`; if the ``resource`` 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 resource
|
object is found at ``/foo`` (but not at ``/foo/bar``), the 'view
|
name' will be ``u'bar'``. If the ``resource`` was found via
|
urldispatch, the view_name will be the name the route found was
|
registered with.
|
|
- ``subpath``: For a ``resource`` found via :term:`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 sequence. An example: if the path
|
passed is ``/foo/bar/baz/buz``, and a resource object is found at
|
``/foo`` (but not ``/foo/bar``), the 'view name' will be
|
``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``.
|
For a ``resource`` 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 from the
|
root to find the ``context`` object during :term:`traversal`.
|
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 sequence. If the ``resource`` is a resource found
|
via :term:`url dispatch`, traversed will be None.
|
|
- ``virtual_root``: A resource object representing the 'virtual' root
|
of the resource tree being traversed during :term:`traversal`.
|
See :ref:`vhosting_chapter` for a definition of the virtual root
|
object. If no virtual hosting is in effect, and the ``path``
|
passed in was absolute, the ``virtual_root`` will be the
|
*physical* root resource object (the object at which :term:`traversal`
|
begins). If the ``resource`` passed in was found via :term:`URL
|
dispatch` or if the ``path`` passed in was relative, the
|
``virtual_root`` will always equal the ``root`` object (the
|
resource passed in).
|
|
- ``virtual_root_path`` -- If :term:`traversal` was used to find
|
the ``resource``, this will be the sequence of path elements
|
traversed to find the ``virtual_root`` resource. Each of these
|
items is a Unicode object. If no path segments were traversed
|
to find the ``virtual_root`` resource (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 ``resource``, this will be
|
``None``.
|
|
If the path cannot be resolved, a :exc:`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 resource tree
|
traversal will start at the root resource. If the first character
|
of the path string is *not* the ``/`` character, the path is
|
considered relative and resource tree traversal will begin at the resource
|
object supplied to the function as the ``resource`` argument. If an
|
empty string is passed as ``path``, the ``resource`` passed in will
|
be returned. Resource path strings must be escaped in the following
|
manner: each Unicode path segment must be encoded as UTF-8 and
|
each path segment must escaped via Python's :mod:`urllib.quote`.
|
For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
|
``to%20the/La%20Pe%C3%B1a`` (relative). The
|
:func:`pyramid.traversal.resource_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 resource tree
|
traversal will start at the resource tree 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 resource tree
|
traversal will begin at the resource object supplied to the function
|
as the ``resource`` argument. If an empty sequence is passed as
|
``path``, the ``resource`` 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 resource name).
|
|
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
|
:exc:`pyramid.exceptions.URLDecodeError` 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__'):
|
# 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).
|
if path:
|
path = _join_path_tuple(tuple(path))
|
else:
|
path = ''
|
|
# The user is supposed to pass us a string object, never Unicode. In
|
# practice, however, users indeed pass Unicode to this API. If they do
|
# pass a Unicode object, its data *must* be entirely encodeable to ASCII,
|
# so we encode it here as a convenience to the user and to prevent
|
# second-order failures from cropping up (all failures will occur at this
|
# step rather than later down the line as the result of calling
|
# ``traversal_path``).
|
|
if isinstance(path, unicode):
|
path = path.encode('ascii')
|
|
if path and path[0] == '/':
|
resource = find_root(resource)
|
|
reg = get_current_registry()
|
|
request_factory = reg.queryUtility(IRequestFactory)
|
if request_factory is None:
|
from pyramid.request import Request # avoid circdep
|
request_factory = Request
|
|
request = request_factory.blank(path)
|
request.registry = reg
|
traverser = reg.queryAdapter(resource, ITraverser)
|
if traverser is None:
|
traverser = ResourceTreeTraverser(resource)
|
|
return traverser(request)
|
|
def resource_path_tuple(resource, *elements):
|
"""
|
Return a tuple representing the absolute physical path of the
|
``resource`` object based on its position in a resource tree, e.g
|
``('', 'foo', 'bar')``. Any positional arguments passed in as
|
``elements`` will be appended as elements in the tuple
|
representing the resource path. For instance, if the resource's
|
path is ``('', 'foo', 'bar')`` and elements equals ``('a', 'b')``,
|
the returned tuple will be ``('', 'foo', 'bar', 'a', 'b')``. The
|
first element of this tuple will always be the empty string (a
|
leading empty string element in a path tuple represents that the
|
path is absolute).
|
|
This function is a logical inverse of
|
:func:`pyramid.traversal.find_resource`: it can be used to
|
generate path references that can later be resolved by that function.
|
|
The ``resource`` passed in *must* be :term:`location`-aware.
|
|
.. note:: Each segment in the path tuple returned will equal the
|
``__name__`` attribute of the resource it represents within
|
the resource tree. Each of these segments *should* be a unicode
|
or string object (as per the contract of
|
:term:`location`-awareness). However, no conversion or
|
safety checking of resource names is performed. For
|
instance, if one of the resources in your tree has a
|
``__name__`` which (by error) is a dictionary, that
|
dictionary will be placed in the path tuple; no warning
|
or error will be given.
|
|
.. note:: The :term:`root` resource *must* have a ``__name__``
|
attribute with a value of either ``None`` or the empty
|
string for path tuples to be generated properly. If
|
the root resource has a non-null ``__name__`` attribute,
|
its name will be the first element in the generated
|
path tuple rather than the empty string.
|
|
.. note:: For backwards compatibility purposes, this function can also be
|
imported as ``model_path_tuple``, although doing so will cause a
|
deprecation warning to be emitted.
|
|
"""
|
return tuple(_resource_path_list(resource, *elements))
|
|
model_path_tuple = resource_path_tuple # b/w compat (forever)
|
|
def _resource_path_list(resource, *elements):
|
""" Implementation detail shared by resource_path and resource_path_tuple"""
|
path = [loc.__name__ or '' for loc in lineage(resource)]
|
path.reverse()
|
path.extend(elements)
|
return path
|
|
_model_path_list = _resource_path_list # b/w compat, not an API
|
|
def virtual_root(resource, request):
|
"""
|
Provided any :term:`resource` and a :term:`request` object, return
|
the resource object representing the :term:`virtual root` of the
|
current :term:`request`. Using a virtual root in a
|
:term:`traversal` -based :app:`Pyramid` application permits
|
rooting, for example, the resource at the traversal path ``/cms`` at
|
``http://example.com/`` instead of rooting it at
|
``http://example.com/cms/``.
|
|
If the ``resource`` passed in is a context obtained via
|
:term:`traversal`, and if the ``HTTP_X_VHM_ROOT`` key is in the
|
WSGI environment, the value of this key will be treated as a
|
'virtual root path': the :func:`pyramid.traversal.find_resource`
|
API will be used to find the virtual root resource using this path;
|
if the resource is found, it will be returned. If the
|
``HTTP_X_VHM_ROOT`` key is is not present in the WSGI environment,
|
the physical :term:`root` of the resource tree will be returned instead.
|
|
Virtual roots are not useful at all in applications that use
|
:term:`URL dispatch`. Contexts obtained via URL dispatch don't
|
really support being virtually rooted (each URL dispatch context
|
is both its own physical and virtual root). However if this API
|
is called with a ``resource`` argument which is a context obtained
|
via URL dispatch, the resource passed in will be returned
|
unconditionally."""
|
try:
|
reg = request.registry
|
except AttributeError:
|
reg = get_current_registry() # b/c
|
urlgenerator = reg.queryMultiAdapter((resource, request), IContextURL)
|
if urlgenerator is None:
|
urlgenerator = TraversalContextURL(resource, request)
|
return urlgenerator.virtual_root()
|
|
@lru_cache(1000)
|
def traversal_path(path):
|
""" Given a ``PATH_INFO`` string (slash-separated path segments),
|
return a tuple representing that path which can be used to
|
traverse a resource tree.
|
|
The ``PATH_INFO`` is split on slashes, creating a list of
|
segments. Each segment is URL-unquoted, and subsequently decoded
|
into Unicode. Each segment is assumed to be encoded using the
|
UTF-8 encoding (or a subset, such as ASCII); a
|
:exc:`pyramid.exceptions.URLDecodeError` 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.
|
|
If this function is passed a Unicode object instead of a string,
|
that Unicode object *must* directly encodeable to ASCII. For
|
example, u'/foo' will work but u'/<unprintable unicode>' (a
|
Unicode object with characters that cannot be encoded to ascii)
|
will not.
|
|
Examples:
|
|
``/``
|
|
()
|
|
``/foo/bar/baz``
|
|
(u'foo', u'bar', u'baz')
|
|
``foo/bar/baz``
|
|
(u'foo', u'bar', u'baz')
|
|
``/foo/bar/baz/``
|
|
(u'foo', u'bar', u'baz')
|
|
``/foo//bar//baz/``
|
|
(u'foo', u'bar', u'baz')
|
|
``/foo/bar/baz/..``
|
|
(u'foo', u'bar')
|
|
``/my%20archives/hello``
|
|
(u'my archives', u'hello')
|
|
``/archives/La%20Pe%C3%B1a``
|
|
(u'archives', u'<unprintable unicode>')
|
|
.. note:: This function does not generate the same type of tuples
|
that :func:`pyramid.traversal.resource_path_tuple` does.
|
In particular, the leading empty string is not present
|
in the tuple it returns, unlike tuples returned by
|
:func:`pyramid.traversal.resource_path_tuple`. As a
|
result, tuples generated by ``traversal_path`` are not
|
resolveable by the
|
:func:`pyramid.traversal.find_resource` API.
|
``traversal_path`` is a function mostly used by the
|
internals of :app:`Pyramid` and by people writing
|
their own traversal machinery, as opposed to users
|
writing applications in :app:`Pyramid`.
|
"""
|
if isinstance(path, unicode):
|
path = path.encode('ascii')
|
path = path.strip('/')
|
clean = []
|
for segment in path.split('/'):
|
segment = urllib.unquote(segment) # deal with spaces in path segment
|
if not segment or segment=='.':
|
continue
|
elif segment == '..':
|
del clean[-1]
|
else:
|
try:
|
segment = segment.decode('utf-8')
|
except UnicodeDecodeError, e:
|
raise URLDecodeError(
|
e.encoding, e.object, e.start, e.end, e.reason
|
)
|
clean.append(segment)
|
return tuple(clean)
|
|
_segment_cache = {}
|
|
def quote_path_segment(segment, safe=''):
|
""" Return a quoted representation of a 'path segment' (such as
|
the string ``__name__`` attribute of a resource) as a string. If the
|
``segment`` passed in is a unicode object, it is converted to a
|
UTF-8 string, then it is URL-quoted using Python's
|
``urllib.quote``. If the ``segment`` passed in is a string, it is
|
URL-quoted using Python's :mod:`urllib.quote`. If the segment
|
passed in is not a string or unicode object, an error will be
|
raised. The return value of ``quote_path_segment`` is always a
|
string, never Unicode.
|
|
You may pass a string of characters that need not be encoded as
|
the ``safe`` argument to this function. This corresponds to the
|
``safe`` argument to :mod:`urllib.quote`.
|
|
.. note:: The return value for each segment passed to this
|
function is cached in a module-scope dictionary for
|
speed: the cached version is returned when possible
|
rather than recomputing the quoted version. No cache
|
emptying is ever done for the lifetime of an
|
application, however. If you pass arbitrary
|
user-supplied strings to this function (as opposed to
|
some bounded set of values from a 'working set' known to
|
your application), it may become a memory leak.
|
"""
|
# The bit of this code that deals with ``_segment_cache`` is an
|
# optimization: we cache all the computation of URL path segments
|
# in this module-scope dictionary with the original string (or
|
# unicode value) as the key, so we can look it up later without
|
# needing to reencode or re-url-quote it
|
try:
|
return _segment_cache[(segment, safe)]
|
except KeyError:
|
if segment.__class__ is unicode: # isinstance slighly slower (~15%)
|
result = url_quote(segment.encode('utf-8'), safe)
|
else:
|
result = url_quote(str(segment), safe)
|
# we don't need a lock to mutate _segment_cache, as the below
|
# will generate exactly one Python bytecode (STORE_SUBSCR)
|
_segment_cache[(segment, safe)] = result
|
return result
|
|
class ResourceTreeTraverser(object):
|
""" A resource tree traverser that should be used (for speed) when
|
every resource in the tree supplies a ``__name__`` and
|
``__parent__`` attribute (ie. every resource in the tree is
|
:term:`location` aware) ."""
|
|
implements(ITraverser)
|
|
VIEW_SELECTOR = '@@'
|
|
def __init__(self, root):
|
self.root = root
|
|
def __call__(self, request):
|
try:
|
environ = request.environ
|
except AttributeError:
|
# In BFG 1.0 and before, this API expected an environ
|
# rather than a request; some bit of code may still be
|
# passing us an environ. If so, deal.
|
environ = request
|
depwarn = ('Passing an environ dictionary directly to a traverser '
|
'is deprecated in Pyramid 1.1. Pass a request object '
|
'instead.')
|
warnings.warn(depwarn, DeprecationWarning, 2)
|
|
if 'bfg.routes.matchdict' in environ:
|
matchdict = environ['bfg.routes.matchdict']
|
|
path = matchdict.get('traverse', '/') or '/'
|
if hasattr(path, '__iter__'):
|
# this is a *traverse stararg (not a {traverse})
|
path = '/'.join([quote_path_segment(x) for x in path]) or '/'
|
|
subpath = matchdict.get('subpath', ())
|
if not hasattr(subpath, '__iter__'):
|
# this is not a *subpath stararg (just a {subpath})
|
subpath = traversal_path(subpath)
|
|
else:
|
# this request did not match a route
|
subpath = ()
|
try:
|
path = environ['PATH_INFO'] or '/'
|
except KeyError:
|
path = '/'
|
|
if VH_ROOT_KEY in environ:
|
vroot_path = environ[VH_ROOT_KEY]
|
vroot_tuple = traversal_path(vroot_path)
|
vpath = vroot_path + path
|
vroot_idx = len(vroot_tuple) -1
|
else:
|
vroot_tuple = ()
|
vpath = path
|
vroot_idx = -1
|
|
root = self.root
|
ob = vroot = root
|
|
if vpath == '/': # invariant: vpath must not be empty
|
# prevent a call to traversal_path if we know it's going
|
# to return the empty tuple
|
vpath_tuple = ()
|
else:
|
# we do dead reckoning here via tuple slicing instead of
|
# pushing and popping temporary lists for speed purposes
|
# and this hurts readability; apologies
|
i = 0
|
view_selector = self.VIEW_SELECTOR
|
vpath_tuple = traversal_path(vpath)
|
for segment in vpath_tuple:
|
if segment[:2] == view_selector:
|
return {'context':ob,
|
'view_name':segment[2:],
|
'subpath':vpath_tuple[i+1:],
|
'traversed':vpath_tuple[:vroot_idx+i+1],
|
'virtual_root':vroot,
|
'virtual_root_path':vroot_tuple,
|
'root':root}
|
try:
|
getitem = ob.__getitem__
|
except AttributeError:
|
return {'context':ob,
|
'view_name':segment,
|
'subpath':vpath_tuple[i+1:],
|
'traversed':vpath_tuple[:vroot_idx+i+1],
|
'virtual_root':vroot,
|
'virtual_root_path':vroot_tuple,
|
'root':root}
|
|
try:
|
next = getitem(segment)
|
except KeyError:
|
return {'context':ob,
|
'view_name':segment,
|
'subpath':vpath_tuple[i+1:],
|
'traversed':vpath_tuple[:vroot_idx+i+1],
|
'virtual_root':vroot,
|
'virtual_root_path':vroot_tuple,
|
'root':root}
|
if i == vroot_idx:
|
vroot = next
|
ob = next
|
i += 1
|
|
return {'context':ob, 'view_name':u'', 'subpath':subpath,
|
'traversed':vpath_tuple, 'virtual_root':vroot,
|
'virtual_root_path':vroot_tuple, 'root':root}
|
|
ModelGraphTraverser = ResourceTreeTraverser # b/w compat, not API, used in wild
|
|
class TraversalContextURL(object):
|
""" The IContextURL adapter used to generate URLs for a resource in a
|
resource tree"""
|
implements(IContextURL)
|
|
vroot_varname = VH_ROOT_KEY
|
|
def __init__(self, context, request):
|
self.context = context
|
self.request = request
|
|
def virtual_root(self):
|
environ = self.request.environ
|
vroot_varname = self.vroot_varname
|
if vroot_varname in environ:
|
return find_resource(self.context, environ[vroot_varname])
|
# shortcut instead of using find_root; we probably already
|
# have it on the request
|
try:
|
return self.request.root
|
except AttributeError:
|
return find_root(self.context)
|
|
def __call__(self):
|
""" Generate a URL based on the :term:`lineage` of a :term:`resource`
|
object that is ``self.context``. If any resource in the context
|
lineage has a Unicode name, it will be converted to a UTF-8 string
|
before being attached to the URL. If a ``HTTP_X_VHM_ROOT`` key is
|
present in the WSGI environment, its value will be treated as a
|
'virtual root path': the path of the URL generated by this will be
|
left-stripped of this virtual root path value.
|
"""
|
resource = self.context
|
physical_path = resource_path(resource)
|
if physical_path != '/':
|
physical_path = physical_path + '/'
|
virtual_path = physical_path
|
|
request = self.request
|
environ = request.environ
|
vroot_varname = self.vroot_varname
|
vroot_path = environ.get(vroot_varname)
|
|
# if the physical path starts with the virtual root path, trim it out
|
# of the virtual path
|
if vroot_path is not None:
|
if physical_path.startswith(vroot_path):
|
virtual_path = physical_path[len(vroot_path):]
|
|
local_url = getattr(resource, '__resource_url__', None)
|
if local_url is not None:
|
result = local_url(request,
|
{'virtual_path':virtual_path,
|
'physical_path':physical_path},
|
)
|
if result is not None:
|
# allow it to punt by returning ``None``
|
return result
|
|
app_url = request.application_url # never ends in a slash
|
return app_url + virtual_path
|
|
@lru_cache(1000)
|
def _join_path_tuple(tuple):
|
return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/'
|
|
class DefaultRootFactory:
|
__parent__ = None
|
__name__ = None
|
def __init__(self, request):
|
matchdict = request.matchdict
|
# provide backwards compatibility for applications which
|
# used routes (at least apps without any custom "context
|
# factory") in BFG 0.9.X and before
|
if matchdict is not None:
|
self.__dict__.update(matchdict)
|