Michael Merickel
2018-10-19 d579f2104de139e0b0fc5d6c81aabb2f826e5e54
commit | author | age
3b7334 1 from zope.interface import implementer
b397ac 2
5bf23f 3 from pyramid.interfaces import ITweens
CM 4
0c29cf 5 from pyramid.compat import string_types, is_nonstr_iter
ee117e 6
5bf23f 7 from pyramid.exceptions import ConfigurationError
ee117e 8
0c29cf 9 from pyramid.tweens import MAIN, INGRESS, EXCVIEW
5bf23f 10
0c29cf 11 from pyramid.util import is_string_or_iterable, TopologicalSorter
52fde9 12
d579f2 13 from pyramid.config.actions import action_method
0c29cf 14
5bf23f 15
CM 16 class TweensConfiguratorMixin(object):
4f070b 17     def add_tween(self, tween_factory, under=None, over=None):
5bf23f 18         """
0b23b3 19         .. versionadded:: 1.2
adfc23 20
5bf23f 21         Add a 'tween factory'.  A :term:`tween` (a contraction of 'between')
CM 22         is a bit of code that sits between the Pyramid router's main request
23         handling function and the upstream WSGI component that uses
24         :app:`Pyramid` as its 'app'.  Tweens are a feature that may be used
25         by Pyramid framework extensions, to provide, for example,
26         Pyramid-specific view timing support, bookkeeping code that examines
27         exceptions before they are returned to the upstream WSGI application,
28         or a variety of other features.  Tweens behave a bit like
29         :term:`WSGI` 'middleware' but they have the benefit of running in a
30         context in which they have access to the Pyramid :term:`application
31         registry` as well as the Pyramid rendering machinery.
32
33         .. note:: You can view the tween ordering configured into a given
cfb2b5 34                   Pyramid application by using the ``ptweens``
5bf23f 35                   command.  See :ref:`displaying_tweens`.
CM 36
37         The ``tween_factory`` argument must be a :term:`dotted Python name`
38         to a global object representing the tween factory.
39
40         The ``under`` and ``over`` arguments allow the caller of
41         ``add_tween`` to provide a hint about where in the tween chain this
42         tween factory should be placed when an implicit tween chain is used.
43         These hints are only used when an explicit tween chain is not used
44         (when the ``pyramid.tweens`` configuration value is not set).
45         Allowable values for ``under`` or ``over`` (or both) are:
46
47         - ``None`` (the default).
48
49         - A :term:`dotted Python name` to a tween factory: a string
50           representing the dotted name of a tween factory added in a call to
51           ``add_tween`` in the same configuration session.
52
53         - One of the constants :attr:`pyramid.tweens.MAIN`,
54           :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`.
55
56         - An iterable of any combination of the above. This allows the user
57           to specify fallbacks if the desired tween is not included, as well
58           as compatibility with multiple other tweens.
a54bc1 59
5bf23f 60         ``under`` means 'closer to the main Pyramid application than',
CM 61         ``over`` means 'closer to the request ingress than'.
62
63         For example, calling ``add_tween('myapp.tfactory',
64         over=pyramid.tweens.MAIN)`` will attempt to place the tween factory
4f070b 65         represented by the dotted name ``myapp.tfactory`` directly 'above'
cfb2b5 66         (in ``ptweens`` order) the main Pyramid request handler.
5bf23f 67         Likewise, calling ``add_tween('myapp.tfactory',
4f070b 68         over=pyramid.tweens.MAIN, under='mypkg.someothertween')`` will
CM 69         attempt to place this tween factory 'above' the main handler but
70         'below' (a fictional) 'mypkg.someothertween' tween factory.
5bf23f 71
CM 72         If all options for ``under`` (or ``over``) cannot be found in the
73         current configuration, it is an error. If some options are specified
74         purely for compatibilty with other tweens, just add a fallback of
4f070b 75         MAIN or INGRESS. For example, ``under=('mypkg.someothertween',
CM 76         'mypkg.someothertween2', INGRESS)``.  This constraint will require
77         the tween to be located under both the 'mypkg.someothertween' tween,
78         the 'mypkg.someothertween2' tween, and INGRESS. If any of these is
79         not in the current configuration, this constraint will only organize
80         itself based on the tweens that are present.
5bf23f 81
CM 82         Specifying neither ``over`` nor ``under`` is equivalent to specifying
83         ``under=INGRESS``.
84
85         Implicit tween ordering is obviously only best-effort.  Pyramid will
86         attempt to present an implicit order of tweens as best it can, but
87         the only surefire way to get any particular ordering is to use an
88         explicit tween order.  A user may always override the implicit tween
89         ordering by using an explicit ``pyramid.tweens`` configuration value
90         setting.
91
4f070b 92         ``under``, and ``over`` arguments are ignored when an explicit tween
CM 93         chain is specified using the ``pyramid.tweens`` configuration value.
5bf23f 94
CM 95         For more information, see :ref:`registering_tweens`.
96
97         """
0c29cf 98         return self._add_tween(
MM 99             tween_factory, under=under, over=over, explicit=False
100         )
5bf23f 101
35b0e3 102     def add_default_tweens(self):
MM 103         self.add_tween(EXCVIEW)
104
79ebb6 105     @action_method
4f070b 106     def _add_tween(self, tween_factory, under=None, over=None, explicit=False):
5bf23f 107
8e606d 108         if not isinstance(tween_factory, string_types):
5bf23f 109             raise ConfigurationError(
CM 110                 'The "tween_factory" argument to add_tween must be a '
0c29cf 111                 'dotted name to a globally importable object, not %r'
MM 112                 % tween_factory
113             )
5bf23f 114
CM 115         name = tween_factory
4f070b 116
CM 117         if name in (MAIN, INGRESS):
118             raise ConfigurationError('%s is a reserved tween name' % name)
119
5bf23f 120         tween_factory = self.maybe_dotted(tween_factory)
CM 121
122         for t, p in [('over', over), ('under', under)]:
123             if p is not None:
124                 if not is_string_or_iterable(p):
125                     raise ConfigurationError(
0c29cf 126                         '"%s" must be a string or iterable, not %s' % (t, p)
MM 127                     )
5bf23f 128
475532 129         if over is INGRESS or is_nonstr_iter(over) and INGRESS in over:
5bf23f 130             raise ConfigurationError('%s cannot be over INGRESS' % name)
CM 131
475532 132         if under is MAIN or is_nonstr_iter(under) and MAIN in under:
5bf23f 133             raise ConfigurationError('%s cannot be under MAIN' % name)
CM 134
135         registry = self.registry
47e1f7 136         introspectables = []
eb2fee 137
5bf23f 138         tweens = registry.queryUtility(ITweens)
CM 139         if tweens is None:
140             tweens = Tweens()
141             registry.registerUtility(tweens, ITweens)
142
eb2fee 143         def register():
CM 144             if explicit:
145                 tweens.add_explicit(name, tween_factory)
146             else:
0c29cf 147                 tweens.add_implicit(
MM 148                     name, tween_factory, under=under, over=over
149                 )
eb2fee 150
87f8d2 151         discriminator = ('tween', name, explicit)
CM 152         tween_type = explicit and 'explicit' or 'implicit'
153
0c29cf 154         intr = self.introspectable(
MM 155             'tweens', discriminator, name, '%s tween' % tween_type
156         )
58c01f 157         intr['name'] = name
87f8d2 158         intr['factory'] = tween_factory
CM 159         intr['type'] = tween_type
160         intr['under'] = under
161         intr['over'] = over
47e1f7 162         introspectables.append(intr)
CM 163         self.action(discriminator, register, introspectables=introspectables)
b397ac 164
0c29cf 165
3b7334 166 @implementer(ITweens)
b397ac 167 class Tweens(object):
CM 168     def __init__(self):
fc3f23 169         self.sorter = TopologicalSorter(
CM 170             default_before=None,
171             default_after=INGRESS,
172             first=INGRESS,
0c29cf 173             last=MAIN,
MM 174         )
b397ac 175         self.explicit = []
CM 176
177     def add_explicit(self, name, factory):
178         self.explicit.append((name, factory))
179
4f070b 180     def add_implicit(self, name, factory, under=None, over=None):
fc3f23 181         self.sorter.add(name, factory, after=under, before=over)
b397ac 182
CM 183     def implicit(self):
fc3f23 184         return self.sorter.sorted()
b397ac 185
CM 186     def __call__(self, handler, registry):
187         if self.explicit:
188             use = self.explicit
189         else:
190             use = self.implicit()
191         for name, factory in use[::-1]:
192             handler = factory(handler, registry)
193         return handler