Michael Merickel
2018-10-15 bda1306749c62ef4f11cfe567ed7d56c8ad94240
commit | author | age
f07765 1 import venusian
CM 2
0c29cf 3 from zope.interface import implementer, Interface
a0423a 4
0c1c39 5 from pyramid.interfaces import (
CM 6     IContextFound,
7     INewRequest,
8     INewResponse,
9     IApplicationCreated,
10     IBeforeRender,
d4f5a8 11     IBeforeTraversal,
0c29cf 12 )
MM 13
a0423a 14
f07765 15 class subscriber(object):
95f766 16     """ Decorator activated via a :term:`scan` which treats the function
CM 17     being decorated as an event subscriber for the set of interfaces passed
18     as ``*ifaces`` and the set of predicate terms passed as ``**predicates``
19     to the decorator constructor.
f07765 20
CM 21     For example:
22
23     .. code-block:: python
012b97 24
6067de 25        from pyramid.events import NewRequest
c81aad 26        from pyramid.events import subscriber
f07765 27
6067de 28        @subscriber(NewRequest)
f07765 29        def mysubscriber(event):
CM 30            event.request.foo = 1
31
52a948 32     More than one event type can be passed as a constructor argument.  The
b3a692 33     decorated subscriber will be called for each event type.
012b97 34
f07765 35     .. code-block:: python
012b97 36
6067de 37        from pyramid.events import NewRequest, NewResponse
c81aad 38        from pyramid.events import subscriber
f07765 39
6067de 40        @subscriber(NewRequest, NewResponse)
f07765 41        def mysubscriber(event):
edfc4f 42            print(event)
f07765 43
CM 44     When the ``subscriber`` decorator is used without passing an arguments,
45     the function it decorates is called for every event sent:
46
47     .. code-block:: python
012b97 48
c81aad 49        from pyramid.events import subscriber
f07765 50
CM 51        @subscriber()
52        def mysubscriber(event):
edfc4f 53            print(event)
f07765 54
CM 55     This method will have no effect until a :term:`scan` is performed
56     against the package or module which contains it, ala:
57
58     .. code-block:: python
012b97 59
aff443 60        from pyramid.config import Configurator
f07765 61        config = Configurator()
CM 62        config.scan('somepackage_containing_subscribers')
63
95f766 64     Any ``**predicate`` arguments will be passed along to
CM 65     :meth:`pyramid.config.Configurator.add_subscriber`.  See
66     :ref:`subscriber_predicates` for a description of how predicates can
67     narrow the set of circumstances in which a subscriber will be called.
68
498158 69     Two additional keyword arguments which will be passed to the
MM 70     :term:`venusian` ``attach`` function are ``_depth`` and ``_category``.
71
72     ``_depth`` is provided for people who wish to reuse this class from another
73     decorator. The default value is ``0`` and should be specified relative to
74     the ``subscriber`` invocation. It will be passed in to the
75     :term:`venusian` ``attach`` function as the depth of the callstack when
76     Venusian checks if the decorator is being used in a class or module
77     context. It's not often used, but it can be useful in this circumstance.
78
79     ``_category`` sets the decorator category name. It can be useful in
80     combination with the ``category`` argument of ``scan`` to control which
81     views should be processed.
82
83     See the :py:func:`venusian.attach` function in Venusian for more
84     information about the ``_depth`` and ``_category`` arguments.
85
86     .. versionchanged:: 1.9.1
87        Added the ``_depth`` and ``_category`` arguments.
88
f07765 89     """
0c29cf 90
MM 91     venusian = venusian  # for unit testing
f07765 92
95f766 93     def __init__(self, *ifaces, **predicates):
f07765 94         self.ifaces = ifaces
95f766 95         self.predicates = predicates
498158 96         self.depth = predicates.pop('_depth', 0)
MM 97         self.category = predicates.pop('_category', 'pyramid')
f07765 98
CM 99     def register(self, scanner, name, wrapped):
100         config = scanner.config
52a948 101         for iface in self.ifaces or (Interface,):
95f766 102             config.add_subscriber(wrapped, iface, **self.predicates)
f07765 103
CM 104     def __call__(self, wrapped):
0c29cf 105         self.venusian.attach(
MM 106             wrapped,
107             self.register,
108             category=self.category,
109             depth=self.depth + 1,
110         )
f07765 111         return wrapped
0c29cf 112
f07765 113
3b7334 114 @implementer(INewRequest)
a0423a 115 class NewRequest(object):
8b1f6e 116     """ An instance of this class is emitted as an :term:`event`
fd5ae9 117     whenever :app:`Pyramid` begins to process a new request.  The
56cfe9 118     event instance has an attribute, ``request``, which is a
8f45be 119     :term:`request` object.  This event class implements the
c81aad 120     :class:`pyramid.interfaces.INewRequest` interface."""
0c29cf 121
a0423a 122     def __init__(self, request):
CM 123         self.request = request
0c29cf 124
a0423a 125
3b7334 126 @implementer(INewResponse)
a0423a 127 class NewResponse(object):
8b1f6e 128     """ An instance of this class is emitted as an :term:`event`
fd5ae9 129     whenever any :app:`Pyramid` :term:`view` or :term:`exception
8f45be 130     view` returns a :term:`response`.
eb7ea4 131
CM 132     The instance has two attributes:``request``, which is the request
133     which caused the response, and ``response``, which is the response
134     object returned by a view or renderer.
8f45be 135
0ffb09 136     If the ``response`` was generated by an :term:`exception view`, the
CM 137     request will have an attribute named ``exception``, which is the
138     exception object which caused the exception view to be executed.  If the
139     response was generated by a 'normal' view, this attribute of the request
140     will be ``None``.
8f45be 141
0ffb09 142     This event will not be generated if a response cannot be created due to
CM 143     an exception that is not caught by an exception view (no response is
144     created under this circumstace).
eb7ea4 145
CM 146     This class implements the
c81aad 147     :class:`pyramid.interfaces.INewResponse` interface.
9ec2d6 148
CM 149     .. note::
150
151        Postprocessing a response is usually better handled in a WSGI
152        :term:`middleware` component than in subscriber code that is
c81aad 153        called by a :class:`pyramid.interfaces.INewResponse` event.
CM 154        The :class:`pyramid.interfaces.INewResponse` event exists
9ec2d6 155        almost purely for symmetry with the
c81aad 156        :class:`pyramid.interfaces.INewRequest` event.
9ec2d6 157     """
0c29cf 158
eb7ea4 159     def __init__(self, request, response):
CM 160         self.request = request
a0423a 161         self.response = response
0c29cf 162
faaebc 163
d4f5a8 164 @implementer(IBeforeTraversal)
BJR 165 class BeforeTraversal(object):
499b78 166     """
DS 167     An instance of this class is emitted as an :term:`event` after the
3f34aa 168     :app:`Pyramid` :term:`router` has attempted to find a :term:`route` object
BJR 169     but before any traversal or view code is executed. The instance has an
170     attribute, ``request``, which is the request object generated by
171     :app:`Pyramid`.
499b78 172
3f34aa 173     Notably, the request object **may** have an attribute named
BJR 174     ``matched_route``, which is the matched route if found. If no route
175     matched, this attribute is not available.
499b78 176
d4f5a8 177     This class implements the :class:`pyramid.interfaces.IBeforeTraversal`
499b78 178     interface.
DS 179     """
180
181     def __init__(self, request):
182         self.request = request
0c29cf 183
499b78 184
3b7334 185 @implementer(IContextFound)
8f45be 186 class ContextFound(object):
8b1f6e 187     """ An instance of this class is emitted as an :term:`event` after
fd5ae9 188     the :app:`Pyramid` :term:`router` finds a :term:`context`
8f45be 189     object (after it performs traversal) but before any view code is
CM 190     executed.  The instance has an attribute, ``request``, which is
fd5ae9 191     the request object generated by :app:`Pyramid`.
8f45be 192
CM 193     Notably, the request object will have an attribute named
194     ``context``, which is the context that will be provided to the
195     view which will eventually be called, as well as other attributes
196     attached by context-finding code.
197
198     This class implements the
c81aad 199     :class:`pyramid.interfaces.IContextFound` interface.
8f45be 200
012b97 201     .. note::
M 202
203        As of :app:`Pyramid` 1.0, for backwards compatibility purposes, this
204        event may also be imported as :class:`pyramid.events.AfterTraversal`.
8f45be 205     """
0c29cf 206
49304c 207     def __init__(self, request):
CM 208         self.request = request
8f45be 209
0c29cf 210
MM 211 AfterTraversal = ContextFound  # b/c as of 1.0
212
012b97 213
3b7334 214 @implementer(IApplicationCreated)
3f34aa 215 class ApplicationCreated(object):
8b1f6e 216     """ An instance of this class is emitted as an :term:`event` when
aff443 217     the :meth:`pyramid.config.Configurator.make_wsgi_app` is
640044 218     called.  The instance has an attribute, ``app``, which is an
83df8b 219     instance of the :term:`router` that will handle WSGI requests.
CM 220     This class implements the
c81aad 221     :class:`pyramid.interfaces.IApplicationCreated` interface.
8f45be 222
012b97 223     .. note::
8f45be 224
012b97 225        For backwards compatibility purposes, this class can also be imported as
M 226        :class:`pyramid.events.WSGIApplicationCreatedEvent`.  This was the name
227        of the event class before :app:`Pyramid` 1.0.
8f45be 228     """
0c29cf 229
faaebc 230     def __init__(self, app):
CM 231         self.app = app
49304c 232         self.object = app
51dcd9 233
0c29cf 234
MM 235 WSGIApplicationCreatedEvent = ApplicationCreated  # b/c (as of 1.0)
236
8f45be 237
3b7334 238 @implementer(IBeforeRender)
a76e99 239 class BeforeRender(dict):
CM 240     """
438277 241     Subscribers to this event may introspect and modify the set of
a76e99 242     :term:`renderer globals` before they are passed to a :term:`renderer`.
1a928d 243     This event object itself has a dictionary-like interface that can be used
a76e99 244     for this purpose.  For example::
CM 245
f32f32 246       from pyramid.events import subscriber
5c52da 247       from pyramid.events import BeforeRender
a76e99 248
5c52da 249       @subscriber(BeforeRender)
a76e99 250       def add_global(event):
CM 251           event['mykey'] = 'foo'
252
253     An object of this type is sent as an event just before a :term:`renderer`
c6601f 254     is invoked.
a76e99 255
438277 256     If a subscriber adds a key via ``__setitem__`` that already exists in
CDLG 257     the renderer globals dictionary, it will overwrite the older value there.
258     This can be problematic because event subscribers to the BeforeRender
259     event do not possess any relative ordering.  For maximum interoperability
260     with other third-party subscribers, if you write an event subscriber meant
261     to be used as a BeforeRender subscriber, your subscriber code will need to
262     ensure no value already exists in the renderer globals dictionary before
263     setting an overriding value (which can be done using ``.get`` or
264     ``__contains__`` of the event object).
a76e99 265
a31924 266     The dictionary returned from the view is accessible through the
JC 267     :attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender`
2516df 268     event.
JC 269
270     Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from
271     your view callable, like so::
272
273       from pyramid.view import view_config
274
275       @view_config(renderer='some_renderer')
276       def myview(request):
277           return {'mykey': 'somevalue', 'mykey2': 'somevalue2'}
278
279     :attr:`rendering_val` can be used to access these values from the
280     :class:`~pyramid.events.BeforeRender` object::
a31924 281
JC 282       from pyramid.events import subscriber
283       from pyramid.events import BeforeRender
284
285       @subscriber(BeforeRender)
286       def read_return(event):
2516df 287           # {'mykey': 'somevalue'} is returned from the view
a31924 288           print(event.rendering_val['mykey'])
JC 289
03403e 290     In other words, :attr:`rendering_val` is the (non-system) value returned
CM 291     by a view or passed to ``render*`` as ``value``.  This feature is new in
292     Pyramid 1.2.
a31924 293
b91ca3 294     For a description of the values present in the renderer globals dictionary,
CDLG 295     see :ref:`renderer_system_values`.
9a66aa 296
2033ee 297     .. seealso::
SP 298
299         See also :class:`pyramid.interfaces.IBeforeRender`.
5c52da 300     """
0c29cf 301
9a66aa 302     def __init__(self, system, rendering_val=None):
5c52da 303         dict.__init__(self, system)
9a66aa 304         self.rendering_val = rendering_val