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

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 collections import deque
2import json
4from zope.interface import implementer
5from zope.interface.interface import InterfaceClass
7from webob import BaseRequest
9from pyramid.interfaces import (
10 IRequest,
11 IRequestExtensions,
12 IResponse,
13 ISessionFactory,
14)
16from pyramid.compat import text_, bytes_, native_, iteritems_
18from pyramid.decorator import reify
19from pyramid.i18n import LocalizerRequestMixin
20from pyramid.response import Response, _get_response_factory
21from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin
22from pyramid.url import URLMethodsMixin
23from pyramid.util import InstancePropertyHelper, InstancePropertyMixin
24from pyramid.view import ViewMethodsMixin
27class TemplateContext(object):
28 pass
31class CallbackMethodsMixin(object):
32 @reify
33 def finished_callbacks(self):
34 return deque()
36 @reify
37 def response_callbacks(self):
38 return deque()
40 def add_response_callback(self, callback):
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
44 successfully created. :app:`Pyramid` does not have a
45 global response object: this functionality allows an
46 application to register an action to be performed against the
47 response once one is created.
49 A 'callback' is a callable which accepts two positional
50 parameters: ``request`` and ``response``. For example:
52 .. code-block:: python
53 :linenos:
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)
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.
65 All response callbacks are called *after* the tweens and
66 *before* the :class:`pyramid.events.NewResponse` event is sent.
68 Errors raised by callbacks are not handled specially. They
69 will be propagated to the caller of the :app:`Pyramid`
70 router application.
72 .. seealso::
74 See also :ref:`using_response_callbacks`.
75 """
77 self.response_callbacks.append(callback)
79 def _process_response_callbacks(self, response):
80 callbacks = self.response_callbacks
81 while callbacks:
82 callback = callbacks.popleft()
83 callback(self, response)
85 def add_finished_callback(self, callback):
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.
91 ``callback`` is a callable which accepts a single positional
92 parameter: ``request``. For example:
94 .. code-block:: python
95 :linenos:
97 import transaction
99 def commit_callback(request):
100 '''commit or abort the transaction associated with request'''
101 if request.exception is not None:
102 transaction.abort()
103 else:
104 transaction.commit()
105 request.add_finished_callback(commit_callback)
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.
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.
124 Errors raised by finished callbacks are not handled specially.
125 They will be propagated to the caller of the :app:`Pyramid`
126 router application.
128 .. seealso::
130 See also :ref:`using_finished_callbacks`.
131 """
132 self.finished_callbacks.append(callback)
134 def _process_finished_callbacks(self):
135 callbacks = self.finished_callbacks
136 while callbacks:
137 callback = callbacks.popleft()
138 callback(self)
141@implementer(IRequest)
142class Request(
143 BaseRequest,
144 URLMethodsMixin,
145 CallbackMethodsMixin,
146 InstancePropertyMixin,
147 LocalizerRequestMixin,
148 AuthenticationAPIMixin,
149 AuthorizationAPIMixin,
150 ViewMethodsMixin,
151):
152 """
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.
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.
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
170 https://webob.org/ for further information.
171 """
173 exception = None
174 exc_info = None
175 matchdict = None
176 matched_route = None
177 request_iface = IRequest
179 ResponseClass = Response
181 @reify
182 def tmpl_context(self):
183 # docs-deprecated template context for Pylons-like apps; do not
184 # remove.
185 return TemplateContext()
187 @reify
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:
195 raise AttributeError(
196 'No session factory registered '
197 '(see the Sessions chapter of the Pyramid documentation)'
198 )
199 return factory(self)
201 @reify
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
206 accessed. Subsequent accesses will return the same Response object.
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."""
215 response_factory = _get_response_factory(self.registry)
216 return response_factory(self)
218 def is_response(self, ob):
219 """ Return ``True`` if the object passed as ``ob`` is a valid
220 response object, ``False`` otherwise."""
221 if ob.__class__ is Response:
222 return True
223 registry = self.registry
224 adapted = registry.queryAdapterOrSelf(ob, IResponse)
225 if adapted is None:
226 return False
227 return adapted is ob
229 @property
230 def json_body(self):
231 return json.loads(text_(self.body, self.charset))
234def route_request_iface(name, bases=()):
235 # zope.interface treats the __name__ as the __doc__ and changes __name__
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.
241 iface = InterfaceClass(
242 '%s_IRequest' % name,
243 bases=bases,
244 __doc__="route_request_iface-generated interface",
245 )
246 # for exception view lookups
247 iface.combined = InterfaceClass(
248 '%s_combined_IRequest' % name,
249 bases=(iface, IRequest),
250 __doc__='route_request_iface-generated combined interface',
251 )
252 return iface
255def add_global_response_headers(request, headerlist):
256 def add_headers(request, response):
257 for k, v in headerlist:
258 response.headerlist.append((k, v))
260 request.add_response_callback(add_headers)
263def call_app_with_subpath_as_path_info(request, app):
264 # Copy the request. Use the source request's subpath (if it exists) as
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.
268 #
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 '/').
275 environ = request.environ
276 script_name = environ.get('SCRIPT_NAME', '')
277 path_info = environ.get('PATH_INFO', '/')
278 subpath = list(getattr(request, 'subpath', ()))
280 new_script_name = ''
282 # compute new_path_info
283 new_path_info = '/' + '/'.join(
284 [native_(x.encode('utf-8'), 'latin-1') for x in subpath]
285 )
287 if new_path_info != '/': # don't want a sole double-slash
288 if path_info != '/': # if orig path_info is '/', we're already done
289 if path_info.endswith('/'):
290 # readd trailing slash stripped by subpath (traversal)
291 # conversion
292 new_path_info += '/'
294 # compute new_script_name
295 workback = (script_name + path_info).split('/')
297 tmp = []
298 while workback:
299 if tmp == subpath:
300 break
301 el = workback.pop()
302 if el:
303 tmp.insert(0, text_(bytes_(el, 'latin-1'), 'utf-8'))
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]
310 new_script_name = '/'.join(workback)
312 new_request = request.copy()
313 new_request.environ['SCRIPT_NAME'] = new_script_name
314 new_request.environ['PATH_INFO'] = new_path_info
316 return new_request.get_response(app)
319def apply_request_extensions(request, extensions=None):
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.
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)
335 InstancePropertyHelper.apply_properties(
336 request, extensions.descriptors
337 )