commit | author | age
|
3b7334
|
1 |
from zope.interface import implementer |
0f5d8b
|
2 |
from zope.interface.interfaces import IInterface |
77e32d
|
3 |
|
0c1c39
|
4 |
from pyramid.interfaces import ( |
c51896
|
5 |
IResourceURL, |
0c1c39
|
6 |
IRequestFactory, |
CM |
7 |
ITraverser, |
|
8 |
VH_ROOT_KEY, |
0c29cf
|
9 |
) |
e6e0ea
|
10 |
|
0c1c39
|
11 |
from pyramid.compat import ( |
bc37a5
|
12 |
PY2, |
0c1c39
|
13 |
native_, |
CM |
14 |
text_, |
|
15 |
ascii_native_, |
|
16 |
text_type, |
|
17 |
binary_type, |
|
18 |
is_nonstr_iter, |
5c43d5
|
19 |
decode_path_info, |
CM |
20 |
unquote_bytes_to_wsgi, |
e80491
|
21 |
lru_cache, |
0c29cf
|
22 |
) |
0c1c39
|
23 |
|
b60bdb
|
24 |
from pyramid.encode import url_quote |
CM |
25 |
from pyramid.exceptions import URLDecodeError |
|
26 |
from pyramid.location import lineage |
|
27 |
from pyramid.threadlocal import get_current_registry |
e6c2d2
|
28 |
|
0c29cf
|
29 |
PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob |
f52759
|
30 |
PATH_SAFE = PATH_SEGMENT_SAFE + "/" |
MK |
31 |
|
e6c2d2
|
32 |
empty = text_('') |
0c29cf
|
33 |
|
423b85
|
34 |
|
fb6a5c
|
35 |
def find_root(resource): |
bac5b3
|
36 |
""" Find the root node in the resource tree to which ``resource`` |
fb6a5c
|
37 |
belongs. Note that ``resource`` should be :term:`location`-aware. |
CM |
38 |
Note that the root resource is available in the request object by |
67b8a1
|
39 |
accessing the ``request.root`` attribute. |
FCN |
40 |
""" |
fb6a5c
|
41 |
for location in lineage(resource): |
cc5e49
|
42 |
if location.__parent__ is None: |
fb6a5c
|
43 |
resource = location |
cc5e49
|
44 |
break |
fb6a5c
|
45 |
return resource |
0c29cf
|
46 |
|
cc5e49
|
47 |
|
fb6a5c
|
48 |
def find_resource(resource, path): |
CM |
49 |
""" Given a resource object and a string or tuple representing a path |
bac5b3
|
50 |
(such as the return value of :func:`pyramid.traversal.resource_path` or |
CM |
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. |
a97a70
|
56 |
|
c6895b
|
57 |
This function is the logical inverse of |
fb6a5c
|
58 |
:func:`pyramid.traversal.resource_path` and |
CM |
59 |
:func:`pyramid.traversal.resource_path_tuple`; it can resolve any |
c6895b
|
60 |
path string or tuple generated by either of those functions. |
a97a70
|
61 |
|
CM |
62 |
Rules for passing a *string* as the ``path`` argument: if the |
eaf1b4
|
63 |
first character in the path string is the ``/`` |
CDLG |
64 |
character, the path is considered absolute and the resource tree |
b32bfd
|
65 |
traversal will start at the root resource. If the first character |
a97a70
|
66 |
of the path string is *not* the ``/`` character, the path is |
bac5b3
|
67 |
considered relative and resource tree traversal will begin at the resource |
fb6a5c
|
68 |
object supplied to the function as the ``resource`` argument. If an |
CM |
69 |
empty string is passed as ``path``, the ``resource`` passed in will |
|
70 |
be returned. Resource path strings must be escaped in the following |
a97a70
|
71 |
manner: each Unicode path segment must be encoded as UTF-8 and as |
c6895b
|
72 |
each path segment must escaped via Python's :mod:`urllib.quote`. |
CM |
73 |
For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or |
|
74 |
``to%20the/La%20Pe%C3%B1a`` (relative). The |
fb6a5c
|
75 |
:func:`pyramid.traversal.resource_path` function generates strings |
c6895b
|
76 |
which follow these rules (albeit only absolute ones). |
a97a70
|
77 |
|
f84147
|
78 |
Rules for passing *text* (Unicode) as the ``path`` argument are the same |
CM |
79 |
as those for a string. In particular, the text may not have any nonascii |
|
80 |
characters in it. |
|
81 |
|
a97a70
|
82 |
Rules for passing a *tuple* as the ``path`` argument: if the first |
CM |
83 |
element in the path tuple is the empty string (for example ``('', |
bac5b3
|
84 |
'a', 'b', 'c')``, the path is considered absolute and the resource tree |
CM |
85 |
traversal will start at the resource tree root object. If the first |
a97a70
|
86 |
element in the path tuple is not the empty string (for example |
bac5b3
|
87 |
``('a', 'b', 'c')``), the path is considered relative and resource tree |
fb6a5c
|
88 |
traversal will begin at the resource object supplied to the function |
CM |
89 |
as the ``resource`` argument. If an empty sequence is passed as |
|
90 |
``path``, the ``resource`` passed in itself will be returned. No |
dbab77
|
91 |
URL-quoting or UTF-8-encoding of individual path segments within |
CM |
92 |
the tuple is required (each segment may be any string or unicode |
fb6a5c
|
93 |
object representing a resource name). Resource path tuples generated by |
CM |
94 |
:func:`pyramid.traversal.resource_path_tuple` can always be |
|
95 |
resolved by ``find_resource``. |
dbab77
|
96 |
""" |
f84147
|
97 |
if isinstance(path, text_type): |
CM |
98 |
path = ascii_native_(path) |
fb6a5c
|
99 |
D = traverse(resource, path) |
916f88
|
100 |
view_name = D['view_name'] |
CM |
101 |
context = D['context'] |
|
102 |
if view_name: |
|
103 |
raise KeyError('%r has no subelement %s' % (context, view_name)) |
|
104 |
return context |
cc5e49
|
105 |
|
0c29cf
|
106 |
|
MM |
107 |
find_model = find_resource # b/w compat (forever) |
|
108 |
|
fb6a5c
|
109 |
|
CM |
110 |
def find_interface(resource, class_or_interface): |
7ae0c2
|
111 |
""" |
b32bfd
|
112 |
Return the first resource found in the :term:`lineage` of ``resource`` |
CM |
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. |
7ae0c2
|
119 |
""" |
0f5d8b
|
120 |
if IInterface.providedBy(class_or_interface): |
CM |
121 |
test = class_or_interface.providedBy |
|
122 |
else: |
|
123 |
test = lambda arg: isinstance(arg, class_or_interface) |
fb6a5c
|
124 |
for location in lineage(resource): |
0f5d8b
|
125 |
if test(location): |
bb5d64
|
126 |
return location |
0c29cf
|
127 |
|
35ff8e
|
128 |
|
fb6a5c
|
129 |
def resource_path(resource, *elements): |
CM |
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). |
a97a70
|
138 |
|
fb6a5c
|
139 |
Resource path strings returned will be escaped in the following |
358821
|
140 |
manner: each unicode path segment will be encoded as UTF-8 and |
c6895b
|
141 |
each path segment will be escaped via Python's :mod:`urllib.quote`. |
a97a70
|
142 |
For example, ``/path/to%20the/La%20Pe%C3%B1a``. |
CM |
143 |
|
c6895b
|
144 |
This function is a logical inverse of |
fb6a5c
|
145 |
:mod:`pyramid.traversal.find_resource`: it can be used to generate |
c6895b
|
146 |
path references that can later be resolved via that function. |
a97a70
|
147 |
|
fb6a5c
|
148 |
The ``resource`` passed in *must* be :term:`location`-aware. |
a97a70
|
149 |
|
012b97
|
150 |
.. note:: |
8e037f
|
151 |
|
012b97
|
152 |
Each segment in the path string returned will use the ``__name__`` |
M |
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`. |
92c3e5
|
161 |
|
012b97
|
162 |
.. note:: |
M |
163 |
|
|
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. |
a97a70
|
169 |
""" |
eb9fbf
|
170 |
# joining strings is a bit expensive so we delegate to a function |
CM |
171 |
# which caches the joined result for us |
fb6a5c
|
172 |
return _join_path_tuple(resource_path_tuple(resource, *elements)) |
eb9fbf
|
173 |
|
0c29cf
|
174 |
|
MM |
175 |
model_path = resource_path # b/w compat (forever) |
|
176 |
|
fb6a5c
|
177 |
|
CM |
178 |
def traverse(resource, path): |
|
179 |
"""Given a resource object as ``resource`` and a string or tuple |
916f88
|
180 |
representing a path as ``path`` (such as the return value of |
fb6a5c
|
181 |
:func:`pyramid.traversal.resource_path` or |
CM |
182 |
:func:`pyramid.traversal.resource_path_tuple` or the value of |
916f88
|
183 |
``request.environ['PATH_INFO']``), return a dictionary with the |
CM |
184 |
keys ``context``, ``root``, ``view_name``, ``subpath``, |
|
185 |
``traversed``, ``virtual_root``, and ``virtual_root_path``. |
|
186 |
|
|
187 |
A definition of each value in the returned dictionary: |
|
188 |
|
fb6a5c
|
189 |
- ``context``: The :term:`context` (a :term:`resource` object) found |
8b1f6e
|
190 |
via traversal or url dispatch. If the ``path`` passed in is the |
fb6a5c
|
191 |
empty string, the value of the ``resource`` argument passed to this |
8b1f6e
|
192 |
function is returned. |
916f88
|
193 |
|
fb6a5c
|
194 |
- ``root``: The resource object at which :term:`traversal` begins. |
CM |
195 |
If the ``resource`` passed in was found via url dispatch or if the |
7f1243
|
196 |
``path`` passed in was relative (non-absolute), the value of the |
fb6a5c
|
197 |
``resource`` argument passed to this function is returned. |
916f88
|
198 |
|
8b1f6e
|
199 |
- ``view_name``: The :term:`view name` found during |
fb6a5c
|
200 |
:term:`traversal` or :term:`url dispatch`; if the ``resource`` was |
8b1f6e
|
201 |
found via traversal, this is usually a representation of the |
CM |
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 |
b32bfd
|
206 |
example: if the path passed is ``/foo/bar``, and a resource |
8b1f6e
|
207 |
object is found at ``/foo`` (but not at ``/foo/bar``), the 'view |
fb6a5c
|
208 |
name' will be ``u'bar'``. If the ``resource`` was found via |
8b1f6e
|
209 |
urldispatch, the view_name will be the name the route found was |
CM |
210 |
registered with. |
916f88
|
211 |
|
fb6a5c
|
212 |
- ``subpath``: For a ``resource`` found via :term:`traversal`, this |
8b1f6e
|
213 |
is a sequence of path segments found in the ``path`` that follow |
CM |
214 |
the ``view_name`` (if any). Each of these items is a Unicode |
916f88
|
215 |
object. If no path segments follow the ``view_name``, the |
8b1f6e
|
216 |
subpath will be the empty sequence. An example: if the path |
b32bfd
|
217 |
passed is ``/foo/bar/baz/buz``, and a resource object is found at |
916f88
|
218 |
``/foo`` (but not ``/foo/bar``), the 'view name' will be |
8b1f6e
|
219 |
``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``. |
fb6a5c
|
220 |
For a ``resource`` found via url dispatch, the subpath will be a |
8b1f6e
|
221 |
sequence of values discerned from ``*subpath`` in the route |
CM |
222 |
pattern matched or the empty sequence. |
916f88
|
223 |
|
7f1243
|
224 |
- ``traversed``: The sequence of path elements traversed from the |
8b1f6e
|
225 |
root to find the ``context`` object during :term:`traversal`. |
CM |
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 |
fb6a5c
|
229 |
will be the empty sequence. If the ``resource`` is a resource found |
8b1f6e
|
230 |
via :term:`url dispatch`, traversed will be None. |
916f88
|
231 |
|
fb6a5c
|
232 |
- ``virtual_root``: A resource object representing the 'virtual' root |
bac5b3
|
233 |
of the resource tree being traversed during :term:`traversal`. |
8b1f6e
|
234 |
See :ref:`vhosting_chapter` for a definition of the virtual root |
7f1243
|
235 |
object. If no virtual hosting is in effect, and the ``path`` |
CM |
236 |
passed in was absolute, the ``virtual_root`` will be the |
b32bfd
|
237 |
*physical* root resource object (the object at which :term:`traversal` |
fb6a5c
|
238 |
begins). If the ``resource`` passed in was found via :term:`URL |
8b1f6e
|
239 |
dispatch` or if the ``path`` passed in was relative, the |
CM |
240 |
``virtual_root`` will always equal the ``root`` object (the |
fb6a5c
|
241 |
resource passed in). |
916f88
|
242 |
|
8b1f6e
|
243 |
- ``virtual_root_path`` -- If :term:`traversal` was used to find |
fb6a5c
|
244 |
the ``resource``, this will be the sequence of path elements |
b32bfd
|
245 |
traversed to find the ``virtual_root`` resource. Each of these |
8b1f6e
|
246 |
items is a Unicode object. If no path segments were traversed |
b32bfd
|
247 |
to find the ``virtual_root`` resource (e.g. if virtual hosting is |
8b1f6e
|
248 |
not in effect), the ``traversed`` value will be the empty list. |
fb6a5c
|
249 |
If url dispatch was used to find the ``resource``, this will be |
c6895b
|
250 |
``None``. |
916f88
|
251 |
|
c6895b
|
252 |
If the path cannot be resolved, a :exc:`KeyError` will be raised. |
916f88
|
253 |
|
CM |
254 |
Rules for passing a *string* as the ``path`` argument: if the |
|
255 |
first character in the path string is the with the ``/`` |
bac5b3
|
256 |
character, the path will considered absolute and the resource tree |
b32bfd
|
257 |
traversal will start at the root resource. If the first character |
916f88
|
258 |
of the path string is *not* the ``/`` character, the path is |
bac5b3
|
259 |
considered relative and resource tree traversal will begin at the resource |
fb6a5c
|
260 |
object supplied to the function as the ``resource`` argument. If an |
CM |
261 |
empty string is passed as ``path``, the ``resource`` passed in will |
|
262 |
be returned. Resource path strings must be escaped in the following |
a66593
|
263 |
manner: each Unicode path segment must be encoded as UTF-8 and |
c6895b
|
264 |
each path segment must escaped via Python's :mod:`urllib.quote`. |
CM |
265 |
For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or |
|
266 |
``to%20the/La%20Pe%C3%B1a`` (relative). The |
fb6a5c
|
267 |
:func:`pyramid.traversal.resource_path` function generates strings |
c6895b
|
268 |
which follow these rules (albeit only absolute ones). |
916f88
|
269 |
|
CM |
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 ``('', |
bac5b3
|
272 |
'a', 'b', 'c')``, the path is considered absolute and the resource tree |
CM |
273 |
traversal will start at the resource tree root object. If the first |
916f88
|
274 |
element in the path tuple is not the empty string (for example |
bac5b3
|
275 |
``('a', 'b', 'c')``), the path is considered relative and resource tree |
fb6a5c
|
276 |
traversal will begin at the resource object supplied to the function |
CM |
277 |
as the ``resource`` argument. If an empty sequence is passed as |
|
278 |
``path``, the ``resource`` passed in itself will be returned. No |
916f88
|
279 |
URL-quoting or UTF-8-encoding of individual path segments within |
CM |
280 |
the tuple is required (each segment may be any string or unicode |
fb6a5c
|
281 |
object representing a resource name). |
916f88
|
282 |
|
CM |
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 |
c6895b
|
286 |
the UTF-8 encoding (or a subset, such as ASCII); a |
c81aad
|
287 |
:exc:`pyramid.exceptions.URLDecodeError` is raised if a segment |
0482bd
|
288 |
cannot be decoded. If a segment name is empty or if it is ``.``, |
CM |
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. |
916f88
|
295 |
""" |
487651
|
296 |
|
8e606d
|
297 |
if is_nonstr_iter(path): |
916f88
|
298 |
# the traverser factory expects PATH_INFO to be a string, not |
CM |
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: |
391afe
|
303 |
path = _join_path_tuple(tuple(path)) |
916f88
|
304 |
else: |
CM |
305 |
path = '' |
|
306 |
|
a66593
|
307 |
# The user is supposed to pass us a string object, never Unicode. In |
CM |
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``). |
|
314 |
|
f84147
|
315 |
path = ascii_native_(path) |
a66593
|
316 |
|
916f88
|
317 |
if path and path[0] == '/': |
fb6a5c
|
318 |
resource = find_root(resource) |
916f88
|
319 |
|
6fec21
|
320 |
reg = get_current_registry() |
012b97
|
321 |
|
4eafaa
|
322 |
request_factory = reg.queryUtility(IRequestFactory) |
CM |
323 |
if request_factory is None: |
0c29cf
|
324 |
from pyramid.request import Request # avoid circdep |
MM |
325 |
|
4eafaa
|
326 |
request_factory = Request |
CM |
327 |
|
81a833
|
328 |
request = request_factory.blank(path) |
6fec21
|
329 |
request.registry = reg |
fb6a5c
|
330 |
traverser = reg.queryAdapter(resource, ITraverser) |
0a0ffe
|
331 |
if traverser is None: |
fb6a5c
|
332 |
traverser = ResourceTreeTraverser(resource) |
5e829c
|
333 |
|
acc776
|
334 |
return traverser(request) |
0c29cf
|
335 |
|
a97a70
|
336 |
|
fb6a5c
|
337 |
def resource_path_tuple(resource, *elements): |
eec9cf
|
338 |
""" |
CM |
339 |
Return a tuple representing the absolute physical path of the |
bac5b3
|
340 |
``resource`` object based on its position in a resource tree, e.g |
8b1f6e
|
341 |
``('', 'foo', 'bar')``. Any positional arguments passed in as |
dbab77
|
342 |
``elements`` will be appended as elements in the tuple |
fb6a5c
|
343 |
representing the resource path. For instance, if the resource's |
dbab77
|
344 |
path is ``('', 'foo', 'bar')`` and elements equals ``('a', 'b')``, |
f8d20a
|
345 |
the returned tuple will be ``('', 'foo', 'bar', 'a', 'b')``. The |
a97a70
|
346 |
first element of this tuple will always be the empty string (a |
CM |
347 |
leading empty string element in a path tuple represents that the |
|
348 |
path is absolute). |
|
349 |
|
c6895b
|
350 |
This function is a logical inverse of |
fb6a5c
|
351 |
:func:`pyramid.traversal.find_resource`: it can be used to |
f8d20a
|
352 |
generate path references that can later be resolved by that function. |
a97a70
|
353 |
|
fb6a5c
|
354 |
The ``resource`` passed in *must* be :term:`location`-aware. |
c8a91c
|
355 |
|
012b97
|
356 |
.. note:: |
8e037f
|
357 |
|
012b97
|
358 |
Each segment in the path tuple returned will equal the ``__name__`` |
M |
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. |
92c3e5
|
366 |
|
012b97
|
367 |
.. note:: |
92c3e5
|
368 |
|
012b97
|
369 |
The :term:`root` resource *must* have a ``__name__`` attribute with a |
M |
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 tuple |
|
373 |
rather than the empty string. |
7ae0c2
|
374 |
""" |
fb6a5c
|
375 |
return tuple(_resource_path_list(resource, *elements)) |
dbab77
|
376 |
|
0c29cf
|
377 |
|
33516a
|
378 |
model_path_tuple = resource_path_tuple # b/w compat (forever) |
0c29cf
|
379 |
|
182210
|
380 |
|
fb6a5c
|
381 |
def _resource_path_list(resource, *elements): |
CM |
382 |
""" Implementation detail shared by resource_path and resource_path_tuple""" |
|
383 |
path = [loc.__name__ or '' for loc in lineage(resource)] |
eb9fbf
|
384 |
path.reverse() |
a6b51e
|
385 |
path.extend(elements) |
e0be0c
|
386 |
return path |
e62e47
|
387 |
|
0c29cf
|
388 |
|
MM |
389 |
_model_path_list = _resource_path_list # b/w compat, not an API |
|
390 |
|
fb6a5c
|
391 |
|
CM |
392 |
def virtual_root(resource, request): |
7b75a8
|
393 |
""" |
fb6a5c
|
394 |
Provided any :term:`resource` and a :term:`request` object, return |
CM |
395 |
the resource object representing the :term:`virtual root` of the |
8b1f6e
|
396 |
current :term:`request`. Using a virtual root in a |
fd5ae9
|
397 |
:term:`traversal` -based :app:`Pyramid` application permits |
7ba907
|
398 |
rooting. For example, the resource at the traversal path ``/cms`` will |
MM |
399 |
be found at ``http://example.com/`` instead of rooting it at |
7b75a8
|
400 |
``http://example.com/cms/``. |
e62e47
|
401 |
|
fb6a5c
|
402 |
If the ``resource`` passed in is a context obtained via |
7b75a8
|
403 |
:term:`traversal`, and if the ``HTTP_X_VHM_ROOT`` key is in the |
CM |
404 |
WSGI environment, the value of this key will be treated as a |
fb6a5c
|
405 |
'virtual root path': the :func:`pyramid.traversal.find_resource` |
b32bfd
|
406 |
API will be used to find the virtual root resource using this path; |
CM |
407 |
if the resource is found, it will be returned. If the |
043ccd
|
408 |
``HTTP_X_VHM_ROOT`` key is not present in the WSGI environment, |
bac5b3
|
409 |
the physical :term:`root` of the resource tree will be returned instead. |
e62e47
|
410 |
|
7b75a8
|
411 |
Virtual roots are not useful at all in applications that use |
CM |
412 |
:term:`URL dispatch`. Contexts obtained via URL dispatch don't |
|
413 |
really support being virtually rooted (each URL dispatch context |
|
414 |
is both its own physical and virtual root). However if this API |
fb6a5c
|
415 |
is called with a ``resource`` argument which is a context obtained |
CM |
416 |
via URL dispatch, the resource passed in will be returned |
b74cd4
|
417 |
unconditionally.""" |
6fec21
|
418 |
try: |
CM |
419 |
reg = request.registry |
|
420 |
except AttributeError: |
7ba907
|
421 |
reg = get_current_registry() |
MM |
422 |
url_adapter = reg.queryMultiAdapter((resource, request), IResourceURL) |
|
423 |
if url_adapter is None: |
|
424 |
url_adapter = ResourceURL(resource, request) |
|
425 |
|
|
426 |
vpath, rpath = url_adapter.virtual_path, url_adapter.physical_path |
|
427 |
if rpath != vpath and rpath.endswith(vpath): |
0c29cf
|
428 |
vroot_path = rpath[: -len(vpath)] |
7ba907
|
429 |
return find_resource(resource, vroot_path) |
MM |
430 |
|
|
431 |
try: |
|
432 |
return request.root |
|
433 |
except AttributeError: |
|
434 |
return find_root(resource) |
0c29cf
|
435 |
|
6d4015
|
436 |
|
e6e0ea
|
437 |
def traversal_path(path): |
f84147
|
438 |
""" Variant of :func:`pyramid.traversal.traversal_path_info` suitable for |
5c43d5
|
439 |
decoding paths that are URL-encoded. |
CM |
440 |
|
|
441 |
If this function is passed a Unicode object instead of a sequence of |
|
442 |
bytes as ``path``, that Unicode object *must* directly encodeable to |
|
443 |
ASCII. For example, u'/foo' will work but u'/<unprintable unicode>' (a |
|
444 |
Unicode object with characters that cannot be encoded to ascii) will |
|
445 |
not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be |
|
446 |
encoded directly to ASCII. |
|
447 |
""" |
|
448 |
if isinstance(path, text_type): |
95df3a
|
449 |
# must not possess characters outside ascii |
5c43d5
|
450 |
path = path.encode('ascii') |
95df3a
|
451 |
# we unquote this path exactly like a PEP 3333 server would |
0c29cf
|
452 |
path = unquote_bytes_to_wsgi(path) # result will be a native string |
MM |
453 |
return traversal_path_info(path) # result will be a tuple of unicode |
|
454 |
|
841313
|
455 |
|
6212db
|
456 |
@lru_cache(1000) |
f84147
|
457 |
def traversal_path_info(path): |
5c43d5
|
458 |
""" Given``path``, return a tuple representing that path which can be |
CM |
459 |
used to traverse a resource tree. ``path`` is assumed to be an |
|
460 |
already-URL-decoded ``str`` type as if it had come to us from an upstream |
6212db
|
461 |
WSGI server as the ``PATH_INFO`` environ variable. |
841313
|
462 |
|
5c43d5
|
463 |
The ``path`` is first decoded to from its WSGI representation to Unicode; |
CM |
464 |
it is decoded differently depending on platform: |
f84147
|
465 |
|
5c43d5
|
466 |
- On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8 |
CM |
467 |
decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the |
|
468 |
URL cannot be decoded. |
f84147
|
469 |
|
6212db
|
470 |
- On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to |
CM |
471 |
bytes using the Latin-1 encoding; the resulting set of bytes is |
|
472 |
subsequently decoded to text using the UTF-8 encoding; a |
5c43d5
|
473 |
:exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be |
CM |
474 |
decoded. |
|
475 |
|
|
476 |
The ``path`` is split on slashes, creating a list of segments. If a |
|
477 |
segment name is empty or if it is ``.``, it is ignored. If a segment |
|
478 |
name is ``..``, the previous segment is deleted, and the ``..`` is |
|
479 |
ignored. |
841313
|
480 |
|
CM |
481 |
Examples: |
e6e0ea
|
482 |
|
CM |
483 |
``/`` |
|
484 |
|
|
485 |
() |
|
486 |
|
|
487 |
``/foo/bar/baz`` |
|
488 |
|
|
489 |
(u'foo', u'bar', u'baz') |
|
490 |
|
|
491 |
``foo/bar/baz`` |
|
492 |
|
|
493 |
(u'foo', u'bar', u'baz') |
|
494 |
|
|
495 |
``/foo/bar/baz/`` |
|
496 |
|
|
497 |
(u'foo', u'bar', u'baz') |
|
498 |
|
|
499 |
``/foo//bar//baz/`` |
|
500 |
|
|
501 |
(u'foo', u'bar', u'baz') |
|
502 |
|
|
503 |
``/foo/bar/baz/..`` |
|
504 |
|
|
505 |
(u'foo', u'bar') |
|
506 |
|
|
507 |
``/my%20archives/hello`` |
|
508 |
|
|
509 |
(u'my archives', u'hello') |
|
510 |
|
|
511 |
``/archives/La%20Pe%C3%B1a`` |
|
512 |
|
|
513 |
(u'archives', u'<unprintable unicode>') |
|
514 |
|
012b97
|
515 |
.. note:: |
M |
516 |
|
|
517 |
This function does not generate the same type of tuples that |
|
518 |
:func:`pyramid.traversal.resource_path_tuple` does. In particular, the |
|
519 |
leading empty string is not present in the tuple it returns, unlike tuples |
|
520 |
returned by :func:`pyramid.traversal.resource_path_tuple`. As a result, |
|
521 |
tuples generated by ``traversal_path`` are not resolveable by the |
|
522 |
:func:`pyramid.traversal.find_resource` API. ``traversal_path`` is a |
|
523 |
function mostly used by the internals of :app:`Pyramid` and by people |
|
524 |
writing their own traversal machinery, as opposed to users writing |
|
525 |
applications in :app:`Pyramid`. |
e6e0ea
|
526 |
""" |
f84147
|
527 |
try: |
0c29cf
|
528 |
path = decode_path_info(path) # result will be Unicode |
f84147
|
529 |
except UnicodeDecodeError as e: |
CM |
530 |
raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) |
0c29cf
|
531 |
return split_path_info(path) # result will be tuple of Unicode |
MM |
532 |
|
5c43d5
|
533 |
|
CM |
534 |
@lru_cache(1000) |
|
535 |
def split_path_info(path): |
95df3a
|
536 |
# suitable for splitting an already-unquoted-already-decoded (unicode) |
CM |
537 |
# path value |
bcd781
|
538 |
path = path.strip('/') |
62267e
|
539 |
clean = [] |
CM |
540 |
for segment in path.split('/'): |
83faa0
|
541 |
if not segment or segment == '.': |
62267e
|
542 |
continue |
CM |
543 |
elif segment == '..': |
f2ef79
|
544 |
if clean: |
CM |
545 |
del clean[-1] |
62267e
|
546 |
else: |
CM |
547 |
clean.append(segment) |
e281fa
|
548 |
return tuple(clean) |
0c29cf
|
549 |
|
df1c6f
|
550 |
|
a97a70
|
551 |
_segment_cache = {} |
CM |
552 |
|
5942ec
|
553 |
quote_path_segment_doc = """ \ |
CM |
554 |
Return a quoted representation of a 'path segment' (such as |
|
555 |
the string ``__name__`` attribute of a resource) as a string. If the |
|
556 |
``segment`` passed in is a unicode object, it is converted to a |
|
557 |
UTF-8 string, then it is URL-quoted using Python's |
|
558 |
``urllib.quote``. If the ``segment`` passed in is a string, it is |
|
559 |
URL-quoted using Python's :mod:`urllib.quote`. If the segment |
|
560 |
passed in is not a string or unicode object, an error will be |
|
561 |
raised. The return value of ``quote_path_segment`` is always a |
|
562 |
string, never Unicode. |
8cf91a
|
563 |
|
5942ec
|
564 |
You may pass a string of characters that need not be encoded as |
CM |
565 |
the ``safe`` argument to this function. This corresponds to the |
|
566 |
``safe`` argument to :mod:`urllib.quote`. |
e0be0c
|
567 |
|
5942ec
|
568 |
.. note:: |
62267e
|
569 |
|
5942ec
|
570 |
The return value for each segment passed to this |
CM |
571 |
function is cached in a module-scope dictionary for |
|
572 |
speed: the cached version is returned when possible |
|
573 |
rather than recomputing the quoted version. No cache |
|
574 |
emptying is ever done for the lifetime of an |
|
575 |
application, however. If you pass arbitrary |
|
576 |
user-supplied strings to this function (as opposed to |
|
577 |
some bounded set of values from a 'working set' known to |
|
578 |
your application), it may become a memory leak. |
|
579 |
""" |
|
580 |
|
|
581 |
|
bc37a5
|
582 |
if PY2: |
5942ec
|
583 |
# special-case on Python 2 for speed? unchecked |
f52759
|
584 |
def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): |
5942ec
|
585 |
""" %s """ % quote_path_segment_doc |
CM |
586 |
# The bit of this code that deals with ``_segment_cache`` is an |
|
587 |
# optimization: we cache all the computation of URL path segments |
|
588 |
# in this module-scope dictionary with the original string (or |
|
589 |
# unicode value) as the key, so we can look it up later without |
|
590 |
# needing to reencode or re-url-quote it |
|
591 |
try: |
|
592 |
return _segment_cache[(segment, safe)] |
|
593 |
except KeyError: |
0c29cf
|
594 |
if ( |
MM |
595 |
segment.__class__ is text_type |
|
596 |
): # isinstance slighly slower (~15%) |
5942ec
|
597 |
result = url_quote(segment.encode('utf-8'), safe) |
CM |
598 |
else: |
|
599 |
result = url_quote(str(segment), safe) |
|
600 |
# we don't need a lock to mutate _segment_cache, as the below |
|
601 |
# will generate exactly one Python bytecode (STORE_SUBSCR) |
|
602 |
_segment_cache[(segment, safe)] = result |
|
603 |
return result |
0c29cf
|
604 |
|
MM |
605 |
|
bc37a5
|
606 |
else: |
0c29cf
|
607 |
|
f52759
|
608 |
def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): |
bc37a5
|
609 |
""" %s """ % quote_path_segment_doc |
MM |
610 |
# The bit of this code that deals with ``_segment_cache`` is an |
|
611 |
# optimization: we cache all the computation of URL path segments |
|
612 |
# in this module-scope dictionary with the original string (or |
|
613 |
# unicode value) as the key, so we can look it up later without |
|
614 |
# needing to reencode or re-url-quote it |
|
615 |
try: |
|
616 |
return _segment_cache[(segment, safe)] |
|
617 |
except KeyError: |
|
618 |
if segment.__class__ not in (text_type, binary_type): |
|
619 |
segment = str(segment) |
|
620 |
result = url_quote(native_(segment, 'utf-8'), safe) |
|
621 |
# we don't need a lock to mutate _segment_cache, as the below |
|
622 |
# will generate exactly one Python bytecode (STORE_SUBSCR) |
|
623 |
_segment_cache[(segment, safe)] = result |
|
624 |
return result |
|
625 |
|
0c29cf
|
626 |
|
ebcdc7
|
627 |
slash = text_('/') |
0c29cf
|
628 |
|
62267e
|
629 |
|
3b7334
|
630 |
@implementer(ITraverser) |
fb6a5c
|
631 |
class ResourceTreeTraverser(object): |
CM |
632 |
""" A resource tree traverser that should be used (for speed) when |
b32bfd
|
633 |
every resource in the tree supplies a ``__name__`` and |
CM |
634 |
``__parent__`` attribute (ie. every resource in the tree is |
c6895b
|
635 |
:term:`location` aware) .""" |
077c3c
|
636 |
|
7ba907
|
637 |
VH_ROOT_KEY = VH_ROOT_KEY |
427859
|
638 |
VIEW_SELECTOR = '@@' |
CM |
639 |
|
62267e
|
640 |
def __init__(self, root): |
CM |
641 |
self.root = root |
3bfa35
|
642 |
|
acc776
|
643 |
def __call__(self, request): |
def68d
|
644 |
environ = request.environ |
ebcdc7
|
645 |
matchdict = request.matchdict |
acc776
|
646 |
|
def68d
|
647 |
if matchdict is not None: |
25cbe1
|
648 |
|
ebcdc7
|
649 |
path = matchdict.get('traverse', slash) or slash |
8e606d
|
650 |
if is_nonstr_iter(path): |
1db5c4
|
651 |
# this is a *traverse stararg (not a {traverse}) |
5c43d5
|
652 |
# routing has already decoded these elements, so we just |
CM |
653 |
# need to join them |
ece96f
|
654 |
path = '/' + slash.join(path) or slash |
25cbe1
|
655 |
|
CM |
656 |
subpath = matchdict.get('subpath', ()) |
8e606d
|
657 |
if not is_nonstr_iter(subpath): |
1db5c4
|
658 |
# this is not a *subpath stararg (just a {subpath}) |
5c43d5
|
659 |
# routing has already decoded this string, so we just need |
CM |
660 |
# to split it |
|
661 |
subpath = split_path_info(subpath) |
077c3c
|
662 |
|
dfc2b6
|
663 |
else: |
ef8a8c
|
664 |
# this request did not match a route |
6ecdbc
|
665 |
subpath = () |
dfc2b6
|
666 |
try: |
6212db
|
667 |
# empty if mounted under a path in mod_wsgi, for example |
ebcdc7
|
668 |
path = request.path_info or slash |
dfc2b6
|
669 |
except KeyError: |
ebcdc7
|
670 |
# if environ['PATH_INFO'] is just not there |
CM |
671 |
path = slash |
6212db
|
672 |
except UnicodeDecodeError as e: |
0c29cf
|
673 |
raise URLDecodeError( |
MM |
674 |
e.encoding, e.object, e.start, e.end, e.reason |
|
675 |
) |
1cd598
|
676 |
|
7ba907
|
677 |
if self.VH_ROOT_KEY in environ: |
5c43d5
|
678 |
# HTTP_X_VHM_ROOT |
7ba907
|
679 |
vroot_path = decode_path_info(environ[self.VH_ROOT_KEY]) |
92dcb5
|
680 |
vroot_tuple = split_path_info(vroot_path) |
0c29cf
|
681 |
vpath = ( |
MM |
682 |
vroot_path + path |
|
683 |
) # both will (must) be unicode or asciistr |
25c64c
|
684 |
vroot_idx = len(vroot_tuple) - 1 |
160f01
|
685 |
else: |
CM |
686 |
vroot_tuple = () |
|
687 |
vpath = path |
|
688 |
vroot_idx = -1 |
62267e
|
689 |
|
487651
|
690 |
root = self.root |
CM |
691 |
ob = vroot = root |
1cd598
|
692 |
|
0c29cf
|
693 |
if vpath == slash: # invariant: vpath must not be empty |
7fc6a3
|
694 |
# prevent a call to traversal_path if we know it's going |
CM |
695 |
# to return the empty tuple |
|
696 |
vpath_tuple = () |
d70fd9
|
697 |
else: |
7fc6a3
|
698 |
# we do dead reckoning here via tuple slicing instead of |
CM |
699 |
# pushing and popping temporary lists for speed purposes |
|
700 |
# and this hurts readability; apologies |
|
701 |
i = 0 |
3901fc
|
702 |
view_selector = self.VIEW_SELECTOR |
92dcb5
|
703 |
vpath_tuple = split_path_info(vpath) |
7fc6a3
|
704 |
for segment in vpath_tuple: |
3901fc
|
705 |
if segment[:2] == view_selector: |
0c29cf
|
706 |
return { |
MM |
707 |
'context': ob, |
|
708 |
'view_name': segment[2:], |
|
709 |
'subpath': vpath_tuple[i + 1 :], |
|
710 |
'traversed': vpath_tuple[: vroot_idx + i + 1], |
|
711 |
'virtual_root': vroot, |
|
712 |
'virtual_root_path': vroot_tuple, |
|
713 |
'root': root, |
|
714 |
} |
d70fd9
|
715 |
try: |
CM |
716 |
getitem = ob.__getitem__ |
|
717 |
except AttributeError: |
0c29cf
|
718 |
return { |
MM |
719 |
'context': ob, |
|
720 |
'view_name': segment, |
|
721 |
'subpath': vpath_tuple[i + 1 :], |
|
722 |
'traversed': vpath_tuple[: vroot_idx + i + 1], |
|
723 |
'virtual_root': vroot, |
|
724 |
'virtual_root_path': vroot_tuple, |
|
725 |
'root': root, |
|
726 |
} |
1cd598
|
727 |
|
d70fd9
|
728 |
try: |
CM |
729 |
next = getitem(segment) |
|
730 |
except KeyError: |
0c29cf
|
731 |
return { |
MM |
732 |
'context': ob, |
|
733 |
'view_name': segment, |
|
734 |
'subpath': vpath_tuple[i + 1 :], |
|
735 |
'traversed': vpath_tuple[: vroot_idx + i + 1], |
|
736 |
'virtual_root': vroot, |
|
737 |
'virtual_root_path': vroot_tuple, |
|
738 |
'root': root, |
|
739 |
} |
51c305
|
740 |
if i == vroot_idx: |
7fc6a3
|
741 |
vroot = next |
d70fd9
|
742 |
ob = next |
CM |
743 |
i += 1 |
013c4b
|
744 |
|
0c29cf
|
745 |
return { |
MM |
746 |
'context': ob, |
|
747 |
'view_name': empty, |
|
748 |
'subpath': subpath, |
|
749 |
'traversed': vpath_tuple, |
|
750 |
'virtual_root': vroot, |
|
751 |
'virtual_root_path': vroot_tuple, |
|
752 |
'root': root, |
|
753 |
} |
217070
|
754 |
|
0c29cf
|
755 |
|
MM |
756 |
ModelGraphTraverser = ( |
|
757 |
ResourceTreeTraverser |
|
758 |
) # b/w compat, not API, used in wild |
|
759 |
|
fb6a5c
|
760 |
|
7ba907
|
761 |
@implementer(IResourceURL) |
c51896
|
762 |
class ResourceURL(object): |
7ba907
|
763 |
VH_ROOT_KEY = VH_ROOT_KEY |
e62e47
|
764 |
|
c51896
|
765 |
def __init__(self, resource, request): |
db0185
|
766 |
physical_path_tuple = resource_path_tuple(resource) |
CM |
767 |
physical_path = _join_path_tuple(physical_path_tuple) |
|
768 |
|
|
769 |
if physical_path_tuple != ('',): |
|
770 |
physical_path_tuple = physical_path_tuple + ('',) |
c51896
|
771 |
physical_path = physical_path + '/' |
CM |
772 |
|
|
773 |
virtual_path = physical_path |
db0185
|
774 |
virtual_path_tuple = physical_path_tuple |
c51896
|
775 |
|
CM |
776 |
environ = request.environ |
7ba907
|
777 |
vroot_path = environ.get(self.VH_ROOT_KEY) |
c51896
|
778 |
|
CM |
779 |
# if the physical path starts with the virtual root path, trim it out |
|
780 |
# of the virtual path |
|
781 |
if vroot_path is not None: |
db0185
|
782 |
vroot_path = vroot_path.rstrip('/') |
CM |
783 |
if vroot_path and physical_path.startswith(vroot_path): |
|
784 |
vroot_path_tuple = tuple(vroot_path.split('/')) |
|
785 |
numels = len(vroot_path_tuple) |
|
786 |
virtual_path_tuple = ('',) + physical_path_tuple[numels:] |
0c29cf
|
787 |
virtual_path = physical_path[len(vroot_path) :] |
c51896
|
788 |
|
0c29cf
|
789 |
self.virtual_path = virtual_path # IResourceURL attr |
44f585
|
790 |
self.physical_path = physical_path # IResourceURL attr |
0c29cf
|
791 |
self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5) |
MM |
792 |
self.physical_path_tuple = ( |
|
793 |
physical_path_tuple |
|
794 |
) # IResourceURL attr (1.5) |
|
795 |
|
118ea0
|
796 |
|
eb9fbf
|
797 |
@lru_cache(1000) |
CM |
798 |
def _join_path_tuple(tuple): |
|
799 |
return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/' |
e62e47
|
800 |
|
0c29cf
|
801 |
|
eac7c4
|
802 |
class DefaultRootFactory: |
CM |
803 |
__parent__ = None |
|
804 |
__name__ = None |
0c29cf
|
805 |
|
eac7c4
|
806 |
def __init__(self, request): |
95e971
|
807 |
pass |