summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-08-06 20:35:44 -0400
committerChris McDonough <chrism@plope.com>2011-08-06 20:35:44 -0400
commit6c3f78b0a5563a642b98f7a0eb01d1854c45e6b0 (patch)
tree57c0fb799ed585a8c2c5af609a297fdeccc62d8f
parent37699448a389c2712f262c574e5115aaab27adda (diff)
parent18a99ae15e7f36cd21da6e2d2e70d61d1733bf30 (diff)
downloadpyramid-6c3f78b0a5563a642b98f7a0eb01d1854c45e6b0.tar.gz
pyramid-6c3f78b0a5563a642b98f7a0eb01d1854c45e6b0.tar.bz2
pyramid-6c3f78b0a5563a642b98f7a0eb01d1854c45e6b0.zip
Merge branch 'handlerchanges'
-rw-r--r--CHANGES.txt86
-rw-r--r--docs/api.rst1
-rw-r--r--docs/api/config.rst2
-rw-r--r--docs/api/tweens.rst8
-rw-r--r--docs/glossary.rst11
-rw-r--r--docs/narr/commandline.rst75
-rw-r--r--docs/narr/hooks.rst127
-rw-r--r--pyramid/config.py155
-rw-r--r--pyramid/interfaces.py17
-rw-r--r--pyramid/paster.py66
-rw-r--r--pyramid/router.py111
-rw-r--r--pyramid/tests/test_config.py146
-rw-r--r--pyramid/tests/test_paster.py73
-rw-r--r--pyramid/tests/test_router.py25
-rw-r--r--pyramid/tweens.py45
-rw-r--r--pyramid/util.py1
-rw-r--r--setup.py1
17 files changed, 680 insertions, 270 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index c94ecb800..b6f33715e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -12,63 +12,19 @@ Features
``rendering_val``. This can be used to introspect the value returned by a
view in a BeforeRender subscriber.
-- New configurator directive:
- ``pyramid.config.Configurator.add_request_handler``. This directive adds
- a request handler factory.
-
- A request handler factory is used to wrap the Pyramid router's primary
- request handling function. This is a feature may be used by framework
- extensions, to provide, for example, view timing support and as a
- convenient place to hang bookkeeping code that examines exceptions before
- they are returned to the server.
-
- A request handler factory (passed as ``handler_factory``) must be a
- callable which accepts two arguments: ``handler`` and ``registry``.
- ``handler`` will be the request handler being wrapped. ``registry`` will
- be the Pyramid application registry represented by this Configurator. A
- request handler factory must return a request handler when it is called.
-
- A request handler accepts a request object and returns a response object.
-
- Here's an example of creating both a handler factory and a handler, and
- registering the handler factory:
-
- .. code-block:: python
-
- import time
-
- def timing_handler_factory(handler, registry):
- if registry.settings['do_timing']:
- # if timing support is enabled, return a wrapper
- def timing_handler(request):
- start = time.time()
- try:
- response = handler(request)
- finally:
- end = time.time()
- print: 'The request took %s seconds' % (end - start)
- return response
- return timing_handler
- # if timing support is not enabled, return the original handler
- return handler
-
- config.add_request_handler(timing_handler_factory, 'timing')
-
- The ``request`` argument to the handler will be the request created by
- Pyramid's router when it receives a WSGI request.
-
- If more than one request handler factory is registered into a single
- configuration, the request handlers will be chained together. The first
- request handler factory added (in code execution order) will be called with
- the default Pyramid request handler, the second handler factory added will
- be called with the result of the first handler factory, ad infinitum. The
- Pyramid router will use the outermost wrapper in this chain (which is a bit
- like a WSGI middleware "pipeline") as its handler function.
-
- The ``name`` argument to this function is required. The name is used as a
- key for conflict detection. No two request handler factories may share the
- same name in the same configuration (unless automatic_conflict_resolution
- is able to resolve the conflict or this is an autocommitting configurator).
+- New configurator directive: ``pyramid.config.Configurator.add_tween``.
+ This directive adds a "tween". A "tween" is used to wrap the Pyramid
+ router's primary request handling function. This is a feature may be used
+ by Pyramid framework extensions, to provide, for example, view timing
+ support and as a convenient place to hang bookkeeping code.
+
+ Tweens are further described in the narrative docs section in the Hooks
+ chapter, named "Registering Tweens".
+
+- New paster command ``paster ptweens``, which prints the current "tween"
+ configuration for an application. See the section entitled "Displaying
+ Tweens" in the Command-Line Pyramid chapter of the narrative documentation
+ for more info.
- The Pyramid debug logger now uses the standard logging configuration
(usually set up by Paste as part of startup). This means that output from
@@ -80,6 +36,12 @@ Features
will be ``None`` until an exception is caught by the Pyramid router, after
which it will be the result of ``sys.exc_info()``.
+Internal
+--------
+
+- The Pyramid "exception view" machinery is now implemented as a "tween"
+ (``pyramid.tweens.excview_tween_factory``).
+
Deprecations
------------
@@ -97,6 +59,16 @@ Backwards Incompatibilities
that string is considered to be the name of a global Python logger rather
than a dotted name to an instance of a logger.
+Documentation
+-------------
+
+- Added a new module to the API docs: ``pyramid.tweens``.
+
+- Added a "Registering Tweens" section to the "Hooks" narrative chapter.
+
+- Added a "Displaying Tweens" section to the "Command-Line Pyramid" narrative
+ chapter.
+
1.1 (2011-07-22)
================
diff --git a/docs/api.rst b/docs/api.rst
index a7e1566d3..6ff6e9fb1 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -32,6 +32,7 @@ documentation is organized alphabetically by module name.
api/testing
api/threadlocal
api/traversal
+ api/tweens
api/url
api/view
api/wsgi
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 21e2b828d..1a9bb6ba4 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -78,7 +78,7 @@
.. automethod:: set_view_mapper
- .. automethod:: add_request_handler
+ .. automethod:: add_tween
.. automethod:: testing_securitypolicy
diff --git a/docs/api/tweens.rst b/docs/api/tweens.rst
new file mode 100644
index 000000000..5fc15cb00
--- /dev/null
+++ b/docs/api/tweens.rst
@@ -0,0 +1,8 @@
+.. _tweens_module:
+
+:mod:`pyramid.tweens`
+---------------------
+
+.. automodule:: pyramid.tweens
+
+ .. autofunction:: excview_tween_factory
diff --git a/docs/glossary.rst b/docs/glossary.rst
index c6705fdc5..ccb62bbc8 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -917,3 +917,14 @@ Glossary
PyPy is an "alternative implementation of the Python
language":http://pypy.org/
+ 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
+ context in which they have access to the Pyramid :term:`application
+ registry` as well as the Pyramid rendering machinery.
+
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index 509af7dd3..6f969196f 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -297,8 +297,79 @@ application, nothing will be printed to the console when ``paster proutes``
is executed.
.. index::
- single: scripting
- single: bootstrap
+ pair: tweens; printing
+ single: paster ptweens
+ single: ptweens
+
+.. _displaying_tweens:
+
+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.
+
+For example, here's the ``paster pwteens`` command run against a system
+configured without any explicit tweens:
+
+.. code-block:: text
+ :linenos:
+
+ [chrism@thinko starter]$ ../bin/paster ptweens development.ini
+ "pyramid.tweens" config value NOT set (implicitly ordered tweens used)
+
+ Position Name
+ -------- ----
+ 0 pyramid.router.excview_tween_factory
+
+Here's the ``paster pwteens`` command run against a system configured *with*
+explicit tweens defined in its ``development.ini`` file:
+
+.. code-block:: text
+ :linenos:
+
+ [chrism@thinko starter]$ ../bin/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
+
+ Implicit Tween Chain (not used)
+
+ Position Name
+ -------- ----
+ 0 pyramid.tweens.excview_tween_factory
+
+Here's the application configuration section of the ``development.ini`` used
+by the above ``paster ptweens`` command which reprorts that the explicit
+tween chain is used:
+
+.. code-block:: text
+ :linenos:
+
+ [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
+ starter.tween_factory1
+ starter.tween_factory2
.. _writing_a_script:
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 4f493c854..889e8d6d8 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -828,3 +828,130 @@ performed, enabling you to set up the utility in advance:
For full details, please read the `Venusian documentation
<http://docs.repoze.org/venusian>`_.
+.. _registering_tweens:
+
+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
+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
+:term:`application registry` as well as the Pyramid rendering machinery.
+
+To make use of tweens, you must construct a "tween factory". A tween factory
+must be a callable (or a :term:`dotted Python name` to such a callable) which
+accepts two arguments: ``handler`` and ``registry``. ``handler`` will be the
+either the main Pyramid request handling function or another tween (if more
+than one tween is configured into the request handling chain). ``registry``
+will be the Pyramid :term:`application registry` represented by this
+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.
+
+Here's an example creating a tween factory and registering it:
+
+.. code-block:: python
+ :linenos:
+
+ import time
+ from pyramid.settings import asbool
+ import logging
+
+ log = logging.getLogger(__name__)
+
+ def timing_tween_factory(handler, registry):
+ if asbool(registry.settings.get('do_timing')):
+ # if timing support is enabled, return a wrapper
+ def timing_tween(request):
+ start = time.time()
+ try:
+ response = handler(request)
+ finally:
+ end = time.time()
+ log.debug('The request took %s seconds' %
+ (end - start))
+ return response
+ return timing_tween
+ # if timing support is not enabled, return the original
+ # handler
+ return handler
+
+ from pyramid.config import Configurator
+
+ config = Configurator()
+ config.add_tween(timing_tween_factory)
+
+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:
+
+.. code-block:: ini
+ :linenos:
+
+ [app:main]
+ use = egg:MyApp
+ pyramid.reload_templates = true
+ pyramid.debug_authorization = false
+ pyramid.debug_notfound = false
+ pyramid.debug_routematch = false
+ pyramid.debug_templates = true
+ pyramid.tweens = pyramid.tweens.excview_tween_factory
+ myapp.my_cool_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
+:app:`Pyramid` request handling function; it will wrap the tween factory
+declared directly "above" it, ad infinitum.
+
+.. note:: Pyramid's own :term:`exception view` handling logic is implemented
+ as a tween factory function: :func:`pyramid.tweens.excview_tween_factory`.
+ If Pyramid exception view handling is desired, and tween factories are
+ specified via the ``pyramid.tweens`` configuration setting, the
+ :func:`pyramid.tweens.excview_tween_factory` function must be added to the
+ ``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`.
+
diff --git a/pyramid/config.py b/pyramid/config.py
index 159422c22..a12df8ef7 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -38,8 +38,7 @@ from pyramid.interfaces import IRendererFactory
from pyramid.interfaces import IRendererGlobalsFactory
from pyramid.interfaces import IRequest
from pyramid.interfaces import IRequestFactory
-from pyramid.interfaces import IRequestHandlerFactory
-from pyramid.interfaces import IRequestHandlerFactories
+from pyramid.interfaces import ITweens
from pyramid.interfaces import IResponse
from pyramid.interfaces import IRootFactory
from pyramid.interfaces import IRouteRequest
@@ -80,6 +79,7 @@ from pyramid.threadlocal import manager
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.urldispatch import RoutesMapper
from pyramid.util import DottedNameResolver
from pyramid.util import WeakOrderedSet
@@ -717,11 +717,13 @@ class Configurator(object):
policies, renderers, a debug logger, a locale negotiator, and various
other settings using the configurator's current registry, as per the
descriptions in the Configurator constructor."""
+ tweens = []
+ includes = []
if settings:
- includes = settings.pop('pyramid.include', '')
- includes = [x.strip() for x in includes.splitlines()]
- else:
- includes = []
+ includes = [x.strip() for x in
+ settings.get('pyramid.include', '').splitlines()]
+ tweens = [x.strip() for x in
+ settings.get('pyramid.tweens','').splitlines()]
registry = self.registry
self._fix_registry()
self._set_settings(settings)
@@ -731,6 +733,9 @@ 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)
@@ -898,80 +903,45 @@ class Configurator(object):
return self._derive_view(view, attr=attr, renderer=renderer)
@action_method
- def add_request_handler(self, handler_factory, name):
+ def add_tween(self, tween_factory):
"""
- Add a request handler factory. A request handler factory is used to
- wrap the Pyramid router's primary request handling function. This is
- a feature that may be used by framework extensions, to provide, for
- example, view timing support and as a convenient place to hang
- bookkeeping code that examines exceptions before they are returned to
- the server.
-
- A request handler factory (passed as ``handler_factory``) must be a
- callable (or a :term:`dotted Python name` to a callable) which
- accepts two arguments: ``handler`` and ``registry``. ``handler``
- will be the request handler being wrapped. ``registry`` will be the
- Pyramid :term:`application registry` represented by this
- Configurator. A request handler factory must return a request
- handler when it is called.
-
- A request handler accepts a :term:`request` object and returns a
- :term:`response` object.
-
- Here's an example of creating both a handler factory and a handler,
- and registering the handler factory:
-
- .. code-block:: python
-
- import time
-
- def timing_handler_factory(handler, registry):
- if registry.settings['do_timing']:
- # if timing support is enabled, return a wrapper
- def timing_handler(request):
- start = time.time()
- try:
- response = handler(request)
- finally:
- end = time.time()
- print: 'The request took %s seconds' % (end - start)
- return response
- return timing_handler
- # if timing support is not enabled, return the original handler
- return handler
-
- config.add_request_handler(timing_handler_factory, 'timing')
-
- The ``request`` argument to the handler will be the request created
- by Pyramid's router when it receives a WSGI request.
-
- If more than one request handler factory is registered into a single
- configuration, the request handlers will be chained together. The
- first request handler factory added (in code execution order) will be
- called with the default Pyramid request handler, the second handler
- factory added will be called with the result of the first handler
- factory, ad infinitum. The Pyramid router will use the outermost
- wrapper in this chain (which is a bit like a WSGI middleware
- "pipeline") as its handler function.
-
- The ``name`` argument to this function is required. The name is used
- as a key for conflict detection. No two request handler factories
- may share the same name in the same configuration (unless
- :ref:`automatic_conflict_resolution` is able to resolve the conflict
- or this is an autocommitting configurator).
+ 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
+ :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.
+
+ For more information, see :ref:`registering_tweens`.
.. note:: This feature is new as of Pyramid 1.1.1.
"""
- handler_factory = self.maybe_dotted(handler_factory)
+ return self._add_tween(tween_factory, explicit=False)
+
+ def _add_tween(self, tween_factory, explicit):
+ tween_factory = self.maybe_dotted(tween_factory)
+ name = tween_factory_name(tween_factory)
def register():
registry = self.registry
- registry.registerUtility(handler_factory, IRequestHandlerFactory,
- name=name)
- existing_names = registry.queryUtility(IRequestHandlerFactories,
- default=[])
- existing_names.append(name)
- registry.registerUtility(existing_names, IRequestHandlerFactories)
- self.action(('requesthandler', name), register)
+ 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)
+
+ @action_method
+ def add_request_handler(self, factory, name): # pragma: no cover
+ # XXX bw compat for debugtoolbar
+ return self._add_tween(factory, explicit=False)
@action_method
def add_subscriber(self, subscriber, iface=None):
@@ -3407,3 +3377,40 @@ 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/interfaces.py b/pyramid/interfaces.py
index 41e896adf..d97632018 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -446,23 +446,16 @@ class IMultiDict(Interface): # docs-only interface
class IRequest(Interface):
""" Request type interface attached to all request objects """
-class IRequestHandlerFactories(Interface):
+class ITweens(Interface):
""" Marker interface for utility registration representing the ordered
- set of a configuration's request handler factories"""
-
-class IRequestHandlerFactory(Interface):
- """ A request handler factory can be used to augment Pyramid's default
- mainloop request handling."""
- def __call__(self, handler, registry):
- """ Return an IRequestHandler; the ``handler`` argument passed will
- be the previous request handler added, or the default request handler
- if no request handlers have yet been added ."""
+ set of a configuration's tween factories"""
class IRequestHandler(Interface):
""" """
def __call__(self, request):
- """ Must return an IResponse or raise an exception. The ``request``
- argument will be an instance of an object that provides IRequest."""
+ """ Must return a tuple of IReqest, IResponse or raise an exception.
+ The ``request`` argument will be an instance of an object that
+ provides IRequest."""
IRequest.combined = IRequest # for exception view lookups
diff --git a/pyramid/paster.py b/pyramid/paster.py
index 3143fa91e..54f5a51a6 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -9,8 +9,8 @@ from paste.deploy import loadapp
from paste.script.command import Command
from pyramid.interfaces import IMultiView
+from pyramid.interfaces import ITweens
-from pyramid.scripting import get_root
from pyramid.scripting import prepare
from pyramid.util import DottedNameResolver
@@ -534,3 +534,67 @@ class PViewsCommand(PCommand):
self.out(" Not found.")
self.out('')
+
+class PTweensCommand(PCommand):
+ """Print all implicit and explicit :term:`tween` objects used by a
+ Pyramid application. The handler output includes whether the system is
+ using an explicit tweens ordering (will be true when the
+ ``pyramid.tweens`` setting is used) or an implicit tweens ordering (will
+ be true when the ``pyramid.tweens`` setting is *not* used).
+
+ This command accepts one positional argument:
+
+ ``config_uri`` -- specifies the PasteDeploy config file to use for the
+ interactive shell. The format is ``inifile#name``. If the name is left
+ off, ``main`` will be assumed.
+
+ Example::
+
+ $ paster ptweens myapp.ini#main
+
+ """
+ summary = "Print all tweens related to a Pyramid application"
+ min_args = 1
+ max_args = 1
+ stdout = sys.stdout
+
+ parser = Command.standard_parser(simulate=True)
+
+ def _get_tweens(self, registry):
+ from pyramid.config import Configurator
+ config = Configurator(registry = registry)
+ return config.registry.queryUtility(ITweens)
+
+ def out(self, msg): # pragma: no cover
+ print msg
+
+ def command(self):
+ config_uri = self.args[0]
+ env = self.bootstrap[0](config_uri)
+ registry = env['registry']
+ tweens = self._get_tweens(registry)
+ if tweens is not None:
+ ordering = []
+ if tweens.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)'))
+ 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('')
diff --git a/pyramid/router.py b/pyramid/router.py
index ddec23cdb..79fd7992b 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -1,10 +1,7 @@
-import sys
-
from zope.interface import implements
from zope.interface import providedBy
from pyramid.interfaces import IDebugLogger
-from pyramid.interfaces import IExceptionViewClassifier
from pyramid.interfaces import IRequest
from pyramid.interfaces import IRootFactory
from pyramid.interfaces import IRouteRequest
@@ -14,8 +11,7 @@ from pyramid.interfaces import IRoutesMapper
from pyramid.interfaces import ITraverser
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
-from pyramid.interfaces import IRequestHandlerFactory
-from pyramid.interfaces import IRequestHandlerFactories
+from pyramid.interfaces import ITweens
from pyramid.events import ContextFound
from pyramid.events import NewRequest
@@ -25,6 +21,7 @@ from pyramid.request import Request
from pyramid.threadlocal import manager
from pyramid.traversal import DefaultRootFactory
from pyramid.traversal import ResourceTreeTraverser
+from pyramid.tweens import excview_tween_factory
class Router(object):
implements(IRouter)
@@ -40,14 +37,13 @@ class Router(object):
self.root_factory = q(IRootFactory, default=DefaultRootFactory)
self.routes_mapper = q(IRoutesMapper)
self.request_factory = q(IRequestFactory, default=Request)
- handler_factory_names = q(IRequestHandlerFactories)
- handler = self.handle_request
- if handler_factory_names:
- for name in handler_factory_names:
- handler_factory = registry.getUtility(IRequestHandlerFactory,
- name=name)
- handler = handler_factory(handler, registry)
- self.handle_request = handler
+ 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)
+
self.root_policy = self.root_factory # b/w compat
self.registry = registry
settings = registry.settings
@@ -59,16 +55,17 @@ class Router(object):
def handle_request(self, request):
attrs = request.__dict__
registry = attrs['registry']
- 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
-
- try: # matches except Exception (exception view execution)
+
+ 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
@@ -107,13 +104,12 @@ class Router(object):
)
logger and logger.debug(msg)
- request_iface = registry.queryUtility(
+ request.request_iface = registry.queryUtility(
IRouteRequest,
name=route.name,
default=IRequest)
-
- root_factory = route.factory or \
- self.root_factory
+
+ root_factory = route.factory or self.root_factory
root = root_factory(request)
attrs['root'] = root
@@ -124,8 +120,7 @@ class Router(object):
traverser = ResourceTreeTraverser(root)
tdict = traverser(request)
- context, view_name, subpath, traversed, vroot, \
- vroot_path = (
+ context, view_name, subpath, traversed, vroot, vroot_path = (
tdict['context'],
tdict['view_name'],
tdict['subpath'],
@@ -140,7 +135,7 @@ class Router(object):
# find a view callable
context_iface = providedBy(context)
view_callable = adapters.lookup(
- (IViewClassifier, request_iface, context_iface),
+ (IViewClassifier, request.request_iface, context_iface),
IView, name=view_name, default=None)
# invoke the view callable
@@ -152,8 +147,7 @@ class Router(object):
'traversed: %r, root: %r, vroot: %r, '
'vroot_path: %r' % (
request.url, request.path_info, context,
- view_name,
- subpath, traversed, root, vroot,
+ view_name, subpath, traversed, root, vroot,
vroot_path)
)
logger and logger.debug(msg)
@@ -161,39 +155,18 @@ class Router(object):
msg = request.path_info
raise HTTPNotFound(msg)
else:
- # if there were any view wrappers for the current
- # request, use them to wrap the view
-
response = view_callable(context, request)
- # handle exceptions raised during root finding and view-exec
- except Exception, why:
- # clear old generated request.response, if any; it may
- # have been mutated by the view, and its state is not
- # sane (e.g. caching headers)
- if 'response' in attrs:
- del attrs['response']
-
- attrs['exception'] = why
- attrs['exc_info'] = sys.exc_info()
+ has_listeners and notify(NewResponse(request, response))
- for_ = (IExceptionViewClassifier,
- request_iface.combined,
- providedBy(why))
- view_callable = adapters.lookup(for_, IView,
- default=None)
-
- if view_callable is None:
- raise
+ if request.response_callbacks:
+ request._process_response_callbacks(response)
- response = view_callable(why, request)
+ return response
- has_listeners and notify(NewResponse(request, response))
-
- if request.response_callbacks:
- request._process_response_callbacks(response)
-
- return response
+ finally:
+ if request is not None and request.finished_callbacks:
+ request._process_finished_callbacks()
def __call__(self, environ, start_response):
"""
@@ -204,23 +177,13 @@ class Router(object):
return an iterable.
"""
registry = self.registry
- manager = self.threadlocal_manager
- request = None
+ request = self.request_factory(environ)
threadlocals = {'registry':registry, 'request':request}
+ manager = self.threadlocal_manager
manager.push(threadlocals)
-
try:
- try:
- request = self.request_factory(environ)
- threadlocals['request'] = request
- request.registry = registry
- response = self.handle_request(request)
- finally:
- if request is not None:
- if request.finished_callbacks:
- request._process_finished_callbacks()
- request.exc_info = None # avoid leak
-
+ request.registry = registry
+ response = self.handle_request(request)
return response(request.environ, start_response)
finally:
manager.pop()
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 250c53b9a..d73fd7f7d 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -336,6 +336,7 @@ class ConfiguratorTests(unittest.TestCase):
reg = DummyRegistry()
config = self._makeOne(reg)
config.add_view = lambda *arg, **kw: False
+ config._add_tween = lambda *arg, **kw: False
config.setup_registry()
self.assertEqual(reg.has_listeners, True)
@@ -347,6 +348,7 @@ class ConfiguratorTests(unittest.TestCase):
config = self._makeOne(reg)
views = []
config.add_view = lambda *arg, **kw: views.append((arg, kw))
+ config._add_tween = lambda *arg, **kw: False
config.setup_registry()
self.assertEqual(views[0], ((default_exceptionresponse_view,),
{'context':IExceptionResponse}))
@@ -581,6 +583,20 @@ pyramid.tests.test_config.dummy_include2""",
self.assert_(reg.included)
self.assert_(reg.also_included)
+ def test_setup_registry_tweens(self):
+ from pyramid.interfaces import ITweens
+ from pyramid.registry import Registry
+ reg = Registry()
+ config = self._makeOne(reg)
+ settings = {
+ 'pyramid.tweens': 'pyramid.tests.test_config.dummy_tween_factory'
+ }
+ config.setup_registry(settings=settings)
+ tweens = config.registry.getUtility(ITweens)
+ self.assertEqual(tweens.explicit,
+ [('pyramid.tests.test_config.dummy_tween_factory',
+ dummy_tween_factory)])
+
def test_get_settings_nosettings(self):
from pyramid.registry import Registry
reg = Registry()
@@ -612,48 +628,66 @@ pyramid.tests.test_config.dummy_include2""",
settings = reg.getUtility(ISettings)
self.assertEqual(settings['a'], 1)
- def test_add_request_handlers_names_distinct(self):
- from pyramid.interfaces import IRequestHandlerFactories
- from pyramid.interfaces import IRequestHandlerFactory
+ def test_add_tweens_names_distinct(self):
+ from pyramid.interfaces import ITweens
+ from pyramid.tweens import excview_tween_factory
def factory1(handler, registry): return handler
def factory2(handler, registry): return handler
config = self._makeOne()
- config.add_request_handler(factory1, 'name1')
- config.add_request_handler(factory2, 'name2')
+ config.add_tween(factory1)
+ config.add_tween(factory2)
config.commit()
- names = config.registry.queryUtility(IRequestHandlerFactories)
- self.assertEqual(names, ['name1', 'name2'])
- f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1')
- f2 = config.registry.getUtility(IRequestHandlerFactory, name='name2')
- self.assertEqual(f1, factory1)
- self.assertEqual(f2, factory2)
-
- def test_add_request_handlers_dottednames(self):
- import pyramid.tests
- from pyramid.interfaces import IRequestHandlerFactories
- from pyramid.interfaces import IRequestHandlerFactory
+ tweens = config.registry.queryUtility(ITweens)
+ self.assertEqual(
+ tweens.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
+ from pyramid.tweens import excview_tween_factory
config = self._makeOne()
- config.add_request_handler('pyramid.tests', 'name1')
+ config.add_tween('pyramid.tests.test_config.dummy_tween_factory')
config.commit()
- names = config.registry.queryUtility(IRequestHandlerFactories)
- self.assertEqual(names, ['name1'])
- f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1')
- self.assertEqual(f1, pyramid.tests)
-
- def test_add_request_handlers_names_overlap(self):
- from pyramid.interfaces import IRequestHandlerFactories
- from pyramid.interfaces import IRequestHandlerFactory
- def factory1(handler, registry): return handler
- def factory2(handler, registry): return handler
- def factory3(handler, registry): return handler
- config = self._makeOne(autocommit=True)
- config.add_request_handler(factory1, 'name1')
- config.add_request_handler(factory2, 'name2')
- config.add_request_handler(factory3, 'name1')
- names = config.registry.queryUtility(IRequestHandlerFactories)
- self.assertEqual(names, ['name1', 'name2', 'name1'])
- f3 = config.registry.getUtility(IRequestHandlerFactory, name='name1')
- self.assertEqual(f3, factory3)
+ tweens = config.registry.queryUtility(ITweens)
+ self.assertEqual(
+ tweens.implicit,
+ [
+ ('pyramid.tweens.excview_tween_factory', excview_tween_factory),
+ ('pyramid.tests.test_config.dummy_tween_factory',
+ dummy_tween_factory)
+ ])
+
+ def test_add_tween_instance(self):
+ from pyramid.interfaces import ITweens
+ from pyramid.tweens import excview_tween_factory
+ class ATween(object): pass
+ atween = ATween()
+ config = self._makeOne()
+ config.add_tween(atween)
+ config.commit()
+ tweens = config.registry.queryUtility(ITweens)
+ self.assertEqual(len(tweens.implicit), 2)
+ self.assertEqual(
+ tweens.implicit[0],
+ ('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
+ import pyramid.tests
+ config = self._makeOne()
+ self.assertRaises(ConfigurationError, config.add_tween, pyramid.tests)
+
+ def test_add_tweens_conflict(self):
+ from zope.configuration.config import ConfigurationConflictError
+ config = self._makeOne()
+ config.add_tween('pyramid.tests.test_config.dummy_tween_factory')
+ config.add_tween('pyramid.tests.test_config.dummy_tween_factory')
+ self.assertRaises(ConfigurationConflictError, config.commit)
def test_add_subscriber_defaults(self):
from zope.interface import implements
@@ -5477,6 +5511,45 @@ 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
@@ -5663,3 +5736,4 @@ from pyramid.interfaces import IResponse
class DummyResponse(object):
implements(IResponse)
+def dummy_tween_factory(handler, registry): pass
diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py
index 3cf249c5c..58ed73d2c 100644
--- a/pyramid/tests/test_paster.py
+++ b/pyramid/tests/test_paster.py
@@ -824,6 +824,79 @@ class TestBootstrap(unittest.TestCase):
self.assertEqual(result['root'], self.root)
self.assert_('closer' in result)
+class TestPTweensCommand(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.paster import PTweensCommand
+ return PTweensCommand
+
+ def _makeOne(self):
+ cmd = self._getTargetClass()('ptweens')
+ cmd.bootstrap = (DummyBootstrap(),)
+ cmd.args = ('/foo/bar/myapp.ini#myapp',)
+ return cmd
+
+ def test_command_no_tweens(self):
+ command = self._makeOne()
+ command._get_tweens = lambda *arg: None
+ L = []
+ command.out = L.append
+ result = command.command()
+ self.assertEqual(result, None)
+ self.assertEqual(L, [])
+
+ def test_command_implicit_tweens_only(self):
+ command = self._makeOne()
+ tweens = DummyTweens([('name', 'item')], None)
+ command._get_tweens = lambda *arg: tweens
+ L = []
+ command.out = L.append
+ result = command.command()
+ self.assertEqual(result, None)
+ self.assertEqual(
+ L,
+ ['"pyramid.tweens" config value NOT set (implicitly ordered tweens used)',
+ '',
+ 'Position Name ',
+ '-------- ---- ',
+ '0 name ',
+ ''])
+
+ def test_command_implicit_and_explicit_tweens(self):
+ command = self._makeOne()
+ tweens = DummyTweens([('name', 'item')], [('name2', 'item2')])
+ command._get_tweens = lambda *arg: tweens
+ L = []
+ command.out = L.append
+ 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 ',
+ ''
+ ])
+
+ def test__get_tweens(self):
+ command = self._makeOne()
+ registry = DummyRegistry()
+ self.assertEqual(command._get_tweens(registry), None)
+
+class DummyTweens(object):
+ def __init__(self, implicit, explicit):
+ self.implicit = implicit
+ self.explicit = explicit
+
class Dummy:
pass
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index b943f1ee6..6b0354468 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -134,11 +134,16 @@ class TestRouter(unittest.TestCase):
router = self._makeOne()
self.assertEqual(router.request_factory, DummyRequestFactory)
- def test_request_handler_factories(self):
- from pyramid.interfaces import IRequestHandlerFactory
- from pyramid.interfaces import IRequestHandlerFactories
+ def test_tween_factories(self):
+ from pyramid.interfaces import ITweens
+ from pyramid.config import Tweens
+ from pyramid.response import Response
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IResponse
+ tweens = Tweens()
+ self.registry.registerUtility(tweens, ITweens)
L = []
- def handler_factory1(handler, registry):
+ def tween_factory1(handler, registry):
L.append((handler, registry))
def wrapper(request):
request.environ['handled'].append('one')
@@ -146,7 +151,7 @@ class TestRouter(unittest.TestCase):
wrapper.name = 'one'
wrapper.child = handler
return wrapper
- def handler_factory2(handler, registry):
+ def tween_factory2(handler, registry):
L.append((handler, registry))
def wrapper(request):
request.environ['handled'] = ['two']
@@ -154,19 +159,13 @@ class TestRouter(unittest.TestCase):
wrapper.name = 'two'
wrapper.child = handler
return wrapper
- self.registry.registerUtility(['one', 'two'], IRequestHandlerFactories)
- self.registry.registerUtility(handler_factory1,
- IRequestHandlerFactory, name='one')
- self.registry.registerUtility(handler_factory2,
- IRequestHandlerFactory, name='two')
+ tweens.add('one', tween_factory1)
+ tweens.add('two', tween_factory2)
router = self._makeOne()
self.assertEqual(router.handle_request.name, 'two')
self.assertEqual(router.handle_request.child.name, 'one')
self.assertEqual(router.handle_request.child.child.__name__,
'handle_request')
- from pyramid.response import Response
- from pyramid.interfaces import IViewClassifier
- from pyramid.interfaces import IResponse
context = DummyContext()
self._registerTraverserFactory(context)
environ = self._makeEnviron()
diff --git a/pyramid/tweens.py b/pyramid/tweens.py
new file mode 100644
index 000000000..f7673a738
--- /dev/null
+++ b/pyramid/tweens.py
@@ -0,0 +1,45 @@
+import sys
+from pyramid.interfaces import IExceptionViewClassifier
+from pyramid.interfaces import IView
+
+from pyramid.events import NewResponse
+from zope.interface import providedBy
+
+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__
+ try:
+ response = handler(request)
+ except Exception, exc:
+ # WARNING: do not assign the result of sys.exc_info() to a
+ # local var here, doing so will cause a leak
+ attrs['exc_info'] = sys.exc_info()
+ attrs['exception'] = exc
+ # clear old generated request.response, if any; it may
+ # have been mutated by the view, and its state is not
+ # sane (e.g. caching headers)
+ if 'response' in attrs:
+ del attrs['response']
+ request_iface = attrs['request_iface']
+ provides = providedBy(exc)
+ for_ = (IExceptionViewClassifier, request_iface.combined, provides)
+ view_callable = adapters.lookup(for_, IView, default=None)
+ if view_callable is None:
+ raise
+ response = view_callable(exc, request)
+ has_listeners and notify(NewResponse(request, response))
+ finally:
+ attrs['exc_info'] = None
+
+ return response
+
+ return excview_tween
+
diff --git a/pyramid/util.py b/pyramid/util.py
index 7fd1b0dc6..c0e7640c4 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -206,3 +206,4 @@ class WeakOrderedSet(object):
if self._order:
oid = self._order[-1]
return self._items[oid]()
+
diff --git a/setup.py b/setup.py
index 109be6951..5e362ea86 100644
--- a/setup.py
+++ b/setup.py
@@ -86,6 +86,7 @@ setup(name='pyramid',
pshell=pyramid.paster:PShellCommand
proutes=pyramid.paster:PRoutesCommand
pviews=pyramid.paster:PViewsCommand
+ ptweens=pyramid.paster:PTweensCommand
[console_scripts]
bfg2pyramid = pyramid.fixers.fix_bfg_imports:main
"""