From b4843bc087524320460fb3fca9d33688cafa0dbb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 17:38:37 -0400 Subject: requesthandlers->tweens; add ptweens command --- pyramid/config.py | 183 +++++++++++++++++++++++-------------------- pyramid/interfaces.py | 4 +- pyramid/paster.py | 66 +++++++++++++++- pyramid/router.py | 14 ++-- pyramid/tests/test_config.py | 4 +- pyramid/tests/test_router.py | 24 +++--- setup.py | 1 + 7 files changed, 185 insertions(+), 111 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 6a7b4d328..259be7688 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -38,7 +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 IRequestHandlers +from pyramid.interfaces import ITweens from pyramid.interfaces import IResponse from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest @@ -719,12 +719,11 @@ class Configurator(object): if settings: includes = settings.get('pyramid.include', '') includes = [x.strip() for x in includes.splitlines()] - expl_handler_factories = settings.get('pyramid.request_handlers','') - expl_handler_factories = [x.strip() for x in - expl_handler_factories.splitlines()] + expl_tweens = settings.get('pyramid.tweens','') + expl_tweens = [x.strip() for x in expl_tweens.splitlines()] else: includes = [] - expl_handler_factories = [] + expl_tweens = [] registry = self.registry self._fix_registry() self._set_settings(settings) @@ -735,10 +734,10 @@ class Configurator(object): from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) # add a handler manager - handler_manager = RequestHandlers() - registry.registerUtility(handler_manager, IRequestHandlers) - self._add_request_handler('pyramid.router.exc_view_handler_factory', - explicit=False) + tweens = Tweens() + registry.registerUtility(tweens, ITweens) + self._add_tween('pyramid.router.exc_view_tween_factory', + explicit=False) if debug_logger is None: debug_logger = logging.getLogger(self.package_name) @@ -785,8 +784,8 @@ class Configurator(object): if default_view_mapper is not None: self.set_view_mapper(default_view_mapper) self.commit() - for factory in expl_handler_factories: - self._add_request_handler(factory, explicit=True) + for factory in expl_tweens: + self._add_tween(factory, explicit=True) for inc in includes: self.include(inc) @@ -908,31 +907,32 @@ class Configurator(object): return self._derive_view(view, attr=attr, renderer=renderer) @action_method - def add_request_handler(self, handler_factory): + def add_tween(self, tween_factory): """ - Add a request handler factory. A request handler factory is used to - wrap the Pyramid router's main 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 + Add a 'tween factory'. A '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 framework extensions, + to provide, for example, Pyramid-specific view timing support bookkeeping code that examines exceptions before they are returned to - the WSGI server. Request handlers behave a bit like :mod:`WSGI` + 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 tot he Pyramid :term:`application registry` as + which they have access to the Pyramid :term:`application registry` as well as the Pyramid rendering machinery. - A request handler factory (passed as ``handler_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 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 tween factory (passed as ``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 request handler accepts a :term:`request` object and returns a - :term:`response` object. + A tween accepts a :term:`request` object and returns a two-tuple + consisting of a :term:`request` object and a :term:`response` object. - Here's an example creating a handler factory and registering the - handler factory: + Here's an example creating a tween factory and registering it: .. code-block:: python @@ -942,83 +942,92 @@ class Configurator(object): log = logging.getLogger(__name__) - def timing_handler_factory(handler, registry): + def timing_tween_factory(handler, registry): if asbool(registry.settings.get('do_timing')): # if timing support is enabled, return a wrapper - def timing_handler(request): + def timing_tween(request): start = time.time() try: - response = handler(request) + request, response = handler(request) finally: end = time.time() log.debug('The request took %s seconds' % (end - start)) - return response - return timing_handler + return request, response + return timing_tween # if timing support is not enabled, return the original # handler return handler - config.add_request_handler(timing_handler_factory) + config.add_tween(timing_tween_factory) - The ``request`` argument to the handler will be the request created - by Pyramid's router when it receives a WSGI request. + 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 ``add_request_handler`` is made within a - single application configuration, request handlers will be chained - together. The first request handler factory added will be called - with the default Pyramid request handler as its ``handler`` argument, - the second handler factory added will be called with the result of - the first handler factory, and so on, ad infinitum. The Pyramid - router will use the outermost handler produced by this chain (the - very last handler added) as its handler function. + If more than one call to ``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 very last + tween added) as its request handler function. By default, the ordering of the chain is controlled entirely by the - relative ordering of calls to ``add_request_handler``. However, the - deploying user can override request handler inclusion and ordering in - his Pyramid configuration using the ``pyramid.request_handlers`` - settings value. When used, this settings value should be a list of - Python dotted names which imply the ordering (and inclusion) of - handler factories in the request handler chain. - - Pyramid will prevent the same request handler factory from being - added to the request handling chain more than once using - configuration conflict detection. If you wish to add the same - handler factory more than once in a configuration, you should either: - a) use a handler that is an *instance* rather than a function or - class, or b) use a function or class with the same logic as the other - it conflicts with but with a different ``__name__`` attribute. - - A user can get a representation of both the implicit request handler - ordering (the ordering specified by calls to ``add_request_handler``) - and the explicit request handler ordering (specified by the - ``pyramid.request_handlers`` setting) orderings using the ``paster - phandlers`` command. Handler factories which are functions or - classes will show up as a standard Python dotted name in the - ``phandlers`` output. Handler factories which are *instances* will - show their module and class name; the Python object id of the - instance will be appended. + relative ordering of calls to ``add_tween``. However, the deploying + user can override tween inclusion and ordering in his Pyramid + configuration using the ``pyramid.tweens`` settings value. When + used, this settings value should be a list of Python dotted names + which imply the ordering (and inclusion) of tween factories in the + tween chain. + + 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, or 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. + + A user can get a representation of both the implicit tween ordering + (the ordering specified by calls to ``add_tween``) and the explicit + request handler ordering (specified by the ``pyramid.tweens`` + 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 ``ptweens`` output. Tween factories + which are *instances* will show their module and class name; the + Python object id of the instance will be appended. .. note:: This feature is new as of Pyramid 1.1.1. """ - return self._add_request_handler(handler_factory, explicit=False) + return self._add_tween(tween_factory, explicit=False) - def _add_request_handler(self, handler_factory, explicit): - handler_factory = self.maybe_dotted(handler_factory) - if hasattr(handler_factory, '__name__'): + # XXX temporary bw compat for debugtoolbar + def add_request_handler(self, factory, name): + return self._add_tween(factory, explicit=False) + + def _add_tween(self, tween_factory, explicit): + tween_factory = self.maybe_dotted(tween_factory) + if (hasattr(tween_factory, '__name__') and + hasattr(tween_factory, '__module__')): # function or class - name = '.'.join((handler_factory.__module__, - handler_factory.__name__)) - else: + name = '.'.join([tween_factory.__module__, + tween_factory.__name__]) + elif hasattr(tween_factory, '__module__'): # instance - name = '.'.join(handler_factory.__module__, - handler_factory.__class__.__name__, - str(id(handler_factory))) + name = '.'.join([tween_factory.__module__, + tween_factory.__class__.__name__, + str(id(tween_factory))]) + else: + raise ConfigurationError( + 'A tween factory must be a class, an instance, or a function; ' + '%s is not a suitable tween factory' % tween_factory) def register(): registry = self.registry - handler_manager = registry.getUtility(IRequestHandlers) - handler_manager.add(name, handler_factory, explicit) - self.action(('request_handler', name), register) + handler_manager = registry.getUtility(ITweens) + handler_manager.add(name, tween_factory, explicit) + self.action(('tween', name), register) @action_method def add_subscriber(self, subscriber, iface=None): @@ -3455,8 +3464,8 @@ def isexception(o): global_registries = WeakOrderedSet() -class RequestHandlers(object): - implements(IRequestHandlers) +class Tweens(object): + implements(ITweens) def __init__(self): self.explicit = [] self.implicit = [] @@ -3468,10 +3477,10 @@ class RequestHandlers(object): self.implicit.append((name, factory)) def __call__(self, handler, registry): - handler_factories = self.implicit + factories = self.implicit if self.explicit: - handler_factories = self.explicit - for name, factory in handler_factories: + factories = self.explicit + for name, factory in factories: handler = factory(handler, registry) return handler diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index c4bacd46d..d97632018 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -446,9 +446,9 @@ class IMultiDict(Interface): # docs-only interface class IRequest(Interface): """ Request type interface attached to all request objects """ -class IRequestHandlers(Interface): +class ITweens(Interface): """ Marker interface for utility registration representing the ordered - set of a configuration's request handler factories""" + set of a configuration's tween factories""" class IRequestHandler(Interface): """ """ 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 d8f5a08da..8cac733fb 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -14,7 +14,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 IRequestHandlers +from pyramid.interfaces import ITweens from pyramid.events import ContextFound from pyramid.events import NewRequest @@ -39,12 +39,12 @@ class Router(object): self.root_factory = q(IRootFactory, default=DefaultRootFactory) self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) - handler_manager = q(IRequestHandlers) - if handler_manager is None: - self.handle_request = exc_view_handler_factory(self.handle_request, - registry) + tweens = q(ITweens) + if tweens is None: + self.handle_request = exc_view_tween_factory(self.handle_request, + registry) else: - self.handle_request = handler_manager(self.handle_request, registry) + self.handle_request = tweens(self.handle_request, registry) self.root_policy = self.root_factory # b/w compat self.registry = registry @@ -190,7 +190,7 @@ class Router(object): finally: manager.pop() -def exc_view_handler_factory(handler, registry): +def exc_view_tween_factory(handler, registry): has_listeners = registry.has_listeners adapters = registry.adapters notify = registry.notify diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index ebc3f85cc..ef2bb24d7 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -336,7 +336,7 @@ class ConfiguratorTests(unittest.TestCase): reg = DummyRegistry() config = self._makeOne(reg) config.add_view = lambda *arg, **kw: False - config._add_request_handler = lambda *arg, **kw: False + config._add_tween = lambda *arg, **kw: False config.setup_registry() self.assertEqual(reg.has_listeners, True) @@ -348,7 +348,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(reg) views = [] config.add_view = lambda *arg, **kw: views.append((arg, kw)) - config._add_request_handler = lambda *arg, **kw: False + config._add_tween = lambda *arg, **kw: False config.setup_registry() self.assertEqual(views[0], ((default_exceptionresponse_view,), {'context':IExceptionResponse})) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 6921de179..6b0354468 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -134,13 +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 IRequestHandlers - from pyramid.config import RequestHandlers - handler_manager = RequestHandlers() - self.registry.registerUtility(handler_manager, IRequestHandlers) + 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') @@ -148,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'] @@ -156,16 +159,13 @@ class TestRouter(unittest.TestCase): wrapper.name = 'two' wrapper.child = handler return wrapper - handler_manager.add('one', handler_factory1) - handler_manager.add('two', handler_factory2) + 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/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 """ -- cgit v1.2.3