PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/pyramid/config/tweens.py

https://github.com/archanmishra/pyramid
Python | 267 lines | 236 code | 19 blank | 12 comment | 18 complexity | 927634c318c4a5c59625f9f985b00987 MD5 | raw file
  1. from zope.interface import implements
  2. from pyramid.interfaces import ITweens
  3. from pyramid.exceptions import ConfigurationError
  4. from pyramid.tweens import excview_tween_factory
  5. from pyramid.tweens import MAIN, INGRESS, EXCVIEW
  6. from pyramid.config.util import action_method
  7. class TweensConfiguratorMixin(object):
  8. def add_tween(self, tween_factory, under=None, over=None):
  9. """
  10. .. note:: This feature is new as of Pyramid 1.2.
  11. Add a 'tween factory'. A :term:`tween` (a contraction of 'between')
  12. is a bit of code that sits between the Pyramid router's main request
  13. handling function and the upstream WSGI component that uses
  14. :app:`Pyramid` as its 'app'. Tweens are a feature that may be used
  15. by Pyramid framework extensions, to provide, for example,
  16. Pyramid-specific view timing support, bookkeeping code that examines
  17. exceptions before they are returned to the upstream WSGI application,
  18. or a variety of other features. Tweens behave a bit like
  19. :term:`WSGI` 'middleware' but they have the benefit of running in a
  20. context in which they have access to the Pyramid :term:`application
  21. registry` as well as the Pyramid rendering machinery.
  22. .. note:: You can view the tween ordering configured into a given
  23. Pyramid application by using the ``paster ptweens``
  24. command. See :ref:`displaying_tweens`.
  25. The ``tween_factory`` argument must be a :term:`dotted Python name`
  26. to a global object representing the tween factory.
  27. The ``under`` and ``over`` arguments allow the caller of
  28. ``add_tween`` to provide a hint about where in the tween chain this
  29. tween factory should be placed when an implicit tween chain is used.
  30. These hints are only used when an explicit tween chain is not used
  31. (when the ``pyramid.tweens`` configuration value is not set).
  32. Allowable values for ``under`` or ``over`` (or both) are:
  33. - ``None`` (the default).
  34. - A :term:`dotted Python name` to a tween factory: a string
  35. representing the dotted name of a tween factory added in a call to
  36. ``add_tween`` in the same configuration session.
  37. - One of the constants :attr:`pyramid.tweens.MAIN`,
  38. :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`.
  39. - An iterable of any combination of the above. This allows the user
  40. to specify fallbacks if the desired tween is not included, as well
  41. as compatibility with multiple other tweens.
  42. ``under`` means 'closer to the main Pyramid application than',
  43. ``over`` means 'closer to the request ingress than'.
  44. For example, calling ``add_tween('myapp.tfactory',
  45. over=pyramid.tweens.MAIN)`` will attempt to place the tween factory
  46. represented by the dotted name ``myapp.tfactory`` directly 'above'
  47. (in ``paster ptweens`` order) the main Pyramid request handler.
  48. Likewise, calling ``add_tween('myapp.tfactory',
  49. over=pyramid.tweens.MAIN, under='mypkg.someothertween')`` will
  50. attempt to place this tween factory 'above' the main handler but
  51. 'below' (a fictional) 'mypkg.someothertween' tween factory.
  52. If all options for ``under`` (or ``over``) cannot be found in the
  53. current configuration, it is an error. If some options are specified
  54. purely for compatibilty with other tweens, just add a fallback of
  55. MAIN or INGRESS. For example, ``under=('mypkg.someothertween',
  56. 'mypkg.someothertween2', INGRESS)``. This constraint will require
  57. the tween to be located under both the 'mypkg.someothertween' tween,
  58. the 'mypkg.someothertween2' tween, and INGRESS. If any of these is
  59. not in the current configuration, this constraint will only organize
  60. itself based on the tweens that are present.
  61. Specifying neither ``over`` nor ``under`` is equivalent to specifying
  62. ``under=INGRESS``.
  63. Implicit tween ordering is obviously only best-effort. Pyramid will
  64. attempt to present an implicit order of tweens as best it can, but
  65. the only surefire way to get any particular ordering is to use an
  66. explicit tween order. A user may always override the implicit tween
  67. ordering by using an explicit ``pyramid.tweens`` configuration value
  68. setting.
  69. ``under``, and ``over`` arguments are ignored when an explicit tween
  70. chain is specified using the ``pyramid.tweens`` configuration value.
  71. For more information, see :ref:`registering_tweens`.
  72. """
  73. return self._add_tween(tween_factory, under=under, over=over,
  74. explicit=False)
  75. @action_method
  76. def _add_tween(self, tween_factory, under=None, over=None, explicit=False):
  77. if not isinstance(tween_factory, basestring):
  78. raise ConfigurationError(
  79. 'The "tween_factory" argument to add_tween must be a '
  80. 'dotted name to a globally importable object, not %r' %
  81. tween_factory)
  82. name = tween_factory
  83. if name in (MAIN, INGRESS):
  84. raise ConfigurationError('%s is a reserved tween name' % name)
  85. tween_factory = self.maybe_dotted(tween_factory)
  86. def is_string_or_iterable(v):
  87. if isinstance(v, basestring):
  88. return True
  89. if hasattr(v, '__iter__'):
  90. return True
  91. for t, p in [('over', over), ('under', under)]:
  92. if p is not None:
  93. if not is_string_or_iterable(p):
  94. raise ConfigurationError(
  95. '"%s" must be a string or iterable, not %s' % (t, p))
  96. if over is INGRESS or hasattr(over, '__iter__') and INGRESS in over:
  97. raise ConfigurationError('%s cannot be over INGRESS' % name)
  98. if under is MAIN or hasattr(under, '__iter__') and MAIN in under:
  99. raise ConfigurationError('%s cannot be under MAIN' % name)
  100. registry = self.registry
  101. tweens = registry.queryUtility(ITweens)
  102. if tweens is None:
  103. tweens = Tweens()
  104. registry.registerUtility(tweens, ITweens)
  105. tweens.add_implicit(EXCVIEW, excview_tween_factory, over=MAIN)
  106. def register():
  107. if explicit:
  108. tweens.add_explicit(name, tween_factory)
  109. else:
  110. tweens.add_implicit(name, tween_factory, under=under, over=over)
  111. self.action(('tween', name, explicit), register)
  112. class CyclicDependencyError(Exception):
  113. def __init__(self, cycles):
  114. self.cycles = cycles
  115. def __str__(self):
  116. L = []
  117. cycles = self.cycles
  118. for cycle in cycles:
  119. dependent = cycle
  120. dependees = cycles[cycle]
  121. L.append('%r sorts over %r' % (dependent, dependees))
  122. msg = 'Implicit tween ordering cycle:' + '; '.join(L)
  123. return msg
  124. class Tweens(object):
  125. implements(ITweens)
  126. def __init__(self):
  127. self.explicit = []
  128. self.names = []
  129. self.req_over = set()
  130. self.req_under = set()
  131. self.factories = {}
  132. self.order = []
  133. def add_explicit(self, name, factory):
  134. self.explicit.append((name, factory))
  135. def add_implicit(self, name, factory, under=None, over=None):
  136. self.names.append(name)
  137. self.factories[name] = factory
  138. if under is None and over is None:
  139. under = INGRESS
  140. if under is not None:
  141. if not hasattr(under, '__iter__'):
  142. under = (under,)
  143. self.order += [(u, name) for u in under]
  144. self.req_under.add(name)
  145. if over is not None:
  146. if not hasattr(over, '__iter__'):
  147. over = (over,)
  148. self.order += [(name, o) for o in over]
  149. self.req_over.add(name)
  150. def implicit(self):
  151. order = [(INGRESS, MAIN)]
  152. roots = []
  153. graph = {}
  154. names = [INGRESS, MAIN]
  155. names.extend(self.names)
  156. for a, b in self.order:
  157. order.append((a, b))
  158. def add_node(node):
  159. if not graph.has_key(node):
  160. roots.append(node)
  161. graph[node] = [0] # 0 = number of arcs coming into this node
  162. def add_arc(fromnode, tonode):
  163. graph[fromnode].append(tonode)
  164. graph[tonode][0] += 1
  165. if tonode in roots:
  166. roots.remove(tonode)
  167. for name in names:
  168. add_node(name)
  169. has_over, has_under = set(), set()
  170. for a, b in order:
  171. if a in names and b in names: # deal with missing dependencies
  172. add_arc(a, b)
  173. has_over.add(a)
  174. has_under.add(b)
  175. if not self.req_over.issubset(has_over):
  176. raise ConfigurationError(
  177. 'Detected tweens with no satisfied over dependencies: %s'
  178. % (', '.join(sorted(self.req_over - has_over)))
  179. )
  180. if not self.req_under.issubset(has_under):
  181. raise ConfigurationError(
  182. 'Detected tweens with no satisfied under dependencies: %s'
  183. % (', '.join(sorted(self.req_under - has_under)))
  184. )
  185. sorted_names = []
  186. while roots:
  187. root = roots.pop(0)
  188. sorted_names.append(root)
  189. children = graph[root][1:]
  190. for child in children:
  191. arcs = graph[child][0]
  192. arcs -= 1
  193. graph[child][0] = arcs
  194. if arcs == 0:
  195. roots.insert(0, child)
  196. del graph[root]
  197. if graph:
  198. # loop in input
  199. cycledeps = {}
  200. for k, v in graph.items():
  201. cycledeps[k] = v[1:]
  202. raise CyclicDependencyError(cycledeps)
  203. result = []
  204. for name in sorted_names:
  205. if name in self.names:
  206. result.append((name, self.factories[name]))
  207. return result
  208. def __call__(self, handler, registry):
  209. if self.explicit:
  210. use = self.explicit
  211. else:
  212. use = self.implicit()
  213. for name, factory in use[::-1]:
  214. handler = factory(handler, registry)
  215. return handler