Michael Merickel
2018-10-26 4149922e64aecf2a213f8efb120cd2d61fed3eb7
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')