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