Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pyramid/traversal.py : 26%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from zope.interface import implementer
2from zope.interface.interfaces import IInterface
4from pyramid.interfaces import (
5 IResourceURL,
6 IRequestFactory,
7 ITraverser,
8 VH_ROOT_KEY,
9)
11from pyramid.compat import (
12 PY2,
13 native_,
14 text_,
15 ascii_native_,
16 text_type,
17 binary_type,
18 is_nonstr_iter,
19 decode_path_info,
20 unquote_bytes_to_wsgi,
21 lru_cache,
22)
24from pyramid.encode import url_quote
25from pyramid.exceptions import URLDecodeError
26from pyramid.location import lineage
27from pyramid.threadlocal import get_current_registry
29PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob
30PATH_SAFE = PATH_SEGMENT_SAFE + "/"
32empty = text_('')
35def find_root(resource):
36 """ Find the root node in the resource tree to which ``resource``
37 belongs. Note that ``resource`` should be :term:`location`-aware.
38 Note that the root resource is available in the request object by
39 accessing the ``request.root`` attribute.
40 """
41 for location in lineage(resource):
42 if location.__parent__ is None:
43 resource = location
44 break
45 return resource
48def find_resource(resource, path):
49 """ Given a resource object and a string or tuple representing a path
50 (such as the return value of :func:`pyramid.traversal.resource_path` or
51 :func:`pyramid.traversal.resource_path_tuple`), return a resource in this
52 application's resource tree at the specified path. The resource passed
53 in *must* be :term:`location`-aware. If the path cannot be resolved (if
54 the respective node in the resource tree does not exist), a
55 :exc:`KeyError` will be raised.
57 This function is the logical inverse of
58 :func:`pyramid.traversal.resource_path` and
59 :func:`pyramid.traversal.resource_path_tuple`; it can resolve any
60 path string or tuple generated by either of those functions.
62 Rules for passing a *string* as the ``path`` argument: if the
63 first character in the path string is the ``/``
64 character, the path is considered absolute and the resource tree
65 traversal will start at the root resource. If the first character
66 of the path string is *not* the ``/`` character, the path is
67 considered relative and resource tree traversal will begin at the resource
68 object supplied to the function as the ``resource`` argument. If an
69 empty string is passed as ``path``, the ``resource`` passed in will
70 be returned. Resource path strings must be escaped in the following
71 manner: each Unicode path segment must be encoded as UTF-8 and as
72 each path segment must escaped via Python's :mod:`urllib.quote`.
73 For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
74 ``to%20the/La%20Pe%C3%B1a`` (relative). The
75 :func:`pyramid.traversal.resource_path` function generates strings
76 which follow these rules (albeit only absolute ones).
78 Rules for passing *text* (Unicode) as the ``path`` argument are the same
79 as those for a string. In particular, the text may not have any nonascii
80 characters in it.
82 Rules for passing a *tuple* as the ``path`` argument: if the first
83 element in the path tuple is the empty string (for example ``('',
84 'a', 'b', 'c')``, the path is considered absolute and the resource tree
85 traversal will start at the resource tree root object. If the first
86 element in the path tuple is not the empty string (for example
87 ``('a', 'b', 'c')``), the path is considered relative and resource tree
88 traversal will begin at the resource object supplied to the function
89 as the ``resource`` argument. If an empty sequence is passed as
90 ``path``, the ``resource`` passed in itself will be returned. No
91 URL-quoting or UTF-8-encoding of individual path segments within
92 the tuple is required (each segment may be any string or unicode
93 object representing a resource name). Resource path tuples generated by
94 :func:`pyramid.traversal.resource_path_tuple` can always be
95 resolved by ``find_resource``.
96 """
97 if isinstance(path, text_type):
98 path = ascii_native_(path)
99 D = traverse(resource, path)
100 view_name = D['view_name']
101 context = D['context']
102 if view_name:
103 raise KeyError('%r has no subelement %s' % (context, view_name))
104 return context
107find_model = find_resource # b/w compat (forever)
110def find_interface(resource, class_or_interface):
111 """
112 Return the first resource found in the :term:`lineage` of ``resource``
113 which, a) if ``class_or_interface`` is a Python class object, is an
114 instance of the class or any subclass of that class or b) if
115 ``class_or_interface`` is a :term:`interface`, provides the specified
116 interface. Return ``None`` if no resource providing ``interface_or_class``
117 can be found in the lineage. The ``resource`` passed in *must* be
118 :term:`location`-aware.
119 """
120 if IInterface.providedBy(class_or_interface):
121 test = class_or_interface.providedBy
122 else:
123 test = lambda arg: isinstance(arg, class_or_interface)
124 for location in lineage(resource):
125 if test(location):
126 return location
129def resource_path(resource, *elements):
130 """ Return a string object representing the absolute physical path of the
131 resource object based on its position in the resource tree, e.g
132 ``/foo/bar``. Any positional arguments passed in as ``elements`` will be
133 appended as path segments to the end of the resource path. For instance,
134 if the resource's path is ``/foo/bar`` and ``elements`` equals ``('a',
135 'b')``, the returned string will be ``/foo/bar/a/b``. The first
136 character in the string will always be the ``/`` character (a leading
137 ``/`` character in a path string represents that the path is absolute).
139 Resource path strings returned will be escaped in the following
140 manner: each unicode path segment will be encoded as UTF-8 and
141 each path segment will be escaped via Python's :mod:`urllib.quote`.
142 For example, ``/path/to%20the/La%20Pe%C3%B1a``.
144 This function is a logical inverse of
145 :mod:`pyramid.traversal.find_resource`: it can be used to generate
146 path references that can later be resolved via that function.
148 The ``resource`` passed in *must* be :term:`location`-aware.
150 .. note::
152 Each segment in the path string returned will use the ``__name__``
153 attribute of the resource it represents within the resource tree. Each
154 of these segments *should* be a unicode or string object (as per the
155 contract of :term:`location`-awareness). However, no conversion or
156 safety checking of resource names is performed. For instance, if one of
157 the resources in your tree has a ``__name__`` which (by error) is a
158 dictionary, the :func:`pyramid.traversal.resource_path` function will
159 attempt to append it to a string and it will cause a
160 :exc:`pyramid.exceptions.URLDecodeError`.
162 .. note::
164 The :term:`root` resource *must* have a ``__name__`` attribute with a
165 value of either ``None`` or the empty string for paths to be generated
166 properly. If the root resource has a non-null ``__name__`` attribute,
167 its name will be prepended to the generated path rather than a single
168 leading '/' character.
169 """
170 # joining strings is a bit expensive so we delegate to a function
171 # which caches the joined result for us
172 return _join_path_tuple(resource_path_tuple(resource, *elements))
175model_path = resource_path # b/w compat (forever)
178def traverse(resource, path):
179 """Given a resource object as ``resource`` and a string or tuple
180 representing a path as ``path`` (such as the return value of
181 :func:`pyramid.traversal.resource_path` or
182 :func:`pyramid.traversal.resource_path_tuple` or the value of
183 ``request.environ['PATH_INFO']``), return a dictionary with the
184 keys ``context``, ``root``, ``view_name``, ``subpath``,
185 ``traversed``, ``virtual_root``, and ``virtual_root_path``.
187 A definition of each value in the returned dictionary:
189 - ``context``: The :term:`context` (a :term:`resource` object) found
190 via traversal or url dispatch. If the ``path`` passed in is the
191 empty string, the value of the ``resource`` argument passed to this
192 function is returned.
194 - ``root``: The resource object at which :term:`traversal` begins.
195 If the ``resource`` passed in was found via url dispatch or if the
196 ``path`` passed in was relative (non-absolute), the value of the
197 ``resource`` argument passed to this function is returned.
199 - ``view_name``: The :term:`view name` found during
200 :term:`traversal` or :term:`url dispatch`; if the ``resource`` was
201 found via traversal, this is usually a representation of the
202 path segment which directly follows the path to the ``context``
203 in the ``path``. The ``view_name`` will be a Unicode object or
204 the empty string. The ``view_name`` will be the empty string if
205 there is no element which follows the ``context`` path. An
206 example: if the path passed is ``/foo/bar``, and a resource
207 object is found at ``/foo`` (but not at ``/foo/bar``), the 'view
208 name' will be ``u'bar'``. If the ``resource`` was found via
209 urldispatch, the view_name will be the name the route found was
210 registered with.
212 - ``subpath``: For a ``resource`` found via :term:`traversal`, this
213 is a sequence of path segments found in the ``path`` that follow
214 the ``view_name`` (if any). Each of these items is a Unicode
215 object. If no path segments follow the ``view_name``, the
216 subpath will be the empty sequence. An example: if the path
217 passed is ``/foo/bar/baz/buz``, and a resource object is found at
218 ``/foo`` (but not ``/foo/bar``), the 'view name' will be
219 ``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``.
220 For a ``resource`` found via url dispatch, the subpath will be a
221 sequence of values discerned from ``*subpath`` in the route
222 pattern matched or the empty sequence.
224 - ``traversed``: The sequence of path elements traversed from the
225 root to find the ``context`` object during :term:`traversal`.
226 Each of these items is a Unicode object. If no path segments
227 were traversed to find the ``context`` object (e.g. if the
228 ``path`` provided is the empty string), the ``traversed`` value
229 will be the empty sequence. If the ``resource`` is a resource found
230 via :term:`url dispatch`, traversed will be None.
232 - ``virtual_root``: A resource object representing the 'virtual' root
233 of the resource tree being traversed during :term:`traversal`.
234 See :ref:`vhosting_chapter` for a definition of the virtual root
235 object. If no virtual hosting is in effect, and the ``path``
236 passed in was absolute, the ``virtual_root`` will be the
237 *physical* root resource object (the object at which :term:`traversal`
238 begins). If the ``resource`` passed in was found via :term:`URL
239 dispatch` or if the ``path`` passed in was relative, the
240 ``virtual_root`` will always equal the ``root`` object (the
241 resource passed in).
243 - ``virtual_root_path`` -- If :term:`traversal` was used to find
244 the ``resource``, this will be the sequence of path elements
245 traversed to find the ``virtual_root`` resource. Each of these
246 items is a Unicode object. If no path segments were traversed
247 to find the ``virtual_root`` resource (e.g. if virtual hosting is
248 not in effect), the ``traversed`` value will be the empty list.
249 If url dispatch was used to find the ``resource``, this will be
250 ``None``.
252 If the path cannot be resolved, a :exc:`KeyError` will be raised.
254 Rules for passing a *string* as the ``path`` argument: if the
255 first character in the path string is the with the ``/``
256 character, the path will considered absolute and the resource tree
257 traversal will start at the root resource. If the first character
258 of the path string is *not* the ``/`` character, the path is
259 considered relative and resource tree traversal will begin at the resource
260 object supplied to the function as the ``resource`` argument. If an
261 empty string is passed as ``path``, the ``resource`` passed in will
262 be returned. Resource path strings must be escaped in the following
263 manner: each Unicode path segment must be encoded as UTF-8 and
264 each path segment must escaped via Python's :mod:`urllib.quote`.
265 For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
266 ``to%20the/La%20Pe%C3%B1a`` (relative). The
267 :func:`pyramid.traversal.resource_path` function generates strings
268 which follow these rules (albeit only absolute ones).
270 Rules for passing a *tuple* as the ``path`` argument: if the first
271 element in the path tuple is the empty string (for example ``('',
272 'a', 'b', 'c')``, the path is considered absolute and the resource tree
273 traversal will start at the resource tree root object. If the first
274 element in the path tuple is not the empty string (for example
275 ``('a', 'b', 'c')``), the path is considered relative and resource tree
276 traversal will begin at the resource object supplied to the function
277 as the ``resource`` argument. If an empty sequence is passed as
278 ``path``, the ``resource`` passed in itself will be returned. No
279 URL-quoting or UTF-8-encoding of individual path segments within
280 the tuple is required (each segment may be any string or unicode
281 object representing a resource name).
283 Explanation of the conversion of ``path`` segment values to
284 Unicode during traversal: Each segment is URL-unquoted, and
285 decoded into Unicode. Each segment is assumed to be encoded using
286 the UTF-8 encoding (or a subset, such as ASCII); a
287 :exc:`pyramid.exceptions.URLDecodeError` is raised if a segment
288 cannot be decoded. If a segment name is empty or if it is ``.``,
289 it is ignored. If a segment name is ``..``, the previous segment
290 is deleted, and the ``..`` is ignored. As a result of this
291 process, the return values ``view_name``, each element in the
292 ``subpath``, each element in ``traversed``, and each element in
293 the ``virtual_root_path`` will be Unicode as opposed to a string,
294 and will be URL-decoded.
295 """
297 if is_nonstr_iter(path):
298 # the traverser factory expects PATH_INFO to be a string, not
299 # unicode and it expects path segments to be utf-8 and
300 # urlencoded (it's the same traverser which accepts PATH_INFO
301 # from user agents; user agents always send strings).
302 if path:
303 path = _join_path_tuple(tuple(path))
304 else:
305 path = ''
307 # The user is supposed to pass us a string object, never Unicode. In
308 # practice, however, users indeed pass Unicode to this API. If they do
309 # pass a Unicode object, its data *must* be entirely encodeable to ASCII,
310 # so we encode it here as a convenience to the user and to prevent
311 # second-order failures from cropping up (all failures will occur at this
312 # step rather than later down the line as the result of calling
313 # ``traversal_path``).
315 path = ascii_native_(path)
317 if path and path[0] == '/':
318 resource = find_root(resource)
320 reg = get_current_registry()
322 request_factory = reg.queryUtility(IRequestFactory)
323 if request_factory is None:
324 from pyramid.request import Request # avoid circdep
326 request_factory = Request
328 request = request_factory.blank(path)
329 request.registry = reg
330 traverser = reg.queryAdapter(resource, ITraverser)
331 if traverser is None:
332 traverser = ResourceTreeTraverser(resource)
334 return traverser(request)
337def resource_path_tuple(resource, *elements):
338 """
339 Return a tuple representing the absolute physical path of the
340 ``resource`` object based on its position in a resource tree, e.g
341 ``('', 'foo', 'bar')``. Any positional arguments passed in as
342 ``elements`` will be appended as elements in the tuple
343 representing the resource path. For instance, if the resource's
344 path is ``('', 'foo', 'bar')`` and elements equals ``('a', 'b')``,
345 the returned tuple will be ``('', 'foo', 'bar', 'a', 'b')``. The
346 first element of this tuple will always be the empty string (a
347 leading empty string element in a path tuple represents that the
348 path is absolute).
350 This function is a logical inverse of
351 :func:`pyramid.traversal.find_resource`: it can be used to
352 generate path references that can later be resolved by that function.
354 The ``resource`` passed in *must* be :term:`location`-aware.
356 .. note::
358 Each segment in the path tuple returned will equal the ``__name__``
359 attribute of the resource it represents within the resource tree. Each
360 of these segments *should* be a unicode or string object (as per the
361 contract of :term:`location`-awareness). However, no conversion or
362 safety checking of resource names is performed. For instance, if one of
363 the resources in your tree has a ``__name__`` which (by error) is a
364 dictionary, that dictionary will be placed in the path tuple; no warning
365 or error will be given.
367 .. note::
369 The :term:`root` resource *must* have a ``__name__`` attribute with a
370 value of either ``None`` or the empty string for path tuples to be
371 generated properly. If the root resource has a non-null ``__name__``
372 attribute, its name will be the first element in the generated path
373 tuple rather than the empty string.
374 """
375 return tuple(_resource_path_list(resource, *elements))
378model_path_tuple = resource_path_tuple # b/w compat (forever)
381def _resource_path_list(resource, *elements):
382 """ Implementation detail shared by resource_path and
383 resource_path_tuple"""
384 path = [loc.__name__ or '' for loc in lineage(resource)]
385 path.reverse()
386 path.extend(elements)
387 return path
390_model_path_list = _resource_path_list # b/w compat, not an API
393def virtual_root(resource, request):
394 """
395 Provided any :term:`resource` and a :term:`request` object, return
396 the resource object representing the :term:`virtual root` of the
397 current :term:`request`. Using a virtual root in a
398 :term:`traversal` -based :app:`Pyramid` application permits
399 rooting. For example, the resource at the traversal path ``/cms`` will
400 be found at ``http://example.com/`` instead of rooting it at
401 ``http://example.com/cms/``.
403 If the ``resource`` passed in is a context obtained via
404 :term:`traversal`, and if the ``HTTP_X_VHM_ROOT`` key is in the
405 WSGI environment, the value of this key will be treated as a
406 'virtual root path': the :func:`pyramid.traversal.find_resource`
407 API will be used to find the virtual root resource using this path;
408 if the resource is found, it will be returned. If the
409 ``HTTP_X_VHM_ROOT`` key is not present in the WSGI environment,
410 the physical :term:`root` of the resource tree will be returned instead.
412 Virtual roots are not useful at all in applications that use
413 :term:`URL dispatch`. Contexts obtained via URL dispatch don't
414 really support being virtually rooted (each URL dispatch context
415 is both its own physical and virtual root). However if this API
416 is called with a ``resource`` argument which is a context obtained
417 via URL dispatch, the resource passed in will be returned
418 unconditionally."""
419 try:
420 reg = request.registry
421 except AttributeError:
422 reg = get_current_registry()
423 url_adapter = reg.queryMultiAdapter((resource, request), IResourceURL)
424 if url_adapter is None:
425 url_adapter = ResourceURL(resource, request)
427 vpath, rpath = url_adapter.virtual_path, url_adapter.physical_path
428 if rpath != vpath and rpath.endswith(vpath):
429 vroot_path = rpath[: -len(vpath)]
430 return find_resource(resource, vroot_path)
432 try:
433 return request.root
434 except AttributeError:
435 return find_root(resource)
438def traversal_path(path):
439 """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for
440 decoding paths that are URL-encoded.
442 If this function is passed a Unicode object instead of a sequence of
443 bytes as ``path``, that Unicode object *must* directly encodeable to
444 ASCII. For example, u'/foo' will work but u'/<unprintable unicode>' (a
445 Unicode object with characters that cannot be encoded to ascii) will
446 not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be
447 encoded directly to ASCII.
448 """
449 if isinstance(path, text_type):
450 # must not possess characters outside ascii
451 path = path.encode('ascii')
452 # we unquote this path exactly like a PEP 3333 server would
453 path = unquote_bytes_to_wsgi(path) # result will be a native string
454 return traversal_path_info(path) # result will be a tuple of unicode
457@lru_cache(1000)
458def traversal_path_info(path):
459 """ Given``path``, return a tuple representing that path which can be
460 used to traverse a resource tree. ``path`` is assumed to be an
461 already-URL-decoded ``str`` type as if it had come to us from an upstream
462 WSGI server as the ``PATH_INFO`` environ variable.
464 The ``path`` is first decoded to from its WSGI representation to Unicode;
465 it is decoded differently depending on platform:
467 - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8
468 decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the
469 URL cannot be decoded.
471 - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to
472 bytes using the Latin-1 encoding; the resulting set of bytes is
473 subsequently decoded to text using the UTF-8 encoding; a
474 :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be
475 decoded.
477 The ``path`` is split on slashes, creating a list of segments. If a
478 segment name is empty or if it is ``.``, it is ignored. If a segment
479 name is ``..``, the previous segment is deleted, and the ``..`` is
480 ignored.
482 Examples:
484 ``/``
486 ()
488 ``/foo/bar/baz``
490 (u'foo', u'bar', u'baz')
492 ``foo/bar/baz``
494 (u'foo', u'bar', u'baz')
496 ``/foo/bar/baz/``
498 (u'foo', u'bar', u'baz')
500 ``/foo//bar//baz/``
502 (u'foo', u'bar', u'baz')
504 ``/foo/bar/baz/..``
506 (u'foo', u'bar')
508 ``/my%20archives/hello``
510 (u'my archives', u'hello')
512 ``/archives/La%20Pe%C3%B1a``
514 (u'archives', u'<unprintable unicode>')
516 .. note::
518 This function does not generate the same type of tuples that
519 :func:`pyramid.traversal.resource_path_tuple` does. In particular, the
520 leading empty string is not present in the tuple it returns, unlike
521 tuples returned by :func:`pyramid.traversal.resource_path_tuple`. As a
522 result, tuples generated by ``traversal_path`` are not resolveable by
523 the :func:`pyramid.traversal.find_resource` API. ``traversal_path`` is
524 a function mostly used by the internals of :app:`Pyramid` and by people
525 writing their own traversal machinery, as opposed to users writing
526 applications in :app:`Pyramid`.
527 """
528 try:
529 path = decode_path_info(path) # result will be Unicode
530 except UnicodeDecodeError as e:
531 raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
532 return split_path_info(path) # result will be tuple of Unicode
535@lru_cache(1000)
536def split_path_info(path):
537 # suitable for splitting an already-unquoted-already-decoded (unicode)
538 # path value
539 path = path.strip('/')
540 clean = []
541 for segment in path.split('/'):
542 if not segment or segment == '.':
543 continue
544 elif segment == '..':
545 if clean:
546 del clean[-1]
547 else:
548 clean.append(segment)
549 return tuple(clean)
552_segment_cache = {}
554quote_path_segment_doc = """ \
555Return a quoted representation of a 'path segment' (such as
556the string ``__name__`` attribute of a resource) as a string. If the
557``segment`` passed in is a unicode object, it is converted to a
558UTF-8 string, then it is URL-quoted using Python's
559``urllib.quote``. If the ``segment`` passed in is a string, it is
560URL-quoted using Python's :mod:`urllib.quote`. If the segment
561passed in is not a string or unicode object, an error will be
562raised. The return value of ``quote_path_segment`` is always a
563string, never Unicode.
565You may pass a string of characters that need not be encoded as
566the ``safe`` argument to this function. This corresponds to the
567``safe`` argument to :mod:`urllib.quote`.
569.. note::
571 The return value for each segment passed to this
572 function is cached in a module-scope dictionary for
573 speed: the cached version is returned when possible
574 rather than recomputing the quoted version. No cache
575 emptying is ever done for the lifetime of an
576 application, however. If you pass arbitrary
577 user-supplied strings to this function (as opposed to
578 some bounded set of values from a 'working set' known to
579 your application), it may become a memory leak.
580"""
583if PY2:
584 # special-case on Python 2 for speed? unchecked
585 def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE):
586 """ %s """ % quote_path_segment_doc
587 # The bit of this code that deals with ``_segment_cache`` is an
588 # optimization: we cache all the computation of URL path segments
589 # in this module-scope dictionary with the original string (or
590 # unicode value) as the key, so we can look it up later without
591 # needing to reencode or re-url-quote it
592 try:
593 return _segment_cache[(segment, safe)]
594 except KeyError:
595 if (
596 segment.__class__ is text_type
597 ): # isinstance slighly slower (~15%)
598 result = url_quote(segment.encode('utf-8'), safe)
599 else:
600 result = url_quote(str(segment), safe)
601 # we don't need a lock to mutate _segment_cache, as the below
602 # will generate exactly one Python bytecode (STORE_SUBSCR)
603 _segment_cache[(segment, safe)] = result
604 return result
607else:
609 def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE):
610 """ %s """ % quote_path_segment_doc
611 # The bit of this code that deals with ``_segment_cache`` is an
612 # optimization: we cache all the computation of URL path segments
613 # in this module-scope dictionary with the original string (or
614 # unicode value) as the key, so we can look it up later without
615 # needing to reencode or re-url-quote it
616 try:
617 return _segment_cache[(segment, safe)]
618 except KeyError:
619 if segment.__class__ not in (text_type, binary_type):
620 segment = str(segment)
621 result = url_quote(native_(segment, 'utf-8'), safe)
622 # we don't need a lock to mutate _segment_cache, as the below
623 # will generate exactly one Python bytecode (STORE_SUBSCR)
624 _segment_cache[(segment, safe)] = result
625 return result
628slash = text_('/')
631@implementer(ITraverser)
632class ResourceTreeTraverser(object):
633 """ A resource tree traverser that should be used (for speed) when
634 every resource in the tree supplies a ``__name__`` and
635 ``__parent__`` attribute (ie. every resource in the tree is
636 :term:`location` aware) ."""
638 VH_ROOT_KEY = VH_ROOT_KEY
639 VIEW_SELECTOR = '@@'
641 def __init__(self, root):
642 self.root = root
644 def __call__(self, request):
645 environ = request.environ
646 matchdict = request.matchdict
648 if matchdict is not None:
650 path = matchdict.get('traverse', slash) or slash
651 if is_nonstr_iter(path):
652 # this is a *traverse stararg (not a {traverse})
653 # routing has already decoded these elements, so we just
654 # need to join them
655 path = '/' + slash.join(path) or slash
657 subpath = matchdict.get('subpath', ())
658 if not is_nonstr_iter(subpath):
659 # this is not a *subpath stararg (just a {subpath})
660 # routing has already decoded this string, so we just need
661 # to split it
662 subpath = split_path_info(subpath)
664 else:
665 # this request did not match a route
666 subpath = ()
667 try:
668 # empty if mounted under a path in mod_wsgi, for example
669 path = request.path_info or slash
670 except KeyError:
671 # if environ['PATH_INFO'] is just not there
672 path = slash
673 except UnicodeDecodeError as e:
674 raise URLDecodeError(
675 e.encoding, e.object, e.start, e.end, e.reason
676 )
678 if self.VH_ROOT_KEY in environ:
679 # HTTP_X_VHM_ROOT
680 vroot_path = decode_path_info(environ[self.VH_ROOT_KEY])
681 vroot_tuple = split_path_info(vroot_path)
682 vpath = (
683 vroot_path + path
684 ) # both will (must) be unicode or asciistr
685 vroot_idx = len(vroot_tuple) - 1
686 else:
687 vroot_tuple = ()
688 vpath = path
689 vroot_idx = -1
691 root = self.root
692 ob = vroot = root
694 if vpath == slash: # invariant: vpath must not be empty
695 # prevent a call to traversal_path if we know it's going
696 # to return the empty tuple
697 vpath_tuple = ()
698 else:
699 # we do dead reckoning here via tuple slicing instead of
700 # pushing and popping temporary lists for speed purposes
701 # and this hurts readability; apologies
702 i = 0
703 view_selector = self.VIEW_SELECTOR
704 vpath_tuple = split_path_info(vpath)
705 for segment in vpath_tuple:
706 if segment[:2] == view_selector:
707 return {
708 'context': ob,
709 'view_name': segment[2:],
710 'subpath': vpath_tuple[i + 1 :],
711 'traversed': vpath_tuple[: vroot_idx + i + 1],
712 'virtual_root': vroot,
713 'virtual_root_path': vroot_tuple,
714 'root': root,
715 }
716 try:
717 getitem = ob.__getitem__
718 except AttributeError:
719 return {
720 'context': ob,
721 'view_name': segment,
722 'subpath': vpath_tuple[i + 1 :],
723 'traversed': vpath_tuple[: vroot_idx + i + 1],
724 'virtual_root': vroot,
725 'virtual_root_path': vroot_tuple,
726 'root': root,
727 }
729 try:
730 next = getitem(segment)
731 except KeyError:
732 return {
733 'context': ob,
734 'view_name': segment,
735 'subpath': vpath_tuple[i + 1 :],
736 'traversed': vpath_tuple[: vroot_idx + i + 1],
737 'virtual_root': vroot,
738 'virtual_root_path': vroot_tuple,
739 'root': root,
740 }
741 if i == vroot_idx:
742 vroot = next
743 ob = next
744 i += 1
746 return {
747 'context': ob,
748 'view_name': empty,
749 'subpath': subpath,
750 'traversed': vpath_tuple,
751 'virtual_root': vroot,
752 'virtual_root_path': vroot_tuple,
753 'root': root,
754 }
757ModelGraphTraverser = (
758 ResourceTreeTraverser
759) # b/w compat, not API, used in wild
762@implementer(IResourceURL)
763class ResourceURL(object):
764 VH_ROOT_KEY = VH_ROOT_KEY
766 def __init__(self, resource, request):
767 physical_path_tuple = resource_path_tuple(resource)
768 physical_path = _join_path_tuple(physical_path_tuple)
770 if physical_path_tuple != ('',):
771 physical_path_tuple = physical_path_tuple + ('',)
772 physical_path = physical_path + '/'
774 virtual_path = physical_path
775 virtual_path_tuple = physical_path_tuple
777 environ = request.environ
778 vroot_path = environ.get(self.VH_ROOT_KEY)
780 # if the physical path starts with the virtual root path, trim it out
781 # of the virtual path
782 if vroot_path is not None:
783 vroot_path = vroot_path.rstrip('/')
784 if vroot_path and physical_path.startswith(vroot_path):
785 vroot_path_tuple = tuple(vroot_path.split('/'))
786 numels = len(vroot_path_tuple)
787 virtual_path_tuple = ('',) + physical_path_tuple[numels:]
788 virtual_path = physical_path[len(vroot_path) :]
790 self.virtual_path = virtual_path # IResourceURL attr
791 self.physical_path = physical_path # IResourceURL attr
792 self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5)
793 self.physical_path_tuple = (
794 physical_path_tuple
795 ) # IResourceURL attr (1.5)
798@lru_cache(1000)
799def _join_path_tuple(tuple):
800 return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/'
803class DefaultRootFactory:
804 __parent__ = None
805 __name__ = None
807 def __init__(self, request):
808 pass