summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-08-08 23:27:21 -0400
committerChris McDonough <chrism@plope.com>2011-08-08 23:27:21 -0400
commit932fc55e19b47ee670fa608c268a35738e499487 (patch)
tree113f47df27536eb7b069594cbd482458f3d5b82c
parent7a83ba1c6d997829f13b20676cbaaf0a6ee488ab (diff)
parent05f610e6ed66f8d5aca9d77ae0748feb0c8f8479 (diff)
downloadpyramid-932fc55e19b47ee670fa608c268a35738e499487.tar.gz
pyramid-932fc55e19b47ee670fa608c268a35738e499487.tar.bz2
pyramid-932fc55e19b47ee670fa608c268a35738e499487.zip
Merge branch 'topotween'
-rw-r--r--docs/api/tweens.rst17
-rw-r--r--docs/glossary.rst14
-rw-r--r--docs/narr/commandline.rst74
-rw-r--r--docs/narr/hooks.rst208
-rw-r--r--pyramid/config.py158
-rw-r--r--pyramid/paster.py52
-rw-r--r--pyramid/router.py230
-rw-r--r--pyramid/tests/test_config.py117
-rw-r--r--pyramid/tests/test_paster.py33
-rw-r--r--pyramid/tests/test_router.py4
-rw-r--r--pyramid/tests/test_tweens.py286
-rw-r--r--pyramid/tweens.py175
12 files changed, 1020 insertions, 348 deletions
diff --git a/docs/api/tweens.rst b/docs/api/tweens.rst
index 5fc15cb00..ddacd2cde 100644
--- a/docs/api/tweens.rst
+++ b/docs/api/tweens.rst
@@ -6,3 +6,20 @@
.. automodule:: pyramid.tweens
.. autofunction:: excview_tween_factory
+
+ .. attribute:: MAIN
+
+ Constant representing the main Pyramid handling function, for use in
+ ``under`` and ``over`` arguments to
+ :meth:`pyramid.config.Configurator.add_tween`.
+
+ .. attribute:: INGRESS
+
+ Constant representing the request ingress, for use in ``under`` and
+ ``over`` arguments to :meth:`pyramid.config.Configurator.add_tween`.
+
+ .. attribute:: EXCVIEW
+
+ Constant representing the exception view tween, for use in ``under``
+ and ``over`` arguments to
+ :meth:`pyramid.config.Configurator.add_tween`.
diff --git a/docs/glossary.rst b/docs/glossary.rst
index ccb62bbc8..f0ad81ded 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -920,11 +920,13 @@ Glossary
tween
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'. A tween 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. Tweens behave a bit like
- :mod:`WSGI` 'middleware' but they have the benefit of running in a
+ :app:`Pyramid` as its 'app'. The word "tween" is a contraction of
+ "between". A tween 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 :mod:`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.
+ registry` as well as the Pyramid rendering machinery. See
+ :ref:`registering_tweens`.
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index 6f969196f..0f23c153c 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -306,15 +306,16 @@ is executed.
Displaying "Tweens"
-------------------
-A user can get a representation of both the implicit :term:`tween` ordering
-(the ordering specified by calls to
-:meth:`pyramid.config.Configurator.add_tween`) and the explicit tween
-ordering (specified by the ``pyramid.tweens`` configuration setting)
-orderings using the ``paster ptweens`` command. Handler factories which are
-functions or classes will show up as a standard Python dotted name in the
-``paster ptweens`` output. Tween factories which are *instances* will show
-their module and class name; the Python object id of the instance will be
-appended.
+A :term:`tween` is a bit of code that sits between the main Pyramid
+application request handler and the WSGI application which calls it. A user
+can get a representation of both the implicit tween ordering (the ordering
+specified by calls to :meth:`pyramid.config.Configurator.add_tween`) and the
+explicit tween ordering (specified by the ``pyramid.tweens`` configuration
+setting) orderings using the ``paster ptweens`` command. Handler factories
+which are functions or classes will show up as a standard Python dotted name
+in the ``paster ptweens`` output. Tween factories which are *instances* will
+show their module and class name; the Python object id of the instance will
+be appended.
For example, here's the ``paster pwteens`` command run against a system
configured without any explicit tweens:
@@ -322,12 +323,17 @@ configured without any explicit tweens:
.. code-block:: text
:linenos:
- [chrism@thinko starter]$ ../bin/paster ptweens development.ini
+ [chrism@thinko pyramid]$ paster ptweens development.ini
"pyramid.tweens" config value NOT set (implicitly ordered tweens used)
- Position Name
- -------- ----
- 0 pyramid.router.excview_tween_factory
+ Implicit Tween Chain
+
+ Position Name Alias
+ -------- ---- -----
+ - - INGRESS
+ 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory pdbt
+ 1 pyramid.tweens.excview_tween_factory excview
+ - - MAIN
Here's the ``paster pwteens`` command run against a system configured *with*
explicit tweens defined in its ``development.ini`` file:
@@ -335,22 +341,27 @@ explicit tweens defined in its ``development.ini`` file:
.. code-block:: text
:linenos:
- [chrism@thinko starter]$ ../bin/paster ptweens development.ini
+ [chrism@thinko pyramid]$ paster ptweens development.ini
"pyramid.tweens" config value set (explicitly ordered tweens used)
Explicit Tween Chain (used)
- Position Name
- -------- ----
- 0 pyramid.tweens.excview_tween_factory
- 1 starter.tween_factory1
- 2 starter.tween_factory2
+ Position Name
+ -------- ----
+ - INGRESS
+ 0 starter.tween_factory2
+ 1 starter.tween_factory1
+ 2 pyramid.tweens.excview_tween_factory
+ - MAIN
Implicit Tween Chain (not used)
- Position Name
- -------- ----
- 0 pyramid.tweens.excview_tween_factory
+ Position Name Alias
+ -------- ---- -----
+ - - INGRESS
+ 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory pdbt
+ 1 pyramid.tweens.excview_tween_factory excview
+ - - MAIN
Here's the application configuration section of the ``development.ini`` used
by the above ``paster ptweens`` command which reprorts that the explicit
@@ -361,15 +372,18 @@ tween chain is used:
[app:starter]
use = egg:starter
- pyramid.reload_templates = true
- pyramid.debug_authorization = false
- pyramid.debug_notfound = false
- pyramid.debug_routematch = false
- pyramid.debug_templates = true
- pyramid.default_locale_name = en
- pyramid.tweens = pyramid.tweens.excview_tween_factory
+ reload_templates = true
+ debug_authorization = false
+ debug_notfound = false
+ debug_routematch = false
+ debug_templates = true
+ default_locale_name = en
+ pyramid.include = pyramid_debugtoolbar
+ pyramid.tweens = starter.tween_factory2
starter.tween_factory1
- starter.tween_factory2
+ pyramid.tweens.excview_tween_factory
+
+See :ref:`registering_tweens` for more information about tweens.
.. _writing_a_script:
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 889e8d6d8..f7ee82821 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -836,11 +836,11 @@ Registering "Tweens"
.. note:: Tweens are a feature which were added in Pyramid 1.1.1. They are
not available in any previous version.
-A :term:`tween` (think: "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". This is a feature that may
-be used by Pyramid framework extensions, to provide, for example,
-Pyramid-specific view timing support bookkeeping code that examines
+A :term:`tween` (a contraction of the word "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". This is 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. 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
@@ -857,8 +857,8 @@ Configurator. A tween factory must return a tween when it is called.
A tween is a callable which accepts a :term:`request` object and returns a
two-tuple a :term:`response` object.
-Once you've created a tween factory, you can register it using the
-:meth:`pyramid.config.Configurator.add_tween` method.
+Once you've created a tween factory, you can register it into the implicit
+tween chain using the :meth:`pyramid.config.Configurator.add_tween` method.
Here's an example creating a tween factory and registering it:
@@ -897,30 +897,145 @@ The ``request`` argument to a tween will be the request created by Pyramid's
router when it receives a WSGI request.
If more than one call to :meth:`pyramid.config.Configurator.add_tween` is
-made within a single application configuration, the added tweens will be
-chained together. The first tween factory added will be called with the
-default Pyramid request handler as its ``handler`` argument, the second tween
-factory added will be called with the result of the first tween factory as
-its ``handler`` argument, and so on, ad infinitum. The Pyramid router will
-use the outermost tween produced by this chain (the tween generated by the
-very last tween factory added) as its request handler function.
-
-Pyramid will prevent the same tween factory from being added to the implicit
-tween chain more than once using configuration conflict detection. If you
-wish to add the same tween factory more than once in a configuration, you
-should either: a) use a tween factory that is an instance rather than a
-function or class, b) use a function or class as a tween factory with the
-same logic as the other tween factory it conflicts with but with a different
-``__name__`` attribute or c) call :meth:`pyramid.config.Configurator.commit`
-between calls to :meth:`pyramid.config.Configurator.add_tween`.
-
-By default, the ordering of the chain is controlled entirely by the relative
-ordering of calls to :meth:`pyramid.config.Configurator.add_tween`. However,
-the deploying user can override tween inclusion and ordering entirely in his
-Pyramid configuration using the ``pyramid.tweens`` settings value. When
-used, this settings value will be a list of Python dotted names which imply
-the ordering (and inclusion) of tween factories in the tween chain. For
-example:
+made within a single application configuration, the tweens will be chained
+together at application startup time. The *first* tween factory added via
+``add_tween`` will be called with the Pyramid exception view tween factory as
+its ``handler`` argument, then the tween factory added directly after that
+one will be called with the result of the first tween factory as its
+``handler`` argument, and so on, ad infinitum until all tween factories have
+been called. The Pyramid router will use the outermost tween produced by this
+chain (the tween generated by the very last tween factory added) as its
+request handler function. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ config = Configurator()
+ config.add_tween('myapp.tween_factory1')
+ config.add_tween('myapp.tween_factory2')
+
+The above example will generate an implicit tween chain that looks like
+this::
+
+ INGRESS (implicit)
+ myapp.tween_factory2
+ myapp.tween_factory1
+ pyramid.tweens.excview_tween_factory (implicit)
+ MAIN (implicit)
+
+By default, as described above, the ordering of the chain is controlled
+entirely by the relative ordering of calls to
+:meth:`pyramid.config.Configurator.add_tween`. However, the caller of
+add_tween can provide an optional hint that can influence the implicit tween
+chain ordering by supplying ``under`` or ``over`` (or both) arguments to
+:meth:`~pyramid.config.Configurator.add_tween`. These hints are only used
+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
+ predicted dotted name of a tween factory added in a call to ``add_tween``
+ in the same configuration session.
+
+- A "tween alias": a string representing the predicted value of ``alias`` in
+ a separate 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`.
+
+Effectively, ``under`` means "closer to the main Pyramid application than",
+``over`` means "closer to the request ingress than".
+
+For example, the following call to
+:meth:`~pyramid.config.Configurator.add_tween` will attempt to place the
+tween factory represented by ``myapp.tween_factory`` directly 'above' (in
+``paster ptweens`` order) the main Pyramid request handler.
+
+.. code-block:: python
+ :linenos:
+
+ import pyramid.tweens
+
+ config.add_tween('myapp.tween_factory', over=pyramid.tweens.MAIN)
+
+The above example will generate an implicit tween chain that looks like
+this::
+
+ INGRESS (implicit)
+ pyramid.tweens.excview_tween_factory (implicit)
+ myapp.tween_factory
+ MAIN (implicit)
+
+Likewise, calling the following call to
+:meth:`~pyramid.config.Configurator.add_tween` will attempt to place this
+tween factory 'above' the main handler but 'below' a separately added tween
+factory:
+
+.. code-block:: python
+ :linenos:
+
+ import pyramid.tweens
+
+ config.add_tween('myapp.tween_factory1',
+ over=pyramid.tweens.MAIN)
+ config.add_tween('myapp.tween_factory2',
+ over=pyramid.tweens.MAIN,
+ under='myapp.tween_factory1')
+
+The above example will generate an implicit tween chain that looks like
+this::
+
+ INGRESS (implicit)
+ pyramid.tweens.excview_tween_factory (implicit)
+ myapp.tween_factory1
+ myapp.tween_factory2
+ MAIN (implicit)
+
+Specifying neither ``over`` nor ``under`` is equivalent to specifying
+``under=INGRESS``.
+
+If an ``under`` or ``over`` value is provided that does not match a tween
+factory dotted name or alias in the current configuration, that value will be
+ignored. It is not an error to provide an ``under`` or ``over`` value that
+matches an unused tween factory.
+
+:meth:`~pyramid.config.Configurator.add_tween` also accepts an ``alias``
+argument. If ``alias`` is not ``None``, should be a string. The string will
+represent a value that other callers of ``add_tween`` may pass as an
+``under`` and ``over`` argument instead of a dotted name to a tween factory.
+For example:
+
+.. code-block:: python
+ :linenos:
+
+ import pyramid.tweens
+
+ config.add_tween('myapp.tween_factory1',
+ alias='one'
+ over=pyramid.tweens.MAIN)
+ config.add_tween('myapp.tween_factory2',
+ alias='two'
+ over=pyramid.tweens.MAIN,
+ under='one')
+
+Alias names are only useful in relation to ``under`` and ``over`` values.
+They cannot be used in explicit tween chain configuration, or anywhere else.
+
+Implicit tween ordering is obviously only best-effort. Pyramid will attempt
+to provide an implicit order of tweens as best it can using hints provided by
+calls to :meth:`~pyramid.config.Configurator.add_tween`, but because it's
+only best-effort, if very precise tween ordering is required, the only
+surefire way to get it is to use an explicit tween order. The deploying user
+can override the implicit tween inclusion and ordering implied by calls to
+:meth:`~pyramid.config.Configurator.add_tween` entirely by using the
+``pyramid.tweens`` settings value. When used, this settings value must be a
+list of Python dotted names which will override the ordering (and inclusion)
+of tween factories in the implicit tween chain. For example:
.. code-block:: ini
:linenos:
@@ -932,17 +1047,19 @@ example:
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.debug_templates = true
- pyramid.tweens = pyramid.tweens.excview_tween_factory
- myapp.my_cool_tween_factory
+ pyramid.tweens = myapp.my_cool_tween_factory
+ pyramid.tweens.excview_tween_factory
In the above configuration, calls made during configuration to
:meth:`pyramid.config.Configurator.add_tween` are ignored, and the user is
telling the system to use the tween factories he has listed in the
-``pyramid.tweens`` configuration setting (each is a:term:`Python dotted name`
-which points to a tween factory). The *last* tween factory in the
-``pyramid.tweens`` list will be used as the producer of the effective
+``pyramid.tweens`` configuration setting (each is a :term:`dotted Python
+name` which points to a tween factory) instead of any tween factories added
+via :meth:`pyramid.config.Configurator.add_tween`. The *first* tween factory
+in the ``pyramid.tweens`` list will be used as the producer of the effective
:app:`Pyramid` request handling function; it will wrap the tween factory
-declared directly "above" it, ad infinitum.
+declared directly "below" it, ad infinitum. The "main" Pyramid request
+handler is implicit, and always "at the bottom".
.. note:: Pyramid's own :term:`exception view` handling logic is implemented
as a tween factory function: :func:`pyramid.tweens.excview_tween_factory`.
@@ -952,6 +1069,19 @@ declared directly "above" it, ad infinitum.
``pyramid.tweens`` configuration setting list explicitly. If it is not
present, Pyramid will not perform exception view handling.
-The ``paster ptweens`` command-line utility can be used to report the current
-tween chain used by an application. See :ref:`displaying_tweens`.
+Pyramid will prevent the same tween factory from being added to the tween
+chain more than once using configuration conflict detection. If you wish to
+add the same tween factory more than once in a configuration, you should
+either: a) use a tween factory that is an instance rather than a function or
+class, b) use a function or class as a tween factory with the same logic as
+the other tween factory it conflicts with but with a different ``__name__``
+attribute or c) call :meth:`pyramid.config.Configurator.commit` between calls
+to :meth:`pyramid.config.Configurator.add_tween`.
+If a cycle is detected in implicit tween ordering when ``over`` and ``under``
+are used in any call to "add_tween", an exception will be raised at startup
+time.
+
+The ``paster ptweens`` command-line utility can be used to report the current
+implict and explicit tween chains used by an application. See
+:ref:`displaying_tweens`.
diff --git a/pyramid/config.py b/pyramid/config.py
index 3fac6847d..45c5b743f 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -80,6 +80,9 @@ from pyramid.traversal import DefaultRootFactory
from pyramid.traversal import find_interface
from pyramid.traversal import traversal_path
from pyramid.tweens import excview_tween_factory
+from pyramid.tweens import Tweens
+from pyramid.tweens import tween_factory_name
+from pyramid.tweens import MAIN, INGRESS, EXCVIEW
from pyramid.urldispatch import RoutesMapper
from pyramid.util import DottedNameResolver
from pyramid.util import WeakOrderedSet
@@ -733,9 +736,6 @@ class Configurator(object):
# cope with WebOb exc objects not decoratored with IExceptionResponse
from webob.exc import WSGIHTTPException as WebobWSGIHTTPException
registry.registerSelfAdapter((WebobResponse,), IResponse)
- # add a handler manager
- for factory in tweens:
- self._add_tween(factory, explicit=True)
if debug_logger is None:
debug_logger = logging.getLogger(self.package_name)
@@ -784,6 +784,8 @@ class Configurator(object):
self.commit()
for inc in includes:
self.include(inc)
+ for factory in tweens:
+ self._add_tween(factory, explicit=True)
def hook_zca(self):
""" Call :func:`zope.component.getSiteManager.sethook` with
@@ -903,40 +905,110 @@ class Configurator(object):
return self._derive_view(view, attr=attr, renderer=renderer)
@action_method
- def add_tween(self, tween_factory):
+ def add_tween(self, tween_factory, alias=None, under=None, over=None):
"""
- Add a 'tween factory'. A :term:`tween` (think: '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'. This is 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. Tweens behave a bit like
+ 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'. This is 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 ``alias`` argument, if it is not ``None``, should be a string.
+ The string will represent a value that other callers of ``add_tween``
+ may pass as an ``under`` and ``over`` argument instead of a dotted
+ name to a 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 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 predicted dotted name of a tween factory added in
+ a call to ``add_tween`` in the same configuration session.
+
+ - A tween alias: a string representing the predicted value of
+ ``alias`` in a separate 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`.
+
+ ``under`` means 'closer to the main Pyramid application than',
+ ``over`` means 'closer to the request ingress than'.
+
+ For example, calling ``add_tween(factory, over=pyramid.tweens.MAIN)``
+ will attempt to place the tween factory represented by ``factory``
+ directly 'above' (in ``paster ptweens`` order) the main Pyramid
+ request handler. Likewise, calling ``add_tween(factory,
+ over=pyramid.tweens.MAIN, under='someothertween')`` will attempt to
+ place this tween factory 'above' the main handler but 'below' (a
+ fictional) 'someothertween' tween factory (which was presumably added
+ via ``add_tween(factory, alias='someothertween')``).
+
+ If an ``under`` or ``over`` value is provided that does not match a
+ tween factory dotted name or alias in the current configuration, that
+ value will be ignored. It is not an error to provide an ``under`` or
+ ``over`` value that matches an unused tween factory.
+
+ 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.
+
+ ``alias``, ``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`.
.. note:: This feature is new as of Pyramid 1.1.1.
"""
- return self._add_tween(tween_factory, explicit=False)
+ return self._add_tween(tween_factory, alias=alias, under=under,
+ over=over, explicit=False)
- def _add_tween(self, tween_factory, explicit):
+ def _add_tween(self, tween_factory, alias=None, under=None, over=None,
+ explicit=False):
tween_factory = self.maybe_dotted(tween_factory)
name = tween_factory_name(tween_factory)
- def register():
- registry = self.registry
- tweens = registry.queryUtility(ITweens)
- if tweens is None:
- tweens = Tweens()
- registry.registerUtility(tweens, ITweens)
- tweens.add(
- tween_factory_name(excview_tween_factory),
- excview_tween_factory,
- explicit=False)
- tweens.add(name, tween_factory, explicit)
- self.action(('tween', name, explicit), register)
+ if alias in (MAIN, INGRESS):
+ raise ConfigurationError('%s is a reserved tween name' % alias)
+
+ registry = self.registry
+ tweens = registry.queryUtility(ITweens)
+ if tweens is None:
+ tweens = Tweens()
+ registry.registerUtility(tweens, ITweens)
+ tweens.add_implicit(tween_factory_name(excview_tween_factory),
+ excview_tween_factory, alias=EXCVIEW,
+ over=MAIN)
+ if explicit:
+ tweens.add_explicit(name, tween_factory)
+ else:
+ tweens.add_implicit(name, tween_factory, alias=alias, under=under,
+ over=over)
+ self.action(('tween', name, explicit))
+ if not explicit and alias is not None:
+ self.action(('tween', alias, explicit))
@action_method
def add_request_handler(self, factory, name): # pragma: no cover
@@ -3378,39 +3450,3 @@ def isexception(o):
global_registries = WeakOrderedSet()
-class Tweens(object):
- implements(ITweens)
- def __init__(self):
- self.explicit = []
- self.implicit = []
-
- def add(self, name, factory, explicit=False):
- if explicit:
- self.explicit.append((name, factory))
- else:
- self.implicit.append((name, factory))
-
- def __call__(self, handler, registry):
- factories = self.implicit
- if self.explicit:
- factories = self.explicit
- for name, factory in factories:
- handler = factory(handler, registry)
- return handler
-
-def tween_factory_name(factory):
- if (hasattr(factory, '__name__') and
- hasattr(factory, '__module__')):
- # function or class
- name = '.'.join([factory.__module__,
- factory.__name__])
- elif hasattr(factory, '__module__'):
- # instance
- name = '.'.join([factory.__module__,
- factory.__class__.__name__,
- str(id(factory))])
- else:
- raise ConfigurationError(
- 'A tween factory must be a class, an instance, or a function; '
- '%s is not a suitable tween factory' % factory)
- return name
diff --git a/pyramid/paster.py b/pyramid/paster.py
index 54f5a51a6..47f9ebc34 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -14,6 +14,9 @@ from pyramid.interfaces import ITweens
from pyramid.scripting import prepare
from pyramid.util import DottedNameResolver
+from pyramid.tweens import MAIN
+from pyramid.tweens import INGRESS
+
from pyramid.scaffolds import PyramidTemplate # bw compat
zope.deprecation.deprecated(
'PyramidTemplate', ('pyramid.paster.PyramidTemplate was moved to '
@@ -567,6 +570,28 @@ class PTweensCommand(PCommand):
def out(self, msg): # pragma: no cover
print msg
+
+ def show_implicit(self, tweens):
+ implicit = tweens.implicit()
+ fmt = '%-10s %-50s %-15s'
+ self.out(fmt % ('Position', 'Name', 'Alias'))
+ self.out(fmt % (
+ '-'*len('Position'), '-'*len('Name'), '-'*len('Alias')))
+ self.out(fmt % ('-', '-', INGRESS))
+ for pos, (name, _) in enumerate(implicit):
+ alias = tweens.name_to_alias.get(name, None)
+ self.out(fmt % (pos, name, alias))
+ self.out(fmt % ('-', '-', MAIN))
+
+ def show_explicit(self, tweens):
+ explicit = tweens.explicit
+ fmt = '%-10s %-65s'
+ self.out(fmt % ('Position', 'Name'))
+ self.out(fmt % ('-'*len('Position'), '-'*len('Name')))
+ self.out(fmt % ('-', INGRESS))
+ for pos, (name, _) in enumerate(explicit):
+ self.out(fmt % (pos, name))
+ self.out(fmt % ('-', MAIN))
def command(self):
config_uri = self.args[0]
@@ -574,27 +599,22 @@ class PTweensCommand(PCommand):
registry = env['registry']
tweens = self._get_tweens(registry)
if tweens is not None:
- ordering = []
- if tweens.explicit:
+ explicit = tweens.explicit
+ if explicit:
self.out('"pyramid.tweens" config value set '
'(explicitly ordered tweens used)')
self.out('')
- ordering.append((tweens.explicit,
- 'Explicit Tween Chain (used)'))
- ordering.append((tweens.implicit,
- 'Implicit Tween Chain (not used)'))
+ self.out('Explicit Tween Chain (used)')
+ self.out('')
+ self.show_explicit(tweens)
+ self.out('')
+ self.out('Implicit Tween Chain (not used)')
+ self.out('')
+ self.show_implicit(tweens)
else:
self.out('"pyramid.tweens" config value NOT set '
'(implicitly ordered tweens used)')
self.out('')
- ordering.append((tweens.implicit, ''))
- for L, title in ordering:
- if title:
- self.out(title)
- self.out('')
- fmt = '%-8s %-30s'
- self.out(fmt % ('Position', 'Name'))
- self.out(fmt % ('-'*len('Position'), '-'*len('Name')))
- for pos, (name, item) in enumerate(L):
- self.out(fmt % (pos, name))
+ self.out('Implicit Tween Chain')
self.out('')
+ self.show_implicit(tweens)
diff --git a/pyramid/router.py b/pyramid/router.py
index 79fd7992b..248ab3b5b 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -39,15 +39,11 @@ class Router(object):
self.request_factory = q(IRequestFactory, default=Request)
tweens = q(ITweens)
if tweens is None:
- self.handle_request = excview_tween_factory(self.handle_request,
- registry)
- else:
- self.handle_request = tweens(self.handle_request, registry)
-
+ tweens = excview_tween_factory
+ self.handle_request = tweens(self.handle_request, registry)
self.root_policy = self.root_factory # b/w compat
self.registry = registry
settings = registry.settings
-
if settings is not None:
self.debug_notfound = settings['debug_notfound']
self.debug_routematch = settings['debug_routematch']
@@ -56,117 +52,107 @@ class Router(object):
attrs = request.__dict__
registry = attrs['registry']
- try: # matches finally: if request is not None
- request.request_iface = IRequest
- context = None
- routes_mapper = self.routes_mapper
- debug_routematch = self.debug_routematch
- adapters = registry.adapters
- has_listeners = registry.has_listeners
- notify = registry.notify
- logger = self.logger
-
- has_listeners and notify(NewRequest(request))
- # find the root object
- root_factory = self.root_factory
- if routes_mapper is not None:
- info = routes_mapper(request)
- match, route = info['match'], info['route']
- if route is None:
- if debug_routematch:
- msg = ('no route matched for url %s' %
- request.url)
- logger and logger.debug(msg)
- else:
- # TODO: kill off bfg.routes.* environ keys
- # when traverser requires request arg, and
- # cant cope with environ anymore (they are
- # docs-deprecated as of BFG 1.3)
- environ = request.environ
- environ['bfg.routes.route'] = route
- environ['bfg.routes.matchdict'] = match
- attrs['matchdict'] = match
- attrs['matched_route'] = route
-
- if debug_routematch:
- msg = (
- 'route matched for url %s; '
- 'route_name: %r, '
- 'path_info: %r, '
- 'pattern: %r, '
- 'matchdict: %r, '
- 'predicates: %r' % (
- request.url,
- route.name,
- request.path_info,
- route.pattern, match,
- route.predicates)
- )
- logger and logger.debug(msg)
-
- request.request_iface = registry.queryUtility(
- IRouteRequest,
- name=route.name,
- default=IRequest)
-
- root_factory = route.factory or self.root_factory
-
- root = root_factory(request)
- attrs['root'] = root
-
- # find a context
- traverser = adapters.queryAdapter(root, ITraverser)
- if traverser is None:
- traverser = ResourceTreeTraverser(root)
- tdict = traverser(request)
-
- context, view_name, subpath, traversed, vroot, vroot_path = (
- tdict['context'],
- tdict['view_name'],
- tdict['subpath'],
- tdict['traversed'],
- tdict['virtual_root'],
- tdict['virtual_root_path']
- )
-
- attrs.update(tdict)
- has_listeners and notify(ContextFound(request))
-
- # find a view callable
- context_iface = providedBy(context)
- view_callable = adapters.lookup(
- (IViewClassifier, request.request_iface, context_iface),
- IView, name=view_name, default=None)
-
- # invoke the view callable
- if view_callable is None:
- if self.debug_notfound:
+ request.request_iface = IRequest
+ context = None
+ routes_mapper = self.routes_mapper
+ debug_routematch = self.debug_routematch
+ adapters = registry.adapters
+ has_listeners = registry.has_listeners
+ notify = registry.notify
+ logger = self.logger
+
+ has_listeners and notify(NewRequest(request))
+ # find the root object
+ root_factory = self.root_factory
+ if routes_mapper is not None:
+ info = routes_mapper(request)
+ match, route = info['match'], info['route']
+ if route is None:
+ if debug_routematch:
+ msg = ('no route matched for url %s' %
+ request.url)
+ logger and logger.debug(msg)
+ else:
+ # TODO: kill off bfg.routes.* environ keys
+ # when traverser requires request arg, and
+ # cant cope with environ anymore (they are
+ # docs-deprecated as of BFG 1.3)
+ environ = request.environ
+ environ['bfg.routes.route'] = route
+ environ['bfg.routes.matchdict'] = match
+ attrs['matchdict'] = match
+ attrs['matched_route'] = route
+
+ if debug_routematch:
msg = (
- 'debug_notfound of url %s; path_info: %r, '
- 'context: %r, view_name: %r, subpath: %r, '
- 'traversed: %r, root: %r, vroot: %r, '
- 'vroot_path: %r' % (
- request.url, request.path_info, context,
- view_name, subpath, traversed, root, vroot,
- vroot_path)
+ 'route matched for url %s; '
+ 'route_name: %r, '
+ 'path_info: %r, '
+ 'pattern: %r, '
+ 'matchdict: %r, '
+ 'predicates: %r' % (
+ request.url,
+ route.name,
+ request.path_info,
+ route.pattern, match,
+ route.predicates)
)
logger and logger.debug(msg)
- else:
- msg = request.path_info
- raise HTTPNotFound(msg)
- else:
- response = view_callable(context, request)
-
- has_listeners and notify(NewResponse(request, response))
-
- if request.response_callbacks:
- request._process_response_callbacks(response)
- return response
+ request.request_iface = registry.queryUtility(
+ IRouteRequest,
+ name=route.name,
+ default=IRequest)
+
+ root_factory = route.factory or self.root_factory
+
+ root = root_factory(request)
+ attrs['root'] = root
+
+ # find a context
+ traverser = adapters.queryAdapter(root, ITraverser)
+ if traverser is None:
+ traverser = ResourceTreeTraverser(root)
+ tdict = traverser(request)
+
+ context, view_name, subpath, traversed, vroot, vroot_path = (
+ tdict['context'],
+ tdict['view_name'],
+ tdict['subpath'],
+ tdict['traversed'],
+ tdict['virtual_root'],
+ tdict['virtual_root_path']
+ )
+
+ attrs.update(tdict)
+ has_listeners and notify(ContextFound(request))
+
+ # find a view callable
+ context_iface = providedBy(context)
+ view_callable = adapters.lookup(
+ (IViewClassifier, request.request_iface, context_iface),
+ IView, name=view_name, default=None)
+
+ # invoke the view callable
+ if view_callable is None:
+ if self.debug_notfound:
+ msg = (
+ 'debug_notfound of url %s; path_info: %r, '
+ 'context: %r, view_name: %r, subpath: %r, '
+ 'traversed: %r, root: %r, vroot: %r, '
+ 'vroot_path: %r' % (
+ request.url, request.path_info, context,
+ view_name, subpath, traversed, root, vroot,
+ vroot_path)
+ )
+ logger and logger.debug(msg)
+ else:
+ msg = request.path_info
+ raise HTTPNotFound(msg)
+ else:
+ response = view_callable(context, request)
- finally:
- if request is not None and request.finished_callbacks:
- request._process_finished_callbacks()
+ return response
def __call__(self, environ, start_response):
"""
@@ -177,14 +163,28 @@ class Router(object):
return an iterable.
"""
registry = self.registry
+ has_listeners = self.registry.has_listeners
+ notify = self.registry.notify
request = self.request_factory(environ)
threadlocals = {'registry':registry, 'request':request}
manager = self.threadlocal_manager
manager.push(threadlocals)
+ request.registry = registry
try:
- request.registry = registry
- response = self.handle_request(request)
- return response(request.environ, start_response)
+
+ try:
+ response = self.handle_request(request)
+ has_listeners and notify(NewResponse(request, response))
+
+ if request.response_callbacks:
+ request._process_response_callbacks(response)
+
+ return response(request.environ, start_response)
+
+ finally:
+ if request.finished_callbacks:
+ request._process_finished_callbacks()
+
finally:
manager.pop()
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index d73fd7f7d..652fd94dd 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -592,6 +592,7 @@ pyramid.tests.test_config.dummy_include2""",
'pyramid.tweens': 'pyramid.tests.test_config.dummy_tween_factory'
}
config.setup_registry(settings=settings)
+ config.commit()
tweens = config.registry.getUtility(ITweens)
self.assertEqual(tweens.explicit,
[('pyramid.tests.test_config.dummy_tween_factory',
@@ -638,11 +639,36 @@ pyramid.tests.test_config.dummy_include2""",
config.add_tween(factory2)
config.commit()
tweens = config.registry.queryUtility(ITweens)
+ implicit = tweens.implicit()
self.assertEqual(
- tweens.implicit,
- [('pyramid.tweens.excview_tween_factory', excview_tween_factory),
- ('pyramid.tests.test_config.factory1', factory1),
- ('pyramid.tests.test_config.factory2', factory2)])
+ implicit,
+ [
+ ('pyramid.tests.test_config.factory2', factory2),
+ ('pyramid.tests.test_config.factory1', factory1),
+ ('pyramid.tweens.excview_tween_factory', excview_tween_factory),
+ ]
+ )
+
+ def test_add_tweens_names_with_underover(self):
+ from pyramid.interfaces import ITweens
+ from pyramid.tweens import excview_tween_factory
+ from pyramid.tweens import MAIN
+ def factory1(handler, registry): return handler
+ def factory2(handler, registry): return handler
+ config = self._makeOne()
+ config.add_tween(factory1, over=MAIN)
+ config.add_tween(factory2, over=MAIN,
+ under='pyramid.tests.test_config.factory1')
+ config.commit()
+ tweens = config.registry.queryUtility(ITweens)
+ implicit = tweens.implicit()
+ self.assertEqual(
+ implicit,
+ [
+ ('pyramid.tweens.excview_tween_factory', excview_tween_factory),
+ ('pyramid.tests.test_config.factory1', factory1),
+ ('pyramid.tests.test_config.factory2', factory2),
+ ])
def test_add_tween_dottedname(self):
from pyramid.interfaces import ITweens
@@ -652,11 +678,11 @@ pyramid.tests.test_config.dummy_include2""",
config.commit()
tweens = config.registry.queryUtility(ITweens)
self.assertEqual(
- tweens.implicit,
+ tweens.implicit(),
[
- ('pyramid.tweens.excview_tween_factory', excview_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory',
- dummy_tween_factory)
+ dummy_tween_factory),
+ ('pyramid.tweens.excview_tween_factory', excview_tween_factory),
])
def test_add_tween_instance(self):
@@ -668,13 +694,15 @@ pyramid.tests.test_config.dummy_include2""",
config.add_tween(atween)
config.commit()
tweens = config.registry.queryUtility(ITweens)
- self.assertEqual(len(tweens.implicit), 2)
+ implicit = tweens.implicit()
+ self.assertEqual(len(implicit), 2)
+ self.assertTrue(
+ implicit[0][0].startswith(
+ 'pyramid.tests.test_config.ATween.'))
+ self.assertEqual(implicit[0][1], atween)
self.assertEqual(
- tweens.implicit[0],
+ implicit[1],
('pyramid.tweens.excview_tween_factory', excview_tween_factory))
- self.assertTrue(
- tweens.implicit[1][0].startswith('pyramid.tests.test_config.ATween.'))
- self.assertEqual(tweens.implicit[1][1], atween)
def test_add_tween_unsuitable(self):
from pyramid.exceptions import ConfigurationError
@@ -682,6 +710,22 @@ pyramid.tests.test_config.dummy_include2""",
config = self._makeOne()
self.assertRaises(ConfigurationError, config.add_tween, pyramid.tests)
+ def test_add_tween_alias_ingress(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.tweens import INGRESS
+ config = self._makeOne()
+ self.assertRaises(ConfigurationError,
+ config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory',
+ alias=INGRESS)
+
+ def test_add_tween_alias_main(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.tweens import MAIN
+ config = self._makeOne()
+ self.assertRaises(ConfigurationError,
+ config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory',
+ alias=MAIN)
+
def test_add_tweens_conflict(self):
from zope.configuration.config import ConfigurationConflictError
config = self._makeOne()
@@ -689,6 +733,16 @@ pyramid.tests.test_config.dummy_include2""",
config.add_tween('pyramid.tests.test_config.dummy_tween_factory')
self.assertRaises(ConfigurationConflictError, config.commit)
+ def test_add_tweens_conflict_same_alias(self):
+ from zope.configuration.config import ConfigurationConflictError
+ class ATween(object): pass
+ atween1 = ATween()
+ atween2 = ATween()
+ config = self._makeOne()
+ config.add_tween(atween1, alias='a')
+ config.add_tween(atween2, alias='a')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
def test_add_subscriber_defaults(self):
from zope.interface import implements
from zope.interface import Interface
@@ -5511,45 +5565,6 @@ class Test_isexception(unittest.TestCase):
pass
self.assertEqual(self._callFUT(ISubException), True)
-class TestTweens(unittest.TestCase):
- def _makeOne(self):
- from pyramid.config import Tweens
- return Tweens()
-
- def test_add_explicit(self):
- tweens = self._makeOne()
- tweens.add('name', 'factory', explicit=True)
- self.assertEqual(tweens.explicit, [('name', 'factory')])
- tweens.add('name2', 'factory2', explicit=True)
- self.assertEqual(tweens.explicit, [('name', 'factory'),
- ('name2', 'factory2')])
-
- def test_add_implicit(self):
- tweens = self._makeOne()
- tweens.add('name', 'factory', explicit=False)
- self.assertEqual(tweens.implicit, [('name', 'factory')])
- tweens.add('name2', 'factory2', explicit=False)
- self.assertEqual(tweens.implicit, [('name', 'factory'),
- ('name2', 'factory2')])
-
- def test___call___explicit(self):
- tweens = self._makeOne()
- def factory1(handler, registry):
- return handler
- def factory2(handler, registry):
- return '123'
- tweens.explicit = [('name', factory1), ('name', factory2)]
- self.assertEqual(tweens(None, None), '123')
-
- def test___call___implicit(self):
- tweens = self._makeOne()
- def factory1(handler, registry):
- return handler
- def factory2(handler, registry):
- return '123'
- tweens.implicit = [('name', factory1), ('name', factory2)]
- self.assertEqual(tweens(None, None), '123')
-
class DummyRequest:
subpath = ()
matchdict = None
diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py
index 58ed73d2c..36c3a51be 100644
--- a/pyramid/tests/test_paster.py
+++ b/pyramid/tests/test_paster.py
@@ -853,13 +853,9 @@ class TestPTweensCommand(unittest.TestCase):
result = command.command()
self.assertEqual(result, None)
self.assertEqual(
- L,
- ['"pyramid.tweens" config value NOT set (implicitly ordered tweens used)',
- '',
- 'Position Name ',
- '-------- ---- ',
- '0 name ',
- ''])
+ L[0],
+ '"pyramid.tweens" config value NOT set (implicitly ordered tweens '
+ 'used)')
def test_command_implicit_and_explicit_tweens(self):
command = self._makeOne()
@@ -870,22 +866,8 @@ class TestPTweensCommand(unittest.TestCase):
result = command.command()
self.assertEqual(result, None)
self.assertEqual(
- L,
- ['"pyramid.tweens" config value set (explicitly ordered tweens used)',
- '',
- 'Explicit Tween Chain (used)',
- '',
- 'Position Name ',
- '-------- ---- ',
- '0 name2 ',
- '',
- 'Implicit Tween Chain (not used)',
- '',
- 'Position Name ',
- '-------- ---- ',
- '0 name ',
- ''
- ])
+ L[0],
+ '"pyramid.tweens" config value set (explicitly ordered tweens used)')
def test__get_tweens(self):
command = self._makeOne()
@@ -894,8 +876,11 @@ class TestPTweensCommand(unittest.TestCase):
class DummyTweens(object):
def __init__(self, implicit, explicit):
- self.implicit = implicit
+ self._implicit = implicit
self.explicit = explicit
+ self.name_to_alias = {}
+ def implicit(self):
+ return self._implicit
class Dummy:
pass
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 6b0354468..19134813f 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -159,8 +159,8 @@ class TestRouter(unittest.TestCase):
wrapper.name = 'two'
wrapper.child = handler
return wrapper
- tweens.add('one', tween_factory1)
- tweens.add('two', tween_factory2)
+ tweens.add_implicit('one', tween_factory1)
+ tweens.add_implicit('two', tween_factory2)
router = self._makeOne()
self.assertEqual(router.handle_request.name, 'two')
self.assertEqual(router.handle_request.child.name, 'one')
diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py
new file mode 100644
index 000000000..a626afa11
--- /dev/null
+++ b/pyramid/tests/test_tweens.py
@@ -0,0 +1,286 @@
+import unittest
+
+class TestTweens(unittest.TestCase):
+ def _makeOne(self):
+ from pyramid.config import Tweens
+ return Tweens()
+
+ def test_add_explicit(self):
+ tweens = self._makeOne()
+ tweens.add_explicit('name', 'factory')
+ self.assertEqual(tweens.explicit, [('name', 'factory')])
+ tweens.add_explicit('name2', 'factory2')
+ self.assertEqual(tweens.explicit, [('name', 'factory'),
+ ('name2', 'factory2')])
+
+ def test_add_implicit_noaliases(self):
+ from pyramid.tweens import INGRESS
+ from pyramid.tweens import MAIN
+ D = {MAIN:MAIN, INGRESS:INGRESS}
+ tweens = self._makeOne()
+ tweens.add_implicit('name', 'factory')
+ self.assertEqual(tweens.names, ['name'])
+ self.assertEqual(tweens.factories,
+ {'name':'factory'})
+ self.assertEqual(tweens.alias_to_name, D)
+ self.assertEqual(tweens.name_to_alias, D)
+ self.assertEqual(tweens.order, [(INGRESS, 'name')])
+ self.assertEqual(tweens.ingress_alias_names, ['name'])
+ tweens.add_implicit('name2', 'factory2')
+ self.assertEqual(tweens.names, ['name', 'name2'])
+ self.assertEqual(tweens.factories,
+ {'name':'factory', 'name2':'factory2'})
+ self.assertEqual(tweens.alias_to_name, D)
+ self.assertEqual(tweens.name_to_alias, D)
+ self.assertEqual(tweens.order,
+ [(INGRESS, 'name'), (INGRESS, 'name2')])
+ self.assertEqual(tweens.ingress_alias_names, ['name', 'name2'])
+ tweens.add_implicit('name3', 'factory3', over='name2')
+ self.assertEqual(tweens.names,
+ ['name', 'name2', 'name3'])
+ self.assertEqual(tweens.factories,
+ {'name':'factory', 'name2':'factory2',
+ 'name3':'factory3'})
+ self.assertEqual(tweens.alias_to_name, D)
+ self.assertEqual(tweens.name_to_alias, D)
+ self.assertEqual(tweens.order,
+ [(INGRESS, 'name'), (INGRESS, 'name2'),
+ ('name3', 'name2')])
+ self.assertEqual(tweens.ingress_alias_names, ['name', 'name2'])
+
+ def test_add_implicit_withaliases(self):
+ from pyramid.tweens import INGRESS
+ tweens = self._makeOne()
+ tweens.add_implicit('name1', 'factory', alias='n1')
+ self.assertEqual(tweens.names, ['name1'])
+ self.assertEqual(tweens.factories,
+ {'name1':'factory'})
+ self.assertEqual(tweens.alias_to_name['n1'], 'name1')
+ self.assertEqual(tweens.name_to_alias['name1'], 'n1')
+ self.assertEqual(tweens.order, [(INGRESS, 'n1')])
+ self.assertEqual(tweens.ingress_alias_names, ['n1'])
+ tweens.add_implicit('name2', 'factory2', alias='n2')
+ self.assertEqual(tweens.names, ['name1', 'name2'])
+ self.assertEqual(tweens.factories,
+ {'name1':'factory', 'name2':'factory2'})
+ self.assertEqual(tweens.alias_to_name['n2'], 'name2')
+ self.assertEqual(tweens.name_to_alias['name2'], 'n2')
+ self.assertEqual(tweens.order,
+ [(INGRESS, 'n1'), (INGRESS, 'n2')])
+ self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2'])
+ tweens.add_implicit('name3', 'factory3', alias='n3', over='name2')
+ self.assertEqual(tweens.names,
+ ['name1', 'name2', 'name3'])
+ self.assertEqual(tweens.factories,
+ {'name1':'factory', 'name2':'factory2',
+ 'name3':'factory3'})
+ self.assertEqual(tweens.alias_to_name['n3'], 'name3')
+ self.assertEqual(tweens.name_to_alias['name3'], 'n3')
+ self.assertEqual(tweens.order,
+ [(INGRESS, 'n1'), (INGRESS, 'n2'),
+ ('n3', 'name2')])
+ self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2'])
+
+ def test___call___explicit(self):
+ tweens = self._makeOne()
+ def factory1(handler, registry):
+ return handler
+ def factory2(handler, registry):
+ return '123'
+ tweens.explicit = [('name', factory1), ('name', factory2)]
+ self.assertEqual(tweens(None, None), '123')
+
+ def test___call___implicit(self):
+ tweens = self._makeOne()
+ def factory1(handler, registry):
+ return handler
+ def factory2(handler, registry):
+ return '123'
+ tweens.names = ['name', 'name2']
+ tweens.alias_to_name = {'name':'name', 'name2':'name2'}
+ tweens.name_to_alias = {'name':'name', 'name2':'name2'}
+ tweens.factories = {'name':factory1, 'name2':factory2}
+ self.assertEqual(tweens(None, None), '123')
+
+ def test___call___implicit_with_aliasnames_different_than_names(self):
+ tweens = self._makeOne()
+ def factory1(handler, registry):
+ return handler
+ def factory2(handler, registry):
+ return '123'
+ tweens.names = ['foo1', 'foo2']
+ tweens.alias_to_name = {'foo1':'name', 'foo2':'name2'}
+ tweens.name_to_alias = {'name':'foo1', 'name2':'foo2'}
+ tweens.factories = {'name':factory1, 'name2':factory2}
+ self.assertEqual(tweens(None, None), '123')
+
+ def test_implicit_ordering_1(self):
+ tweens = self._makeOne()
+ tweens.add_implicit('name1', 'factory1')
+ tweens.add_implicit('name2', 'factory2')
+ self.assertEqual(tweens.implicit(),
+ [
+ ('name2', 'factory2'),
+ ('name1', 'factory1'),
+ ])
+
+ def test_implicit_ordering_2(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ tweens.add_implicit('name1', 'factory1')
+ tweens.add_implicit('name2', 'factory2', over=MAIN)
+ self.assertEqual(tweens.implicit(),
+ [
+ ('name1', 'factory1'),
+ ('name2', 'factory2'),
+ ])
+
+ def test_implicit_ordering_3(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('auth', 'auth_factory', under='browserid')
+ add('dbt', 'dbt_factory')
+ add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', under='exceptionview')
+ add('exceptionview', 'excview_factory', over=MAIN)
+ self.assertEqual(tweens.implicit(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ])
+
+ def test_implicit_ordering_4(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', over=MAIN)
+ add('auth', 'auth_factory', under='browserid')
+ add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', under='exceptionview')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(tweens.implicit(),
+ [
+ ('dbt', 'dbt_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ])
+
+ def test_implicit_ordering_withaliases(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', alias='e', over=MAIN)
+ add('auth', 'auth_factory', under='b')
+ add('retry', 'retry_factory', over='t', under='exceptionview')
+ add('browserid', 'browserid_factory', alias='b')
+ add('txnmgr', 'txnmgr_factory', alias='t', under='exceptionview')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(tweens.implicit(),
+ [
+ ('dbt', 'dbt_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ('txnmgr', 'txnmgr_factory'),
+ ])
+
+ def test_implicit_ordering_missing_partial(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', over=MAIN)
+ add('auth', 'auth_factory', under='browserid')
+ add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(tweens.implicit(),
+ [
+ ('dbt', 'dbt_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ])
+
+ def test_implicit_ordering_missing_partial2(self):
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', under='browserid')
+ add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertEqual(tweens.implicit(),
+ [
+ ('retry', 'retry_factory'),
+ ('browserid', 'browserid_factory'),
+ ('auth', 'auth_factory'),
+ ('dbt', 'dbt_factory'),
+ ])
+
+ def test_implicit_ordering_missing_partial3(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', over=MAIN)
+ add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertEqual(tweens.implicit(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ])
+
+ def test_implicit_ordering_missing_partial_with_aliases(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', alias='e', over=MAIN)
+ add('retry', 'retry_factory', over='txnmgr', under='e')
+ add('browserid', 'browserid_factory')
+ self.assertEqual(tweens.implicit(),
+ [
+ ('browserid', 'browserid_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
+ ])
+
+ def test_implicit_ordering_conflict_direct(self):
+ from pyramid.tweens import CyclicDependencyError
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', over='browserid', under='browserid')
+ self.assertRaises(CyclicDependencyError, tweens.implicit)
+
+ def test_implicit_ordering_conflict_indirect(self):
+ from pyramid.tweens import CyclicDependencyError
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', over='browserid')
+ add('dbt', 'dbt_factory', under='browserid', over='auth')
+ self.assertRaises(CyclicDependencyError, tweens.implicit)
+
+class TestCyclicDependencyError(unittest.TestCase):
+ def _makeOne(self, cycles):
+ from pyramid.tweens import CyclicDependencyError
+ return CyclicDependencyError(cycles)
+
+ def test___str__(self):
+ exc = self._makeOne({'a':['c', 'd'], 'c':['a']})
+ result = str(exc)
+ self.assertEqual(result,
+ "'a' sorts over ['c', 'd']; 'c' sorts over ['a']")
+
diff --git a/pyramid/tweens.py b/pyramid/tweens.py
index f7673a738..9a7d432dc 100644
--- a/pyramid/tweens.py
+++ b/pyramid/tweens.py
@@ -1,18 +1,18 @@
import sys
+from pyramid.exceptions import ConfigurationError
from pyramid.interfaces import IExceptionViewClassifier
from pyramid.interfaces import IView
+from pyramid.interfaces import ITweens
-from pyramid.events import NewResponse
from zope.interface import providedBy
+from zope.interface import implements
def excview_tween_factory(handler, registry):
""" A :term:`tween` factory which produces a tween that catches an
exception raised by downstream tweens (or the main Pyramid request
handler) and, if possible, converts it into a Response using an
:term:`exception view`."""
- has_listeners = registry.has_listeners
adapters = registry.adapters
- notify = registry.notify
def excview_tween(request):
attrs = request.__dict__
@@ -35,11 +35,178 @@ def excview_tween_factory(handler, registry):
if view_callable is None:
raise
response = view_callable(exc, request)
- has_listeners and notify(NewResponse(request, response))
finally:
+ # prevent leakage
attrs['exc_info'] = None
return response
return excview_tween
+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 = '; '.join(L)
+ return msg
+
+class Tweens(object):
+ implements(ITweens)
+ def __init__(self):
+ self.explicit = []
+ self.names = []
+ self.factories = {}
+ self.order = []
+ self.ingress_alias_names = []
+ self.alias_to_name = {INGRESS:INGRESS, MAIN:MAIN}
+ self.name_to_alias = {INGRESS:INGRESS, MAIN:MAIN}
+
+ def add_explicit(self, name, factory):
+ self.explicit.append((name, factory))
+
+ def add_implicit(self, name, factory, alias=None, under=None, over=None):
+ if alias is not None:
+ self.alias_to_name[alias] = name
+ self.name_to_alias[name] = alias
+ else:
+ alias = name
+ self.names.append(name)
+ self.factories[name] = factory
+ if under is None and over is None:
+ under = INGRESS
+ self.ingress_alias_names.append(alias)
+ if under is not None:
+ self.order.append((under, alias))
+ if over is not None:
+ self.order.append((alias, over))
+
+ def implicit(self):
+ order = []
+ roots = []
+ graph = {}
+ has_order = {}
+ aliases = [INGRESS, MAIN]
+ ingress_alias_names = self.ingress_alias_names[:]
+
+ for name in self.names:
+ aliases.append(self.name_to_alias.get(name, name))
+
+ for a, b in self.order:
+ # try to convert both a and b to an alias
+ a = self.name_to_alias.get(a, a)
+ b = self.name_to_alias.get(b, b)
+ order.append((a, b))
+
+ def add_node(graph, node):
+ if not graph.has_key(node):
+ roots.append(node)
+ graph[node] = [0] # 0 = number of arcs coming into this node
+
+ def add_arc(graph, fromnode, tonode):
+ graph[fromnode].append(tonode)
+ graph[tonode][0] += 1
+ if tonode in roots:
+ roots.remove(tonode)
+
+ # remove ordering information that mentions unknown names/aliases
+ for pos, (first, second) in enumerate(order):
+ has_first = first in aliases
+ has_second = second in aliases
+ if (not has_first) or (not has_second):
+ order[pos] = None, None
+ else:
+ has_order[first] = has_order[second] = True
+
+ for v in aliases:
+ # any alias that doesn't have an ordering after we detect all
+ # nodes with orders should get an ordering relative to INGRESS,
+ # as if it were added with no under or over in add_implicit
+ if (not v in has_order) and (v not in (INGRESS, MAIN)):
+ order.append((INGRESS, v))
+ ingress_alias_names.append(v)
+ add_node(graph, v)
+
+ for a, b in order:
+ if a is not None and b is not None: # deal with removed orders
+ add_arc(graph, a, b)
+
+ def sortroots(alias):
+ # sort roots so that roots (and their children) that depend only
+ # on the ingress sort nearer the (nearer the ingress)
+ if alias in ingress_alias_names:
+ return -1
+ children = graph[alias][1:]
+ for child in children:
+ if sortroots(child) == -1:
+ return -1
+ return 1
+
+ roots.sort(key=sortroots)
+
+ sorted_aliases = []
+
+ while roots:
+ root = roots.pop(0)
+ sorted_aliases.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 alias in sorted_aliases:
+ if alias not in (MAIN, INGRESS):
+ name = self.alias_to_name.get(alias, alias)
+ 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
+
+def tween_factory_name(factory):
+ if (hasattr(factory, '__name__') and
+ hasattr(factory, '__module__')):
+ # function or class
+ name = '.'.join([factory.__module__,
+ factory.__name__])
+ elif hasattr(factory, '__module__'):
+ # instance
+ name = '.'.join([factory.__module__,
+ factory.__class__.__name__,
+ str(id(factory))])
+ else:
+ raise ConfigurationError(
+ 'A tween factory must be a class, an instance, or a function; '
+ '%s is not a suitable tween factory' % factory)
+ return name
+
+MAIN = 'MAIN'
+INGRESS = 'INGRESS'
+EXCVIEW = 'excview'
+