|
1 # |
|
2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net> |
|
3 # All Rights Reserved. |
|
4 # |
|
5 # This software is subject to the provisions of the Zope Public License, |
|
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. |
|
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED |
|
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
|
10 # FOR A PARTICULAR PURPOSE. |
|
11 # |
|
12 |
|
13 __docformat__ = 'restructuredtext' |
|
14 |
|
15 |
|
16 # import standard library |
|
17 |
|
18 # import interfaces |
|
19 from pyramid.interfaces import VH_ROOT_KEY |
|
20 from zope.traversing.interfaces import ITraversable, BeforeTraverseEvent |
|
21 |
|
22 # import packages |
|
23 from pyramid.compat import decode_path_info, is_nonstr_iter |
|
24 from pyramid.exceptions import URLDecodeError, NotFound |
|
25 from pyramid.threadlocal import get_current_registry |
|
26 from pyramid.traversal import ResourceTreeTraverser, slash, split_path_info, empty |
|
27 from zope.interface import Interface |
|
28 |
|
29 |
|
30 class NamespaceTraverser(ResourceTreeTraverser): |
|
31 """Custom traverser handling views and namespaces |
|
32 |
|
33 This is an upgraded version of native Pyramid traverser. |
|
34 It adds: |
|
35 - a new BeforeTraverseEvent before traversing each object in the path |
|
36 - support for namespaces with "++" notation |
|
37 """ |
|
38 |
|
39 NAMESPACE_SELECTOR = '++' |
|
40 |
|
41 def __call__(self, request): |
|
42 |
|
43 environ = request.environ |
|
44 matchdict = request.matchdict |
|
45 |
|
46 if matchdict is not None: |
|
47 path = matchdict.get('traverse', slash) or slash |
|
48 if is_nonstr_iter(path): |
|
49 # this is a *traverse stararg (not a {traverse}) |
|
50 # routing has already decoded these elements, so we just |
|
51 # need to join them |
|
52 path = '/' + slash.join(path) or slash |
|
53 |
|
54 subpath = matchdict.get('subpath', ()) |
|
55 if not is_nonstr_iter(subpath): |
|
56 # this is not a *subpath stararg (just a {subpath}) |
|
57 # routing has already decoded this string, so we just need |
|
58 # to split it |
|
59 subpath = split_path_info(subpath) |
|
60 |
|
61 else: |
|
62 subpath = () |
|
63 try: |
|
64 # empty if mounted under a path in mod_wsgi, for example |
|
65 path = request.path_info or slash |
|
66 except KeyError: |
|
67 # if environ['PATH_INFO'] is just not there |
|
68 path = slash |
|
69 except UnicodeDecodeError as e: |
|
70 raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) |
|
71 |
|
72 if VH_ROOT_KEY in environ: |
|
73 # HTTP_X_VHM_ROOT |
|
74 vroot_path = decode_path_info(environ[VH_ROOT_KEY]) |
|
75 vroot_tuple = split_path_info(vroot_path) |
|
76 vpath = vroot_path + path |
|
77 vroot_idx = len(vroot_tuple) - 1 |
|
78 else: |
|
79 vroot_tuple = () |
|
80 vpath = path |
|
81 vroot_idx = -1 |
|
82 |
|
83 root = self.root |
|
84 ob = vroot = root |
|
85 |
|
86 request.registry.notify(BeforeTraverseEvent(root, request)) |
|
87 |
|
88 if vpath == slash: |
|
89 # invariant: vpath must not be empty |
|
90 # prevent a call to traversal_path if we know it's going |
|
91 # to return the empty tuple |
|
92 vpath_tuple = () |
|
93 |
|
94 else: |
|
95 # we do dead reckoning here via tuple slicing instead of |
|
96 # pushing and popping temporary lists for speed purposes |
|
97 # and this hurts readability; apologies |
|
98 i = 0 |
|
99 view_selector = self.VIEW_SELECTOR |
|
100 ns_selector = self.NAMESPACE_SELECTOR |
|
101 vpath_tuple = split_path_info(vpath) |
|
102 |
|
103 for segment in vpath_tuple: |
|
104 request.registry.notify(BeforeTraverseEvent(ob, request)) |
|
105 |
|
106 if segment[:2] == view_selector: |
|
107 return {'context': ob, |
|
108 'view_name': segment[2:], |
|
109 'subpath': vpath_tuple[i + 1:], |
|
110 'traversed': vpath_tuple[:vroot_idx + i + 1], |
|
111 'virtual_root': vroot, |
|
112 'virtual_root_path': vroot_tuple, |
|
113 'root': root} |
|
114 |
|
115 if segment[:2] == ns_selector: |
|
116 ns, name = segment[2:].split(ns_selector, 1) |
|
117 registry = get_current_registry() |
|
118 traverser = registry.queryMultiAdapter((ob, request), ITraversable, ns) |
|
119 if traverser is None: |
|
120 traverser = registry.queryAdapter(ob, ITraversable, ns) |
|
121 if traverser is None: |
|
122 traverser = registry.queryAdapter(request, ITraversable, ns) |
|
123 if traverser is None: |
|
124 raise NotFound() |
|
125 ob = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:]) |
|
126 i += 1 |
|
127 continue |
|
128 |
|
129 try: |
|
130 getitem = ob.__getitem__ |
|
131 except AttributeError: |
|
132 return {'context': ob, |
|
133 'view_name': segment, |
|
134 'subpath': vpath_tuple[i + 1:], |
|
135 'traversed': vpath_tuple[:vroot_idx + i + 1], |
|
136 'virtual_root': vroot, |
|
137 'virtual_root_path': vroot_tuple, |
|
138 'root': root} |
|
139 |
|
140 try: |
|
141 next = getitem(segment) |
|
142 except KeyError: |
|
143 return {'context': ob, |
|
144 'view_name': segment, |
|
145 'subpath': vpath_tuple[i + 1:], |
|
146 'traversed': vpath_tuple[:vroot_idx + i + 1], |
|
147 'virtual_root': vroot, |
|
148 'virtual_root_path': vroot_tuple, |
|
149 'root': root} |
|
150 if i == vroot_idx: |
|
151 vroot = next |
|
152 ob = next |
|
153 i += 1 |
|
154 |
|
155 return {'context': ob, |
|
156 'view_name': empty, |
|
157 'subpath': subpath, |
|
158 'traversed': vpath_tuple, |
|
159 'virtual_root': vroot, |
|
160 'virtual_root_path': vroot_tuple, |
|
161 'root': root} |
|
162 |
|
163 |
|
164 def get_parent(context, interface=Interface, allow_context=True): |
|
165 """Get first parent of the context that implements given interface""" |
|
166 if allow_context: |
|
167 parent = context |
|
168 else: |
|
169 parent = getattr(context, '__parent__', None) |
|
170 while parent is not None: |
|
171 if interface.providedBy(parent): |
|
172 return interface(parent) |
|
173 parent = getattr(parent, '__parent__', None) |
|
174 return None |