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