commit | author | age
|
c73eb9
|
1 |
from collections import deque |
5cf9fc
|
2 |
import json |
CM |
3 |
|
3b7334
|
4 |
from zope.interface import implementer |
d66bfb
|
5 |
from zope.interface.interface import InterfaceClass |
CM |
6 |
|
bca03f
|
7 |
from webob import BaseRequest |
dfc2b6
|
8 |
|
0c1c39
|
9 |
from pyramid.interfaces import ( |
CM |
10 |
IRequest, |
04cc91
|
11 |
IRequestExtensions, |
0c1c39
|
12 |
IResponse, |
CM |
13 |
ISessionFactory, |
0c29cf
|
14 |
) |
968209
|
15 |
|
0c29cf
|
16 |
from pyramid.compat import text_, bytes_, native_, iteritems_ |
0c1c39
|
17 |
|
968209
|
18 |
from pyramid.decorator import reify |
330164
|
19 |
from pyramid.i18n import LocalizerRequestMixin |
25c64c
|
20 |
from pyramid.response import Response, _get_response_factory |
0c29cf
|
21 |
from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin |
fb90f0
|
22 |
from pyramid.url import URLMethodsMixin |
0c29cf
|
23 |
from pyramid.util import InstancePropertyHelper, InstancePropertyMixin |
e40ef2
|
24 |
from pyramid.view import ViewMethodsMixin |
0c29cf
|
25 |
|
c4e3f4
|
26 |
|
14dc81
|
27 |
class TemplateContext(object): |
CM |
28 |
pass |
0c29cf
|
29 |
|
9395f0
|
30 |
|
274435
|
31 |
class CallbackMethodsMixin(object): |
37f3ed
|
32 |
@reify |
MM |
33 |
def finished_callbacks(self): |
|
34 |
return deque() |
|
35 |
|
|
36 |
@reify |
|
37 |
def response_callbacks(self): |
|
38 |
return deque() |
|
39 |
|
844e98
|
40 |
def add_response_callback(self, callback): |
CM |
41 |
""" |
|
42 |
Add a callback to the set of callbacks to be called by the |
|
43 |
:term:`router` at a point after a :term:`response` object is |
fd5ae9
|
44 |
successfully created. :app:`Pyramid` does not have a |
844e98
|
45 |
global response object: this functionality allows an |
CM |
46 |
application to register an action to be performed against the |
|
47 |
response once one is created. |
|
48 |
|
|
49 |
A 'callback' is a callable which accepts two positional |
|
50 |
parameters: ``request`` and ``response``. For example: |
|
51 |
|
|
52 |
.. code-block:: python |
|
53 |
:linenos: |
|
54 |
|
|
55 |
def cache_callback(request, response): |
|
56 |
'Set the cache_control max_age for the response' |
|
57 |
response.cache_control.max_age = 360 |
|
58 |
request.add_response_callback(cache_callback) |
|
59 |
|
|
60 |
Response callbacks are called in the order they're added |
|
61 |
(first-to-most-recently-added). No response callback is |
|
62 |
called if an exception happens in application code, or if the |
|
63 |
response object returned by :term:`view` code is invalid. |
|
64 |
|
25c64c
|
65 |
All response callbacks are called *after* the tweens and |
1e5c58
|
66 |
*before* the :class:`pyramid.events.NewResponse` event is sent. |
844e98
|
67 |
|
CM |
68 |
Errors raised by callbacks are not handled specially. They |
fd5ae9
|
69 |
will be propagated to the caller of the :app:`Pyramid` |
81d3b5
|
70 |
router application. |
CM |
71 |
|
2033ee
|
72 |
.. seealso:: |
SP |
73 |
|
|
74 |
See also :ref:`using_response_callbacks`. |
81d3b5
|
75 |
""" |
844e98
|
76 |
|
37f3ed
|
77 |
self.response_callbacks.append(callback) |
844e98
|
78 |
|
CM |
79 |
def _process_response_callbacks(self, response): |
b847e0
|
80 |
callbacks = self.response_callbacks |
CM |
81 |
while callbacks: |
c73eb9
|
82 |
callback = callbacks.popleft() |
844e98
|
83 |
callback(self, response) |
d66bfb
|
84 |
|
ad6a67
|
85 |
def add_finished_callback(self, callback): |
CM |
86 |
""" |
|
87 |
Add a callback to the set of callbacks to be called |
|
88 |
unconditionally by the :term:`router` at the very end of |
|
89 |
request processing. |
|
90 |
|
|
91 |
``callback`` is a callable which accepts a single positional |
|
92 |
parameter: ``request``. For example: |
|
93 |
|
|
94 |
.. code-block:: python |
|
95 |
:linenos: |
|
96 |
|
|
97 |
import transaction |
|
98 |
|
|
99 |
def commit_callback(request): |
|
100 |
'''commit or abort the transaction associated with request''' |
81d3b5
|
101 |
if request.exception is not None: |
ad6a67
|
102 |
transaction.abort() |
CM |
103 |
else: |
|
104 |
transaction.commit() |
|
105 |
request.add_finished_callback(commit_callback) |
|
106 |
|
|
107 |
Finished callbacks are called in the order they're added ( |
|
108 |
first- to most-recently- added). Finished callbacks (unlike |
|
109 |
response callbacks) are *always* called, even if an exception |
|
110 |
happens in application code that prevents a response from |
|
111 |
being generated. |
|
112 |
|
|
113 |
The set of finished callbacks associated with a request are |
|
114 |
called *very late* in the processing of that request; they are |
|
115 |
essentially the last thing called by the :term:`router`. They |
|
116 |
are called after response processing has already occurred in a |
|
117 |
top-level ``finally:`` block within the router request |
|
118 |
processing code. As a result, mutations performed to the |
|
119 |
``request`` provided to a finished callback will have no |
|
120 |
meaningful effect, because response processing will have |
|
121 |
already occurred, and the request's scope will expire almost |
|
122 |
immediately after all finished callbacks have been processed. |
|
123 |
|
|
124 |
Errors raised by finished callbacks are not handled specially. |
fd5ae9
|
125 |
They will be propagated to the caller of the :app:`Pyramid` |
81d3b5
|
126 |
router application. |
CM |
127 |
|
2033ee
|
128 |
.. seealso:: |
SP |
129 |
|
|
130 |
See also :ref:`using_finished_callbacks`. |
81d3b5
|
131 |
""" |
37f3ed
|
132 |
self.finished_callbacks.append(callback) |
ad6a67
|
133 |
|
CM |
134 |
def _process_finished_callbacks(self): |
b847e0
|
135 |
callbacks = self.finished_callbacks |
CM |
136 |
while callbacks: |
c73eb9
|
137 |
callback = callbacks.popleft() |
ad6a67
|
138 |
callback(self) |
CM |
139 |
|
0c29cf
|
140 |
|
3b7334
|
141 |
@implementer(IRequest) |
0184b5
|
142 |
class Request( |
CM |
143 |
BaseRequest, |
|
144 |
URLMethodsMixin, |
|
145 |
CallbackMethodsMixin, |
|
146 |
InstancePropertyMixin, |
|
147 |
LocalizerRequestMixin, |
|
148 |
AuthenticationAPIMixin, |
|
149 |
AuthorizationAPIMixin, |
e40ef2
|
150 |
ViewMethodsMixin, |
0c29cf
|
151 |
): |
274435
|
152 |
""" |
CM |
153 |
A subclass of the :term:`WebOb` Request class. An instance of |
|
154 |
this class is created by the :term:`router` and is provided to a |
|
155 |
view callable (and to other subsystems) as the ``request`` |
|
156 |
argument. |
|
157 |
|
|
158 |
The documentation below (save for the ``add_response_callback`` and |
|
159 |
``add_finished_callback`` methods, which are defined in this subclass |
|
160 |
itself, and the attributes ``context``, ``registry``, ``root``, |
|
161 |
``subpath``, ``traversed``, ``view_name``, ``virtual_root`` , and |
|
162 |
``virtual_root_path``, each of which is added to the request by the |
|
163 |
:term:`router` at request ingress time) are autogenerated from the WebOb |
|
164 |
source code used when this documentation was generated. |
|
165 |
|
|
166 |
Due to technical constraints, we can't yet display the WebOb |
|
167 |
version number from which this documentation is autogenerated, but |
|
168 |
it will be the 'prevailing WebOb version' at the time of the |
|
169 |
release of this :app:`Pyramid` version. See |
a816a8
|
170 |
https://webob.org/ for further information. |
274435
|
171 |
""" |
0c29cf
|
172 |
|
274435
|
173 |
exception = None |
CM |
174 |
exc_info = None |
|
175 |
matchdict = None |
|
176 |
matched_route = None |
849196
|
177 |
request_iface = IRequest |
274435
|
178 |
|
c60c0e
|
179 |
ResponseClass = Response |
CM |
180 |
|
274435
|
181 |
@reify |
CM |
182 |
def tmpl_context(self): |
f077a0
|
183 |
# docs-deprecated template context for Pylons-like apps; do not |
CM |
184 |
# remove. |
274435
|
185 |
return TemplateContext() |
CM |
186 |
|
968209
|
187 |
@reify |
CM |
188 |
def session(self): |
|
189 |
""" Obtain the :term:`session` object associated with this |
|
190 |
request. If a :term:`session factory` has not been registered |
|
191 |
during application configuration, a |
|
192 |
:class:`pyramid.exceptions.ConfigurationError` will be raised""" |
|
193 |
factory = self.registry.queryUtility(ISessionFactory) |
|
194 |
if factory is None: |
24b5c1
|
195 |
raise AttributeError( |
968209
|
196 |
'No session factory registered ' |
0c29cf
|
197 |
'(see the Sessions chapter of the Pyramid documentation)' |
MM |
198 |
) |
968209
|
199 |
return factory(self) |
CM |
200 |
|
a7b1a9
|
201 |
@reify |
CM |
202 |
def response(self): |
|
203 |
"""This attribute is actually a "reified" property which returns an |
|
204 |
instance of the :class:`pyramid.response.Response`. class. The |
|
205 |
response object returned does not exist until this attribute is |
cd2e8e
|
206 |
accessed. Subsequent accesses will return the same Response object. |
a7b1a9
|
207 |
|
CM |
208 |
The ``request.response`` API is used by renderers. A render obtains |
|
209 |
the response object it will return from a view that uses that renderer |
|
210 |
by accessing ``request.response``. Therefore, it's possible to use the |
|
211 |
``request.response`` API to set up a response object with "the |
|
212 |
right" attributes (e.g. by calling ``request.response.set_cookie()``) |
|
213 |
within a view that uses a renderer. Mutations to this response object |
|
214 |
will be preserved in the response sent to the client.""" |
32cb80
|
215 |
response_factory = _get_response_factory(self.registry) |
JA |
216 |
return response_factory(self) |
164677
|
217 |
|
920990
|
218 |
def is_response(self, ob): |
CM |
219 |
""" Return ``True`` if the object passed as ``ob`` is a valid |
|
220 |
response object, ``False`` otherwise.""" |
71cd93
|
221 |
if ob.__class__ is Response: |
CM |
222 |
return True |
920990
|
223 |
registry = self.registry |
CM |
224 |
adapted = registry.queryAdapterOrSelf(ob, IResponse) |
|
225 |
if adapted is None: |
|
226 |
return False |
|
227 |
return adapted is ob |
d8c55c
|
228 |
|
b78eff
|
229 |
@property |
6a0602
|
230 |
def json_body(self): |
55fb96
|
231 |
return json.loads(text_(self.body, self.charset)) |
b78eff
|
232 |
|
25c64c
|
233 |
|
333bd0
|
234 |
def route_request_iface(name, bases=()): |
873d9b
|
235 |
# zope.interface treats the __name__ as the __doc__ and changes __name__ |
CM |
236 |
# to None for interfaces that contain spaces if you do not pass a |
|
237 |
# nonempty __doc__ (insane); see |
|
238 |
# zope.interface.interface.Element.__init__ and |
|
239 |
# https://github.com/Pylons/pyramid/issues/232; as a result, always pass |
|
240 |
# __doc__ to the InterfaceClass constructor. |
0c29cf
|
241 |
iface = InterfaceClass( |
MM |
242 |
'%s_IRequest' % name, |
|
243 |
bases=bases, |
|
244 |
__doc__="route_request_iface-generated interface", |
|
245 |
) |
abb68d
|
246 |
# for exception view lookups |
873d9b
|
247 |
iface.combined = InterfaceClass( |
CM |
248 |
'%s_combined_IRequest' % name, |
|
249 |
bases=(iface, IRequest), |
0c29cf
|
250 |
__doc__='route_request_iface-generated combined interface', |
MM |
251 |
) |
ff1213
|
252 |
return iface |
dfc2b6
|
253 |
|
25c64c
|
254 |
|
839ea0
|
255 |
def add_global_response_headers(request, headerlist): |
844e98
|
256 |
def add_headers(request, response): |
CM |
257 |
for k, v in headerlist: |
a58c4a
|
258 |
response.headerlist.append((k, v)) |
0c29cf
|
259 |
|
844e98
|
260 |
request.add_response_callback(add_headers) |
0c29cf
|
261 |
|
98ea38
|
262 |
|
8cf6dd
|
263 |
def call_app_with_subpath_as_path_info(request, app): |
b64268
|
264 |
# Copy the request. Use the source request's subpath (if it exists) as |
CM |
265 |
# the new request's PATH_INFO. Set the request copy's SCRIPT_NAME to the |
|
266 |
# prefix before the subpath. Call the application with the new request |
|
267 |
# and return a response. |
bcb331
|
268 |
# |
CM |
269 |
# Postconditions: |
|
270 |
# - SCRIPT_NAME and PATH_INFO are empty or start with / |
|
271 |
# - At least one of SCRIPT_NAME or PATH_INFO are set. |
|
272 |
# - SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should |
|
273 |
# be '/'). |
|
274 |
|
896fb1
|
275 |
environ = request.environ |
CM |
276 |
script_name = environ.get('SCRIPT_NAME', '') |
|
277 |
path_info = environ.get('PATH_INFO', '/') |
|
278 |
subpath = list(getattr(request, 'subpath', ())) |
bcb331
|
279 |
|
b64268
|
280 |
new_script_name = '' |
bcb331
|
281 |
|
b64268
|
282 |
# compute new_path_info |
0c29cf
|
283 |
new_path_info = '/' + '/'.join( |
MM |
284 |
[native_(x.encode('utf-8'), 'latin-1') for x in subpath] |
|
285 |
) |
bcb331
|
286 |
|
0c29cf
|
287 |
if new_path_info != '/': # don't want a sole double-slash |
MM |
288 |
if path_info != '/': # if orig path_info is '/', we're already done |
b64268
|
289 |
if path_info.endswith('/'): |
012b97
|
290 |
# readd trailing slash stripped by subpath (traversal) |
b64268
|
291 |
# conversion |
CM |
292 |
new_path_info += '/' |
bcb331
|
293 |
|
b64268
|
294 |
# compute new_script_name |
CM |
295 |
workback = (script_name + path_info).split('/') |
bcb331
|
296 |
|
8cf6dd
|
297 |
tmp = [] |
b64268
|
298 |
while workback: |
CM |
299 |
if tmp == subpath: |
|
300 |
break |
|
301 |
el = workback.pop() |
|
302 |
if el: |
c779f1
|
303 |
tmp.insert(0, text_(bytes_(el, 'latin-1'), 'utf-8')) |
65a5a9
|
304 |
|
CM |
305 |
# strip all trailing slashes from workback to avoid appending undue slashes |
|
306 |
# to end of script_name |
|
307 |
while workback and (workback[-1] == ''): |
|
308 |
workback = workback[:-1] |
012b97
|
309 |
|
b64268
|
310 |
new_script_name = '/'.join(workback) |
CM |
311 |
|
896fb1
|
312 |
new_request = request.copy() |
CM |
313 |
new_request.environ['SCRIPT_NAME'] = new_script_name |
|
314 |
new_request.environ['PATH_INFO'] = new_path_info |
2b4134
|
315 |
|
896fb1
|
316 |
return new_request.get_response(app) |
04cc91
|
317 |
|
0c29cf
|
318 |
|
04cc91
|
319 |
def apply_request_extensions(request, extensions=None): |
MM |
320 |
"""Apply request extensions (methods and properties) to an instance of |
|
321 |
:class:`pyramid.interfaces.IRequest`. This method is dependent on the |
|
322 |
``request`` containing a properly initialized registry. |
|
323 |
|
|
324 |
After invoking this method, the ``request`` should have the methods |
|
325 |
and properties that were defined using |
|
326 |
:meth:`pyramid.config.Configurator.add_request_method`. |
|
327 |
""" |
|
328 |
if extensions is None: |
|
329 |
extensions = request.registry.queryUtility(IRequestExtensions) |
|
330 |
if extensions is not None: |
|
331 |
for name, fn in iteritems_(extensions.methods): |
|
332 |
method = fn.__get__(request, request.__class__) |
|
333 |
setattr(request, name, method) |
|
334 |
|
|
335 |
InstancePropertyHelper.apply_properties( |
0c29cf
|
336 |
request, extensions.descriptors |
MM |
337 |
) |