Michael Merickel
2018-10-19 d579f2104de139e0b0fc5d6c81aabb2f826e5e54
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
from zope.interface import implementer
 
from pyramid.interfaces import ITweens
 
from pyramid.compat import string_types, is_nonstr_iter
 
from pyramid.exceptions import ConfigurationError
 
from pyramid.tweens import MAIN, INGRESS, EXCVIEW
 
from pyramid.util import is_string_or_iterable, TopologicalSorter
 
from pyramid.config.actions import action_method
 
 
class TweensConfiguratorMixin(object):
    def add_tween(self, tween_factory, under=None, over=None):
        """
        .. versionadded:: 1.2
 
        Add a 'tween factory'.  A :term:`tween` (a contraction of 'between')
        is a bit of code that sits between the Pyramid router's main request
        handling function and the upstream WSGI component that uses
        :app:`Pyramid` as its 'app'.  Tweens are a feature that may be used
        by Pyramid framework extensions, to provide, for example,
        Pyramid-specific view timing support, bookkeeping code that examines
        exceptions before they are returned to the upstream WSGI application,
        or a variety of other features.  Tweens behave a bit like
        :term:`WSGI` 'middleware' but they have the benefit of running in a
        context in which they have access to the Pyramid :term:`application
        registry` as well as the Pyramid rendering machinery.
 
        .. note:: You can view the tween ordering configured into a given
                  Pyramid application by using the ``ptweens``
                  command.  See :ref:`displaying_tweens`.
 
        The ``tween_factory`` argument must be a :term:`dotted Python name`
        to a global object representing the tween factory.
 
        The ``under`` and ``over`` arguments allow the caller of
        ``add_tween`` to provide a hint about where in the tween chain this
        tween factory should be placed when an implicit tween chain is used.
        These hints are only used when an explicit tween chain is not used
        (when the ``pyramid.tweens`` configuration value is not set).
        Allowable values for ``under`` or ``over`` (or both) are:
 
        - ``None`` (the default).
 
        - A :term:`dotted Python name` to a tween factory: a string
          representing the dotted name of a tween factory added in a call to
          ``add_tween`` in the same configuration session.
 
        - One of the constants :attr:`pyramid.tweens.MAIN`,
          :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`.
 
        - An iterable of any combination of the above. This allows the user
          to specify fallbacks if the desired tween is not included, as well
          as compatibility with multiple other tweens.
 
        ``under`` means 'closer to the main Pyramid application than',
        ``over`` means 'closer to the request ingress than'.
 
        For example, calling ``add_tween('myapp.tfactory',
        over=pyramid.tweens.MAIN)`` will attempt to place the tween factory
        represented by the dotted name ``myapp.tfactory`` directly 'above'
        (in ``ptweens`` order) the main Pyramid request handler.
        Likewise, calling ``add_tween('myapp.tfactory',
        over=pyramid.tweens.MAIN, under='mypkg.someothertween')`` will
        attempt to place this tween factory 'above' the main handler but
        'below' (a fictional) 'mypkg.someothertween' tween factory.
 
        If all options for ``under`` (or ``over``) cannot be found in the
        current configuration, it is an error. If some options are specified
        purely for compatibilty with other tweens, just add a fallback of
        MAIN or INGRESS. For example, ``under=('mypkg.someothertween',
        'mypkg.someothertween2', INGRESS)``.  This constraint will require
        the tween to be located under both the 'mypkg.someothertween' tween,
        the 'mypkg.someothertween2' tween, and INGRESS. If any of these is
        not in the current configuration, this constraint will only organize
        itself based on the tweens that are present.
 
        Specifying neither ``over`` nor ``under`` is equivalent to specifying
        ``under=INGRESS``.
 
        Implicit tween ordering is obviously only best-effort.  Pyramid will
        attempt to present an implicit order of tweens as best it can, but
        the only surefire way to get any particular ordering is to use an
        explicit tween order.  A user may always override the implicit tween
        ordering by using an explicit ``pyramid.tweens`` configuration value
        setting.
 
        ``under``, and ``over`` arguments are ignored when an explicit tween
        chain is specified using the ``pyramid.tweens`` configuration value.
 
        For more information, see :ref:`registering_tweens`.
 
        """
        return self._add_tween(
            tween_factory, under=under, over=over, explicit=False
        )
 
    def add_default_tweens(self):
        self.add_tween(EXCVIEW)
 
    @action_method
    def _add_tween(self, tween_factory, under=None, over=None, explicit=False):
 
        if not isinstance(tween_factory, string_types):
            raise ConfigurationError(
                'The "tween_factory" argument to add_tween must be a '
                'dotted name to a globally importable object, not %r'
                % tween_factory
            )
 
        name = tween_factory
 
        if name in (MAIN, INGRESS):
            raise ConfigurationError('%s is a reserved tween name' % name)
 
        tween_factory = self.maybe_dotted(tween_factory)
 
        for t, p in [('over', over), ('under', under)]:
            if p is not None:
                if not is_string_or_iterable(p):
                    raise ConfigurationError(
                        '"%s" must be a string or iterable, not %s' % (t, p)
                    )
 
        if over is INGRESS or is_nonstr_iter(over) and INGRESS in over:
            raise ConfigurationError('%s cannot be over INGRESS' % name)
 
        if under is MAIN or is_nonstr_iter(under) and MAIN in under:
            raise ConfigurationError('%s cannot be under MAIN' % name)
 
        registry = self.registry
        introspectables = []
 
        tweens = registry.queryUtility(ITweens)
        if tweens is None:
            tweens = Tweens()
            registry.registerUtility(tweens, ITweens)
 
        def register():
            if explicit:
                tweens.add_explicit(name, tween_factory)
            else:
                tweens.add_implicit(
                    name, tween_factory, under=under, over=over
                )
 
        discriminator = ('tween', name, explicit)
        tween_type = explicit and 'explicit' or 'implicit'
 
        intr = self.introspectable(
            'tweens', discriminator, name, '%s tween' % tween_type
        )
        intr['name'] = name
        intr['factory'] = tween_factory
        intr['type'] = tween_type
        intr['under'] = under
        intr['over'] = over
        introspectables.append(intr)
        self.action(discriminator, register, introspectables=introspectables)
 
 
@implementer(ITweens)
class Tweens(object):
    def __init__(self):
        self.sorter = TopologicalSorter(
            default_before=None,
            default_after=INGRESS,
            first=INGRESS,
            last=MAIN,
        )
        self.explicit = []
 
    def add_explicit(self, name, factory):
        self.explicit.append((name, factory))
 
    def add_implicit(self, name, factory, under=None, over=None):
        self.sorter.add(name, factory, after=under, before=over)
 
    def implicit(self):
        return self.sorter.sorted()
 
    def __call__(self, handler, registry):
        if self.explicit:
            use = self.explicit
        else:
            use = self.implicit()
        for name, factory in use[::-1]:
            handler = factory(handler, registry)
        return handler