commit | author | age
|
27190e
|
1 |
from webob import Response as WebobResponse |
CM |
2 |
|
95f766
|
3 |
from functools import update_wrapper |
CM |
4 |
|
5bf23f
|
5 |
from zope.interface import Interface |
CM |
6 |
|
0c29cf
|
7 |
from pyramid.interfaces import IResponse, ITraverser, IResourceURL |
5bf23f
|
8 |
|
52fde9
|
9 |
from pyramid.util import takes_one_arg |
MM |
10 |
|
d579f2
|
11 |
from pyramid.config.actions import action_method |
95f766
|
12 |
|
5bf23f
|
13 |
|
CM |
14 |
class AdaptersConfiguratorMixin(object): |
|
15 |
@action_method |
95f766
|
16 |
def add_subscriber(self, subscriber, iface=None, **predicates): |
5bf23f
|
17 |
"""Add an event :term:`subscriber` for the event stream |
95f766
|
18 |
implied by the supplied ``iface`` interface. |
CM |
19 |
|
|
20 |
The ``subscriber`` argument represents a callable object (or a |
|
21 |
:term:`dotted Python name` which identifies a callable); it will be |
|
22 |
called with a single object ``event`` whenever :app:`Pyramid` emits |
|
23 |
an :term:`event` associated with the ``iface``, which may be an |
|
24 |
:term:`interface` or a class or a :term:`dotted Python name` to a |
|
25 |
global object representing an interface or a class. |
|
26 |
|
|
27 |
Using the default ``iface`` value, ``None`` will cause the subscriber |
|
28 |
to be registered for all event types. See :ref:`events_chapter` for |
|
29 |
more information about events and subscribers. |
|
30 |
|
|
31 |
Any number of predicate keyword arguments may be passed in |
|
32 |
``**predicates``. Each predicate named will narrow the set of |
bf38db
|
33 |
circumstances in which the subscriber will be invoked. Each named |
95f766
|
34 |
predicate must have been registered via |
CM |
35 |
:meth:`pyramid.config.Configurator.add_subscriber_predicate` before it |
|
36 |
can be used. See :ref:`subscriber_predicates` for more information. |
|
37 |
|
0b23b3
|
38 |
.. versionadded:: 1.4 |
TL |
39 |
The ``**predicates`` argument. |
95f766
|
40 |
""" |
5bf23f
|
41 |
dotted = self.maybe_dotted |
CM |
42 |
subscriber, iface = dotted(subscriber), dotted(iface) |
|
43 |
if iface is None: |
|
44 |
iface = (Interface,) |
|
45 |
if not isinstance(iface, (tuple, list)): |
|
46 |
iface = (iface,) |
95f766
|
47 |
|
5bf23f
|
48 |
def register(): |
405213
|
49 |
predlist = self.get_predlist('subscriber') |
95f766
|
50 |
order, preds, phash = predlist.make(self, **predicates) |
28fc3d
|
51 |
|
0c29cf
|
52 |
derived_predicates = [self._derive_predicate(p) for p in preds] |
28fc3d
|
53 |
derived_subscriber = self._derive_subscriber( |
0c29cf
|
54 |
subscriber, derived_predicates |
MM |
55 |
) |
28fc3d
|
56 |
|
CM |
57 |
intr.update( |
0c29cf
|
58 |
{ |
MM |
59 |
'phash': phash, |
|
60 |
'order': order, |
|
61 |
'predicates': preds, |
|
62 |
'derived_predicates': derived_predicates, |
|
63 |
'derived_subscriber': derived_subscriber, |
|
64 |
} |
|
65 |
) |
28fc3d
|
66 |
|
95f766
|
67 |
self.registry.registerHandler(derived_subscriber, iface) |
0c29cf
|
68 |
|
a31280
|
69 |
intr = self.introspectable( |
CM |
70 |
'subscribers', |
|
71 |
id(subscriber), |
|
72 |
self.object_description(subscriber), |
0c29cf
|
73 |
'subscriber', |
MM |
74 |
) |
|
75 |
|
3b5ccb
|
76 |
intr['subscriber'] = subscriber |
CM |
77 |
intr['interfaces'] = iface |
0c29cf
|
78 |
|
3b5ccb
|
79 |
self.action(None, register, introspectables=(intr,)) |
5bf23f
|
80 |
return subscriber |
95f766
|
81 |
|
28fc3d
|
82 |
def _derive_predicate(self, predicate): |
CM |
83 |
derived_predicate = predicate |
|
84 |
|
|
85 |
if eventonly(predicate): |
0c29cf
|
86 |
|
28fc3d
|
87 |
def derived_predicate(*arg): |
CM |
88 |
return predicate(arg[0]) |
0c29cf
|
89 |
|
28fc3d
|
90 |
# seems pointless to try to fix __doc__, __module__, etc as |
CM |
91 |
# predicate will invariably be an instance |
|
92 |
|
|
93 |
return derived_predicate |
|
94 |
|
95f766
|
95 |
def _derive_subscriber(self, subscriber, predicates): |
28fc3d
|
96 |
derived_subscriber = subscriber |
CM |
97 |
|
|
98 |
if eventonly(subscriber): |
0c29cf
|
99 |
|
28fc3d
|
100 |
def derived_subscriber(*arg): |
CM |
101 |
return subscriber(arg[0]) |
0c29cf
|
102 |
|
28fc3d
|
103 |
if hasattr(subscriber, '__name__'): |
CM |
104 |
update_wrapper(derived_subscriber, subscriber) |
|
105 |
|
95f766
|
106 |
if not predicates: |
28fc3d
|
107 |
return derived_subscriber |
CM |
108 |
|
cc33a5
|
109 |
def subscriber_wrapper(*arg): |
28fc3d
|
110 |
# We need to accept *arg and pass it along because zope subscribers |
CM |
111 |
# are designed awkwardly. Notification via |
|
112 |
# registry.adapter.subscribers will always call an associated |
|
113 |
# subscriber with all of the objects involved in the subscription |
|
114 |
# lookup, despite the fact that the event sender always has the |
|
115 |
# option to attach those objects to the event object itself, and |
|
116 |
# almost always does. |
|
117 |
# |
|
118 |
# The "eventonly" jazz sprinkled in this function and related |
|
119 |
# functions allows users to define subscribers and predicates which |
|
120 |
# accept only an event argument without needing to accept the rest |
|
121 |
# of the adaptation arguments. Had I been smart enough early on to |
|
122 |
# use .subscriptions to find the subscriber functions in order to |
|
123 |
# call them manually with a single "event" argument instead of |
|
124 |
# relying on .subscribers to both find and call them implicitly |
|
125 |
# with all args, the eventonly hack would not have been required. |
|
126 |
# At this point, though, using .subscriptions and manual execution |
|
127 |
# is not possible without badly breaking backwards compatibility. |
cc33a5
|
128 |
if all((predicate(*arg) for predicate in predicates)): |
28fc3d
|
129 |
return derived_subscriber(*arg) |
CM |
130 |
|
95f766
|
131 |
if hasattr(subscriber, '__name__'): |
CM |
132 |
update_wrapper(subscriber_wrapper, subscriber) |
28fc3d
|
133 |
|
95f766
|
134 |
return subscriber_wrapper |
0c29cf
|
135 |
|
95f766
|
136 |
@action_method |
0c29cf
|
137 |
def add_subscriber_predicate( |
MM |
138 |
self, name, factory, weighs_more_than=None, weighs_less_than=None |
|
139 |
): |
95f766
|
140 |
""" |
40dbf4
|
141 |
.. versionadded:: 1.4 |
TL |
142 |
|
95f766
|
143 |
Adds a subscriber predicate factory. The associated subscriber |
CM |
144 |
predicate can later be named as a keyword argument to |
|
145 |
:meth:`pyramid.config.Configurator.add_subscriber` in the |
420756
|
146 |
``**predicates`` anonymous keyword argument dictionary. |
95f766
|
147 |
|
CM |
148 |
``name`` should be the name of the predicate. It must be a valid |
|
149 |
Python identifier (it will be used as a ``**predicates`` keyword |
|
150 |
argument to :meth:`~pyramid.config.Configurator.add_subscriber`). |
|
151 |
|
d71aca
|
152 |
``factory`` should be a :term:`predicate factory` or :term:`dotted |
BJR |
153 |
Python name` which refers to a predicate factory. |
95f766
|
154 |
|
CM |
155 |
See :ref:`subscriber_predicates` for more information. |
|
156 |
|
|
157 |
""" |
|
158 |
self._add_predicate( |
|
159 |
'subscriber', |
|
160 |
name, |
|
161 |
factory, |
|
162 |
weighs_more_than=weighs_more_than, |
0c29cf
|
163 |
weighs_less_than=weighs_less_than, |
MM |
164 |
) |
5bf23f
|
165 |
|
CM |
166 |
@action_method |
|
167 |
def add_response_adapter(self, adapter, type_or_iface): |
|
168 |
""" When an object of type (or interface) ``type_or_iface`` is |
|
169 |
returned from a view callable, Pyramid will use the adapter |
|
170 |
``adapter`` to convert it into an object which implements the |
|
171 |
:class:`pyramid.interfaces.IResponse` interface. If ``adapter`` is |
|
172 |
None, an object returned of type (or interface) ``type_or_iface`` |
|
173 |
will itself be used as a response object. |
|
174 |
|
|
175 |
``adapter`` and ``type_or_interface`` may be Python objects or |
|
176 |
strings representing dotted names to importable Python global |
|
177 |
objects. |
|
178 |
|
|
179 |
See :ref:`using_iresponse` for more information.""" |
|
180 |
adapter = self.maybe_dotted(adapter) |
|
181 |
type_or_iface = self.maybe_dotted(type_or_iface) |
0c29cf
|
182 |
|
5bf23f
|
183 |
def register(): |
CM |
184 |
reg = self.registry |
|
185 |
if adapter is None: |
|
186 |
reg.registerSelfAdapter((type_or_iface,), IResponse) |
|
187 |
else: |
|
188 |
reg.registerAdapter(adapter, (type_or_iface,), IResponse) |
0c29cf
|
189 |
|
3b5ccb
|
190 |
discriminator = (IResponse, type_or_iface) |
5e92f3
|
191 |
intr = self.introspectable( |
CM |
192 |
'response adapters', |
|
193 |
discriminator, |
8b6f09
|
194 |
self.object_description(adapter), |
0c29cf
|
195 |
'response adapter', |
MM |
196 |
) |
3b5ccb
|
197 |
intr['adapter'] = adapter |
CM |
198 |
intr['type'] = type_or_iface |
|
199 |
self.action(discriminator, register, introspectables=(intr,)) |
5bf23f
|
200 |
|
27190e
|
201 |
def add_default_response_adapters(self): |
5bf23f
|
202 |
# cope with WebOb response objects that aren't decorated with IResponse |
27190e
|
203 |
self.add_response_adapter(None, WebobResponse) |
e0551c
|
204 |
|
CM |
205 |
@action_method |
|
206 |
def add_traverser(self, adapter, iface=None): |
|
207 |
""" |
|
208 |
The superdefault :term:`traversal` algorithm that :app:`Pyramid` uses |
|
209 |
is explained in :ref:`traversal_algorithm`. Though it is rarely |
|
210 |
necessary, this default algorithm can be swapped out selectively for |
|
211 |
a different traversal pattern via configuration. The section |
|
212 |
entitled :ref:`changing_the_traverser` details how to create a |
|
213 |
traverser class. |
|
214 |
|
|
215 |
For example, to override the superdefault traverser used by Pyramid, |
|
216 |
you might do something like this: |
|
217 |
|
|
218 |
.. code-block:: python |
|
219 |
|
|
220 |
from myapp.traversal import MyCustomTraverser |
|
221 |
config.add_traverser(MyCustomTraverser) |
|
222 |
|
|
223 |
This would cause the Pyramid superdefault traverser to never be used; |
b32e46
|
224 |
instead all traversal would be done using your ``MyCustomTraverser`` |
e0551c
|
225 |
class, no matter which object was returned by the :term:`root |
CM |
226 |
factory` of this application. Note that we passed no arguments to |
|
227 |
the ``iface`` keyword parameter. The default value of ``iface``, |
|
228 |
``None`` represents that the registered traverser should be used when |
|
229 |
no other more specific traverser is available for the object returned |
|
230 |
by the root factory. |
|
231 |
|
|
232 |
However, more than one traversal algorithm can be active at the same |
|
233 |
time. The traverser used can depend on the result of the :term:`root |
|
234 |
factory`. For instance, if your root factory returns more than one |
|
235 |
type of object conditionally, you could claim that an alternate |
71d2be
|
236 |
traverser adapter should be used against one particular class or |
e0551c
|
237 |
interface returned by that root factory. When the root factory |
CM |
238 |
returned an object that implemented that class or interface, a custom |
|
239 |
traverser would be used. Otherwise, the default traverser would be |
|
240 |
used. The ``iface`` argument represents the class of the object that |
|
241 |
the root factory might return or an :term:`interface` that the object |
|
242 |
might implement. |
|
243 |
|
|
244 |
To use a particular traverser only when the root factory returns a |
|
245 |
particular class: |
|
246 |
|
|
247 |
.. code-block:: python |
|
248 |
|
|
249 |
config.add_traverser(MyCustomTraverser, MyRootClass) |
|
250 |
|
|
251 |
When more than one traverser is active, the "most specific" traverser |
|
252 |
will be used (the one that matches the class or interface of the |
|
253 |
value returned by the root factory most closely). |
|
254 |
|
|
255 |
Note that either ``adapter`` or ``iface`` can be a :term:`dotted |
|
256 |
Python name` or a Python object. |
|
257 |
|
|
258 |
See :ref:`changing_the_traverser` for more information. |
|
259 |
""" |
|
260 |
iface = self.maybe_dotted(iface) |
25c64c
|
261 |
adapter = self.maybe_dotted(adapter) |
0c29cf
|
262 |
|
e0551c
|
263 |
def register(iface=iface): |
CM |
264 |
if iface is None: |
|
265 |
iface = Interface |
|
266 |
self.registry.registerAdapter(adapter, (iface,), ITraverser) |
0c29cf
|
267 |
|
e0551c
|
268 |
discriminator = ('traverser', iface) |
CM |
269 |
intr = self.introspectable( |
0c29cf
|
270 |
'traversers', |
e0551c
|
271 |
discriminator, |
CM |
272 |
'traverser for %r' % iface, |
|
273 |
'traverser', |
0c29cf
|
274 |
) |
e0551c
|
275 |
intr['adapter'] = adapter |
CM |
276 |
intr['iface'] = iface |
|
277 |
self.action(discriminator, register, introspectables=(intr,)) |
|
278 |
|
|
279 |
@action_method |
8b59f6
|
280 |
def add_resource_url_adapter(self, adapter, resource_iface=None): |
e0551c
|
281 |
""" |
0b23b3
|
282 |
.. versionadded:: 1.3 |
TL |
283 |
|
e0551c
|
284 |
When you add a traverser as described in |
CM |
285 |
:ref:`changing_the_traverser`, it's convenient to continue to use the |
|
286 |
:meth:`pyramid.request.Request.resource_url` API. However, since the |
|
287 |
way traversal is done may have been modified, the URLs that |
|
288 |
``resource_url`` generates by default may be incorrect when resources |
|
289 |
are returned by a custom traverser. |
|
290 |
|
|
291 |
If you've added a traverser, you can change how |
|
292 |
:meth:`~pyramid.request.Request.resource_url` generates a URL for a |
|
293 |
specific type of resource by calling this method. |
|
294 |
|
|
295 |
The ``adapter`` argument represents a class that implements the |
|
296 |
:class:`~pyramid.interfaces.IResourceURL` interface. The class |
|
297 |
constructor should accept two arguments in its constructor (the |
|
298 |
resource and the request) and the resulting instance should provide |
|
299 |
the attributes detailed in that interface (``virtual_path`` and |
|
300 |
``physical_path``, in particular). |
|
301 |
|
|
302 |
The ``resource_iface`` argument represents a class or interface that |
|
303 |
the resource should possess for this url adapter to be used when |
|
304 |
:meth:`pyramid.request.Request.resource_url` looks up a resource url |
|
305 |
adapter. If ``resource_iface`` is not passed, or it is passed as |
8b59f6
|
306 |
``None``, the url adapter will be used for every type of resource. |
e0551c
|
307 |
|
CM |
308 |
See :ref:`changing_resource_url` for more information. |
|
309 |
""" |
|
310 |
adapter = self.maybe_dotted(adapter) |
|
311 |
resource_iface = self.maybe_dotted(resource_iface) |
0c29cf
|
312 |
|
8b59f6
|
313 |
def register(resource_iface=resource_iface): |
e0551c
|
314 |
if resource_iface is None: |
CM |
315 |
resource_iface = Interface |
|
316 |
self.registry.registerAdapter( |
0c29cf
|
317 |
adapter, (resource_iface, Interface), IResourceURL |
MM |
318 |
) |
|
319 |
|
8b59f6
|
320 |
discriminator = ('resource url adapter', resource_iface) |
e0551c
|
321 |
intr = self.introspectable( |
0c29cf
|
322 |
'resource url adapters', |
e0551c
|
323 |
discriminator, |
8b59f6
|
324 |
'resource url adapter for resource iface %r' % resource_iface, |
e0551c
|
325 |
'resource url adapter', |
0c29cf
|
326 |
) |
e0551c
|
327 |
intr['adapter'] = adapter |
CM |
328 |
intr['resource_iface'] = resource_iface |
|
329 |
self.action(discriminator, register, introspectables=(intr,)) |
|
330 |
|
0c29cf
|
331 |
|
28fc3d
|
332 |
def eventonly(callee): |
CM |
333 |
return takes_one_arg(callee, argname='event') |