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 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
from zope.interface import implements
from pyramid.interfaces import ITweens
from pyramid.exceptions import ConfigurationError
from pyramid.tweens import excview_tween_factory
from pyramid.tweens import MAIN, INGRESS, EXCVIEW
from pyramid.config.util import action_method
class TweensConfiguratorMixin(object):
def add_tween(self, tween_factory, under=None, over=None):
"""
.. note:: This feature is new as of Pyramid 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 ``paster 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 ``paster 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)
@action_method
def _add_tween(self, tween_factory, under=None, over=None, explicit=False):
if not isinstance(tween_factory, basestring):
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)
def is_string_or_iterable(v):
if isinstance(v, basestring):
return True
if hasattr(v, '__iter__'):
return True
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 hasattr(over, '__iter__') and INGRESS in over:
raise ConfigurationError('%s cannot be over INGRESS' % name)
if under is MAIN or hasattr(under, '__iter__') and MAIN in under:
raise ConfigurationError('%s cannot be under MAIN' % name)
registry = self.registry
tweens = registry.queryUtility(ITweens)
if tweens is None:
tweens = Tweens()
registry.registerUtility(tweens, ITweens)
tweens.add_implicit(EXCVIEW, excview_tween_factory, over=MAIN)
def register():
if explicit:
tweens.add_explicit(name, tween_factory)
else:
tweens.add_implicit(name, tween_factory, under=under, over=over)
self.action(('tween', name, explicit), register)
class CyclicDependencyError(Exception):
def __init__(self, cycles):
self.cycles = cycles
def __str__(self):
L = []
cycles = self.cycles
for cycle in cycles:
dependent = cycle
dependees = cycles[cycle]
L.append('%r sorts over %r' % (dependent, dependees))
msg = 'Implicit tween ordering cycle:' + '; '.join(L)
return msg
class Tweens(object):
implements(ITweens)
def __init__(self):
self.explicit = []
self.names = []
self.req_over = set()
self.req_under = set()
self.factories = {}
self.order = []
def add_explicit(self, name, factory):
self.explicit.append((name, factory))
def add_implicit(self, name, factory, under=None, over=None):
self.names.append(name)
self.factories[name] = factory
if under is None and over is None:
under = INGRESS
if under is not None:
if not hasattr(under, '__iter__'):
under = (under,)
self.order += [(u, name) for u in under]
self.req_under.add(name)
if over is not None:
if not hasattr(over, '__iter__'):
over = (over,)
self.order += [(name, o) for o in over]
self.req_over.add(name)
def implicit(self):
order = [(INGRESS, MAIN)]
roots = []
graph = {}
names = [INGRESS, MAIN]
names.extend(self.names)
for a, b in self.order:
order.append((a, b))
def add_node(node):
if not graph.has_key(node):
roots.append(node)
graph[node] = [0] # 0 = number of arcs coming into this node
def add_arc(fromnode, tonode):
graph[fromnode].append(tonode)
graph[tonode][0] += 1
if tonode in roots:
roots.remove(tonode)
for name in names:
add_node(name)
has_over, has_under = set(), set()
for a, b in order:
if a in names and b in names: # deal with missing dependencies
add_arc(a, b)
has_over.add(a)
has_under.add(b)
if not self.req_over.issubset(has_over):
raise ConfigurationError(
'Detected tweens with no satisfied over dependencies: %s'
% (', '.join(sorted(self.req_over - has_over)))
)
if not self.req_under.issubset(has_under):
raise ConfigurationError(
'Detected tweens with no satisfied under dependencies: %s'
% (', '.join(sorted(self.req_under - has_under)))
)
sorted_names = []
while roots:
root = roots.pop(0)
sorted_names.append(root)
children = graph[root][1:]
for child in children:
arcs = graph[child][0]
arcs -= 1
graph[child][0] = arcs
if arcs == 0:
roots.insert(0, child)
del graph[root]
if graph:
# loop in input
cycledeps = {}
for k, v in graph.items():
cycledeps[k] = v[1:]
raise CyclicDependencyError(cycledeps)
result = []
for name in sorted_names:
if name in self.names:
result.append((name, self.factories[name]))
return result
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
|