/pyramid/request.py
Python | 332 lines | 268 code | 23 blank | 41 comment | 10 complexity | fb082ddb869f2dc9aac4874a2236859d MD5 | raw file
- from collections import deque
- import json
- from zope.interface import implementer
- from zope.interface.interface import InterfaceClass
- from webob import BaseRequest
- from pyramid.interfaces import (
- IRequest,
- IRequestExtensions,
- IResponse,
- ISessionFactory,
- )
- from pyramid.compat import (
- text_,
- bytes_,
- native_,
- iteritems_,
- )
- from pyramid.decorator import reify
- from pyramid.i18n import LocalizerRequestMixin
- from pyramid.response import Response, _get_response_factory
- from pyramid.security import (
- AuthenticationAPIMixin,
- AuthorizationAPIMixin,
- )
- from pyramid.url import URLMethodsMixin
- from pyramid.util import (
- InstancePropertyHelper,
- InstancePropertyMixin,
- )
- class TemplateContext(object):
- pass
- class CallbackMethodsMixin(object):
- @reify
- def finished_callbacks(self):
- return deque()
- @reify
- def response_callbacks(self):
- return deque()
- def add_response_callback(self, callback):
- """
- Add a callback to the set of callbacks to be called by the
- :term:`router` at a point after a :term:`response` object is
- successfully created. :app:`Pyramid` does not have a
- global response object: this functionality allows an
- application to register an action to be performed against the
- response once one is created.
- A 'callback' is a callable which accepts two positional
- parameters: ``request`` and ``response``. For example:
- .. code-block:: python
- :linenos:
- def cache_callback(request, response):
- 'Set the cache_control max_age for the response'
- response.cache_control.max_age = 360
- request.add_response_callback(cache_callback)
- Response callbacks are called in the order they're added
- (first-to-most-recently-added). No response callback is
- called if an exception happens in application code, or if the
- response object returned by :term:`view` code is invalid.
- All response callbacks are called *after* the tweens and
- *before* the :class:`pyramid.events.NewResponse` event is sent.
- Errors raised by callbacks are not handled specially. They
- will be propagated to the caller of the :app:`Pyramid`
- router application.
- .. seealso::
- See also :ref:`using_response_callbacks`.
- """
- self.response_callbacks.append(callback)
- def _process_response_callbacks(self, response):
- callbacks = self.response_callbacks
- while callbacks:
- callback = callbacks.popleft()
- callback(self, response)
- def add_finished_callback(self, callback):
- """
- Add a callback to the set of callbacks to be called
- unconditionally by the :term:`router` at the very end of
- request processing.
- ``callback`` is a callable which accepts a single positional
- parameter: ``request``. For example:
- .. code-block:: python
- :linenos:
- import transaction
- def commit_callback(request):
- '''commit or abort the transaction associated with request'''
- if request.exception is not None:
- transaction.abort()
- else:
- transaction.commit()
- request.add_finished_callback(commit_callback)
- Finished callbacks are called in the order they're added (
- first- to most-recently- added). Finished callbacks (unlike
- response callbacks) are *always* called, even if an exception
- happens in application code that prevents a response from
- being generated.
- The set of finished callbacks associated with a request are
- called *very late* in the processing of that request; they are
- essentially the last thing called by the :term:`router`. They
- are called after response processing has already occurred in a
- top-level ``finally:`` block within the router request
- processing code. As a result, mutations performed to the
- ``request`` provided to a finished callback will have no
- meaningful effect, because response processing will have
- already occurred, and the request's scope will expire almost
- immediately after all finished callbacks have been processed.
- Errors raised by finished callbacks are not handled specially.
- They will be propagated to the caller of the :app:`Pyramid`
- router application.
- .. seealso::
- See also :ref:`using_finished_callbacks`.
- """
- self.finished_callbacks.append(callback)
- def _process_finished_callbacks(self):
- callbacks = self.finished_callbacks
- while callbacks:
- callback = callbacks.popleft()
- callback(self)
- @implementer(IRequest)
- class Request(
- BaseRequest,
- URLMethodsMixin,
- CallbackMethodsMixin,
- InstancePropertyMixin,
- LocalizerRequestMixin,
- AuthenticationAPIMixin,
- AuthorizationAPIMixin,
- ):
- """
- A subclass of the :term:`WebOb` Request class. An instance of
- this class is created by the :term:`router` and is provided to a
- view callable (and to other subsystems) as the ``request``
- argument.
- The documentation below (save for the ``add_response_callback`` and
- ``add_finished_callback`` methods, which are defined in this subclass
- itself, and the attributes ``context``, ``registry``, ``root``,
- ``subpath``, ``traversed``, ``view_name``, ``virtual_root`` , and
- ``virtual_root_path``, each of which is added to the request by the
- :term:`router` at request ingress time) are autogenerated from the WebOb
- source code used when this documentation was generated.
- Due to technical constraints, we can't yet display the WebOb
- version number from which this documentation is autogenerated, but
- it will be the 'prevailing WebOb version' at the time of the
- release of this :app:`Pyramid` version. See
- http://webob.org/ for further information.
- """
- exception = None
- exc_info = None
- matchdict = None
- matched_route = None
- request_iface = IRequest
- ResponseClass = Response
- @reify
- def tmpl_context(self):
- # docs-deprecated template context for Pylons-like apps; do not
- # remove.
- return TemplateContext()
- @reify
- def session(self):
- """ Obtain the :term:`session` object associated with this
- request. If a :term:`session factory` has not been registered
- during application configuration, a
- :class:`pyramid.exceptions.ConfigurationError` will be raised"""
- factory = self.registry.queryUtility(ISessionFactory)
- if factory is None:
- raise AttributeError(
- 'No session factory registered '
- '(see the Sessions chapter of the Pyramid documentation)')
- return factory(self)
- @reify
- def response(self):
- """This attribute is actually a "reified" property which returns an
- instance of the :class:`pyramid.response.Response`. class. The
- response object returned does not exist until this attribute is
- accessed. Subsequent accesses will return the same Response object.
- The ``request.response`` API is used by renderers. A render obtains
- the response object it will return from a view that uses that renderer
- by accessing ``request.response``. Therefore, it's possible to use the
- ``request.response`` API to set up a response object with "the
- right" attributes (e.g. by calling ``request.response.set_cookie()``)
- within a view that uses a renderer. Mutations to this response object
- will be preserved in the response sent to the client."""
- response_factory = _get_response_factory(self.registry)
- return response_factory(self)
- def is_response(self, ob):
- """ Return ``True`` if the object passed as ``ob`` is a valid
- response object, ``False`` otherwise."""
- if ob.__class__ is Response:
- return True
- registry = self.registry
- adapted = registry.queryAdapterOrSelf(ob, IResponse)
- if adapted is None:
- return False
- return adapted is ob
- @property
- def json_body(self):
- return json.loads(text_(self.body, self.charset))
- def route_request_iface(name, bases=()):
- # zope.interface treats the __name__ as the __doc__ and changes __name__
- # to None for interfaces that contain spaces if you do not pass a
- # nonempty __doc__ (insane); see
- # zope.interface.interface.Element.__init__ and
- # https://github.com/Pylons/pyramid/issues/232; as a result, always pass
- # __doc__ to the InterfaceClass constructor.
- iface = InterfaceClass('%s_IRequest' % name, bases=bases,
- __doc__="route_request_iface-generated interface")
- # for exception view lookups
- iface.combined = InterfaceClass(
- '%s_combined_IRequest' % name,
- bases=(iface, IRequest),
- __doc__='route_request_iface-generated combined interface')
- return iface
- def add_global_response_headers(request, headerlist):
- def add_headers(request, response):
- for k, v in headerlist:
- response.headerlist.append((k, v))
- request.add_response_callback(add_headers)
- def call_app_with_subpath_as_path_info(request, app):
- # Copy the request. Use the source request's subpath (if it exists) as
- # the new request's PATH_INFO. Set the request copy's SCRIPT_NAME to the
- # prefix before the subpath. Call the application with the new request
- # and return a response.
- #
- # Postconditions:
- # - SCRIPT_NAME and PATH_INFO are empty or start with /
- # - At least one of SCRIPT_NAME or PATH_INFO are set.
- # - SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should
- # be '/').
- environ = request.environ
- script_name = environ.get('SCRIPT_NAME', '')
- path_info = environ.get('PATH_INFO', '/')
- subpath = list(getattr(request, 'subpath', ()))
- new_script_name = ''
- # compute new_path_info
- new_path_info = '/' + '/'.join([native_(x.encode('utf-8'), 'latin-1')
- for x in subpath])
- if new_path_info != '/': # don't want a sole double-slash
- if path_info != '/': # if orig path_info is '/', we're already done
- if path_info.endswith('/'):
- # readd trailing slash stripped by subpath (traversal)
- # conversion
- new_path_info += '/'
- # compute new_script_name
- workback = (script_name + path_info).split('/')
- tmp = []
- while workback:
- if tmp == subpath:
- break
- el = workback.pop()
- if el:
- tmp.insert(0, text_(bytes_(el, 'latin-1'), 'utf-8'))
- # strip all trailing slashes from workback to avoid appending undue slashes
- # to end of script_name
- while workback and (workback[-1] == ''):
- workback = workback[:-1]
- new_script_name = '/'.join(workback)
- new_request = request.copy()
- new_request.environ['SCRIPT_NAME'] = new_script_name
- new_request.environ['PATH_INFO'] = new_path_info
- return new_request.get_response(app)
- def apply_request_extensions(request, extensions=None):
- """Apply request extensions (methods and properties) to an instance of
- :class:`pyramid.interfaces.IRequest`. This method is dependent on the
- ``request`` containing a properly initialized registry.
- After invoking this method, the ``request`` should have the methods
- and properties that were defined using
- :meth:`pyramid.config.Configurator.add_request_method`.
- """
- if extensions is None:
- extensions = request.registry.queryUtility(IRequestExtensions)
- if extensions is not None:
- for name, fn in iteritems_(extensions.methods):
- method = fn.__get__(request, request.__class__)
- setattr(request, name, method)
- InstancePropertyHelper.apply_properties(
- request, extensions.descriptors)