1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
.. _events_chapter:
Using Events
=============
An *event* is an object broadcast by the :mod:`repoze.bfg` framework
at particularly interesting points during the lifetime of your
application. You don't need to use, know about, or care about events
in order to create most :mod:`repoze.bfg` applications, but they can
be useful when you want to do slightly advanced operations, such as
"skinning" a site slightly differently based on, for example, the
hostname used to reach the site.
Events in :mod:`repoze.bfg` are always broadcast by the framework.
They only become useful when you register a *subscriber*. A
subscriber is a function that accepts a single argument named `event`:
.. code-block:: python
:linenos:
def mysubscriber(event):
print event
The above is a subscriber that simply prints the event to the console
when it's called.
The mere existence of a subscriber function, however, is not
sufficient to arrange for it to be called. To arrange for the
subscriber to be called, you'll need to change your :term:`application
registry` by modifying your application's ``configure.zcml``. Here's
an example of a bit of XML you can add to the ``configure.zcml`` file
which registers the above ``mysubscriber`` function, which we assume
lives in a ``subscribers.py`` module within your application:
.. code-block:: xml
:linenos:
<subscriber
for="repoze.bfg.interfaces.INewRequest"
handler=".subscribers.mysubscriber"
/>
The above example means "every time the :mod:`repoze.bfg` framework
emits an event object that supplies an ``INewRequest`` interface, call
the ``mysubscriber`` function with the event object. As you can see,
subscriptions are made to *interfaces*. The event object sent to a
subscriber will always have an interface. You can use the interface
itself to determine what attributes of the event are available.
For example, if you create event listener functions in a
``subscribers.py`` file in your application like so:
.. code-block:: python
:linenos:
def handle_new_request(event):
print 'request', event.request
def handle_new_response(event):
print 'response', event.response
You may configure these functions to be called at the appropriate
times by adding the following to your application's ``configure.zcml``
file:
.. code-block:: xml
:linenos:
<subscriber
for="repoze.bfg.interfaces.INewRequest"
handler=".subscribers.handle_new_request"
/>
<subscriber
for="repoze.bfg.interfaces.INewResponse"
handler=".subscribers.handle_new_response"
/>
This causes the functions as to be registered as event subscribers
within the :term:`application registry` . Under this configuration,
when the application is run, every new request and every response will
be printed to the console. We know that ``INewRequest`` events have a
``request`` attribute, which is a :term:`WebOb` request, because the
interface defined at ``repoze.bfg.interfaces.INewRequest`` says it
must. Likewise, we know that ``INewResponse`` events have a
``response`` attribute, which is a response object constructed by your
application, because the interface defined at
``repoze.bfg.interfaces.INewResponse`` says it must. These particular
interfaces are documented in the :ref:`events_module` API chapter.
.. note::
Usually postprocessing requests is better handled in middleware
components. The ``INewResponse`` event exists purely for symmetry
with ``INewRequest``, really.
The *subscriber* ZCML element takes two values: ``for``, which is the
interface the subscriber is registered for (which limits the events
that the subscriber will receive to those specified by the interface),
and ``handler`` which is a Python dotted-name path to the subscriber
function.
The return value of a subscriber function is ignored.
Using An Event to Vary the Request Type
---------------------------------------
The most common usage of the ``INewRequestEvent`` is to attach an
:term:`interface` to the request to be able to differentiate, for
example, a request issued by a browser from a request issued by a JSON
client. This differentiation makes it possible to register different
views against different ``request_type`` interfaces; for instance,
depending on the presence of a request header, you might return JSON
data.
To do this, you should subscribe an function to the ``INewRequest``
event type, and you should use the ``zope.interface.alsoProvides`` API
within the function to add one or more interfaces to the request
object provided by the event. Here's an example.
.. code-block:: python
:linenos:
from zope.interface import alsoProvides
from zope.interface import Interface
class IJSONRequest(Interface):
""" A request from a JSON client that sets and Accept:
application/json header """
def categorize_request(event):
request = event.request
accept = request.headers.get('accept', '')
if 'application/json' in accept:
alsoProvides(request, IJSONRequest)
Then in your view registration ZCML, if you subscribe
``categorize_request`` for the ``repoze.bfg.interfaces.INewRequest``
type, you can use the ``request_type`` attribute to point at different
view functions depending upon the interface implemented by the
request. For example, if the above subscriber function was
registered, the three view registrations below could be used to point
at separate view functions using separate request type interfaces for
the same model object.
.. code-block:: xml
:linenos:
<subscriber
for="repoze.bfg.interfaces.INewRequest"
handler=".subscribers.categorize_request"
/>
<!-- html default view -->
<view
for=".models.MyModel"
request_type="repoze.bfg.interfaces.IRequest"
view=".views.html_view"/>
<!-- JSON default view -->
<view
for=".models.MyModel"
request_type=".interfaces.IJSONRequest"
view=".views.json_view"/>
The interface ``repoze.bfg.interfaces.IRequest`` is automatically
implemented by every :mod:`repoze.bfg` request, so all requests will
implement that type, and views registered against models which do not
supply a ``request_type`` will be considered to be registered for this
``IRequest`` as a default.
Of course, you are not limited to using the ``Accept`` header to
determine which interfaces to attach to a request. For example, you
might also choose to use hostname
(e.g. ``request.environ.get('HTTP_HOST',
request.environ['SERVER_NAME'])``) in order to "skin" your application
differently based on whether the user should see the "management"
(e.g. "manage.myapp.com") presentation of the application or the
"retail" presentation (e.g. "www.myapp.com").
|