From 95c9f6f331bd3294699969ae399045891c0dc6ad Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 30 Dec 2010 02:07:36 -0500 Subject: - The "view derivation" code is now factored into a set of classes rather than a large number of standalone functions (a side effect of the ``view_mapper`` refactoring). - The ``pyramid.renderer.RendererHelper`` class has grown a ``render_view`` method, which is used by the default view mapper (a side effect of the ``view_mapper`` refactoring). - The object passed as ``renderer`` to the "view deriver" is now an instance of ``pyramid.renderers.RendererHelper`` rather than a dictionary (a side effect of ``view_mapper`` refactoring). --- CHANGES.txt | 32 ++++++-- TODO.txt | 6 +- pyramid/config.py | 187 ++++++++++++++++--------------------------- pyramid/renderers.py | 12 +++ pyramid/tests/test_config.py | 59 +++++++++----- pyramid/tests/test_view.py | 11 ++- pyramid/view.py | 12 ++- pyramid/zcml.py | 5 -- 8 files changed, 163 insertions(+), 161 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fabb882f7..018de8cf7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,12 +13,19 @@ Bug Fixes Features -------- -- config.add_view now accepts a 'decorator' keyword argument, a - callable which will decorate the view callable before it is added to - the registry +- ``config.add_view`` now accepts a ``decorator`` keyword argument, a callable + which will decorate the view callable before it is added to the registry. -- If a handler class provides an _action_decorator classmethod, use that - as the decorator for each view registration for that handler. +- ``config.add_view`` now accepts a ``view_mapper`` keyword argument, which + should be a class which implements the new + ``pyramid.interfaces.IViewMapperFactory`` interface. Use of an alternate + view mapper allows objects that are meant to be used as view callables to + have an arbitrary argument list and an arbitrary result. This feature will + be used by Pyramid extension developers, not by "civilians". + +- If a handler class provides an __action_decorator__ attribute (usually a + classmethod or staticmethod), use that as the decorator for each view + registration for that handler. Documentation ------------- @@ -27,6 +34,21 @@ Documentation removed from the tutorials section. It was moved to the ``pyramid_tutorials`` Github repository. +Internals +--------- + +- The "view derivation" code is now factored into a set of classes rather + than a large number of standalone functions (a side effect of the + ``view_mapper`` refactoring). + +- The ``pyramid.renderer.RendererHelper`` class has grown a ``render_view`` + method, which is used by the default view mapper (a side effect of the + ``view_mapper`` refactoring). + +- The object passed as ``renderer`` to the "view deriver" is now an instance + of ``pyramid.renderers.RendererHelper`` rather than a dictionary (a side + effect of ``view_mapper`` refactoring). + 1.0a8 (2010-12-27) ================== diff --git a/TODO.txt b/TODO.txt index ada3c4c2a..5acc923a1 100644 --- a/TODO.txt +++ b/TODO.txt @@ -11,8 +11,10 @@ Must-Have (before 1.0) - Re-make testing.setUp() and testing.tearDown() the canonical APIs for test configuration. -- ``decorator=`` parameter to view_config. This would replace the existing - _map_view "decorator" if it existed (Rob needs). +- Document ``decorator=`` and ``view_mapper`` parameters to add_view. + +- Allow ``decorator=`` and ``view_mapper=`` to be passed via ZCML and the + ``view_config`` decorator. Should-Have ----------- diff --git a/pyramid/config.py b/pyramid/config.py index 077db28ab..a53c21b81 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -345,6 +345,15 @@ class Configurator(object): viewname=None, accept=None, order=MAX_ORDER, phash=DEFAULT_PHASH): view = self.maybe_dotted(view) + if isinstance(renderer, basestring): + renderer = RendererHelper(name=renderer, package=self.package, + registry = self.registry) + if renderer is None: + # use default renderer if one exists + if self.registry.queryUtility(IRendererFactory) is not None: + renderer = RendererHelper(name=None, + package=self.package, + registry=self.registry) deriver = ViewDeriver( registry=self.registry, permission=permission, @@ -753,9 +762,6 @@ class Configurator(object): a :term:`response` object. If a ``renderer`` argument is not supplied, the user-supplied view must itself return a :term:`response` object. """ - - if renderer is not None and not isinstance(renderer, dict): - renderer = {'name':renderer, 'package':self.package} return self._derive_view(view, attr=attr, renderer=renderer) @action_method @@ -1284,9 +1290,9 @@ class Configurator(object): performs view argument and response mapping. By default it is ``None``, which indicates that the view should use the default view mapper. This plug-point is useful for Pyramid extension - developers, but it's not very useful for' - 'civilians' who are just developing stock Pyramid applications. - Pay no attention to the man behind the curtain. + developers, but it's not very useful for 'civilians' who are + just developing stock Pyramid applications. Pay no attention to + the man behind the curtain. """ view = self.maybe_dotted(view) @@ -1338,9 +1344,6 @@ class Configurator(object): containment=containment, request_type=request_type, custom=custom_predicates) - if renderer is not None and not isinstance(renderer, dict): - renderer = {'name':renderer, 'package':self.package} - if context is None: context = for_ @@ -1350,7 +1353,17 @@ class Configurator(object): if not IInterface.providedBy(r_context): r_context = implementedBy(r_context) - def register(permission=permission): + if isinstance(renderer, basestring): + renderer = RendererHelper(name=renderer, package=self.package, + registry = self.registry) + + def register(permission=permission, renderer=renderer): + if renderer is None: + # use default renderer if one exists + if self.registry.queryUtility(IRendererFactory) is not None: + renderer = RendererHelper(name=None, + package=self.package, + registry=self.registry) if permission is None: # intent: will be None if no default permission is registered @@ -2002,8 +2015,9 @@ class Configurator(object): The ``wrapper`` argument should be the name of another view which will wrap this view when rendered (see the ``add_view`` method's ``wrapper`` argument for a description).""" - if renderer is not None and not isinstance(renderer, dict): - renderer = {'name':renderer, 'package':self.package} + if isinstance(renderer, basestring): + renderer = RendererHelper(name=renderer, package=self.package, + registry = self.registry) view = self._derive_view(view, attr=attr, renderer=renderer) def bwcompat_view(context, request): context = getattr(request, 'context', None) @@ -2041,8 +2055,9 @@ class Configurator(object): which will wrap this view when rendered (see the ``add_view`` method's ``wrapper`` argument for a description). """ - if renderer is not None and not isinstance(renderer, dict): - renderer = {'name':renderer, 'package':self.package} + if isinstance(renderer, basestring): + renderer = RendererHelper(name=renderer, package=self.package, + registry=self.registry) view = self._derive_view(view, attr=attr, renderer=renderer) def bwcompat_view(context, request): context = getattr(request, 'context', None) @@ -2853,23 +2868,12 @@ class ViewDeriver(object): class DefaultViewMapper(object): implements(IViewMapperFactory) def __init__(self, **kw): - self.kw = kw - self.helper = None self.renderer = kw.get('renderer') - self.registry = kw.get('registry') - if self.renderer is None and self.registry is not None: - # use default renderer if one exists - default_renderer_factory = self.registry.queryUtility( - IRendererFactory) - if default_renderer_factory is not None: - self.renderer = {'name':None, 'package':kw.get('package')} - if self.renderer is not None: - self.helper = RendererHelper(self.renderer['name'], - package=self.renderer['package'], - registry=self.registry) + self.attr = kw.get('attr') + self.decorator = kw.get('decorator') def requestonly(self, view): - attr = self.kw.get('attr') + attr = self.attr if attr is None: attr = '__call__' if inspect.isfunction(view): @@ -2911,145 +2915,90 @@ class DefaultViewMapper(object): return False def __call__(self, view): - attr = self.kw.get('attr') - decorator = self.kw.get('decorator') + attr = self.attr + decorator = self.decorator isclass = inspect.isclass(view) ronly = self.requestonly(view) if isclass and ronly: - view = self.map_requestonly_class(view) + view = preserve_view_attrs(view, self.map_requestonly_class(view)) elif isclass: - view = self.map_class(view) + view = preserve_view_attrs(view, self.map_class(view)) elif ronly: - view = self.map_requestonly_func(view) + view = preserve_view_attrs(view, self.map_requestonly_func(view)) elif attr: - view = self.map_attr(view) - elif self.helper is not None: - view = self.map_rendered(view) + view = preserve_view_attrs(view, self.map_attr(view)) + elif self.renderer is not None: + view = preserve_view_attrs(view, self.map_rendered(view)) if decorator is not None: - decorated_view = decorator(view) - view = preserve_view_attrs(view, decorated_view) + view = preserve_view_attrs(view, decorator(view)) return view - @wraps_view def map_requestonly_class(self, view): # its a class that has an __init__ which only accepts request - attr = self.kw.get('attr') - helper = self.helper - renderer = self.renderer + attr = self.attr def _class_requestonly_view(context, request): inst = view(request) if attr is None: response = inst() else: response = getattr(inst, attr)() - if helper is not None: - if not is_response(response): - system = { - 'view':inst, - 'renderer_name':renderer['name'], # b/c - 'renderer_info':renderer, - 'context':context, - 'request':request - } - response = helper.render_to_response(response, system, - request=request) + if self.renderer is not None and not is_response(response): + response = self.renderer.render_view(request, response, view, + context) return response return _class_requestonly_view - @wraps_view def map_class(self, view): # its a class that has an __init__ which accepts both context and # request - attr = self.kw.get('attr') - helper = self.helper - renderer = self.renderer + attr = self.attr def _class_view(context, request): inst = view(context, request) if attr is None: response = inst() else: response = getattr(inst, attr)() - if helper is not None: - if not is_response(response): - system = {'view':inst, - 'renderer_name':renderer['name'], # b/c - 'renderer_info':renderer, - 'context':context, - 'request':request - } - response = helper.render_to_response(response, system, - request=request) + if self.renderer is not None and not is_response(response): + response = self.renderer.render_view(request, response, view, + context) return response return _class_view - @wraps_view def map_requestonly_func(self, view): - # its a function or instance that has a __call__ accepts only a - # single request argument - attr = self.kw.get('attr') - helper = self.helper - renderer = self.renderer + # its a function that has a __call__ which accepts only a single + # request argument + attr = self.attr def _requestonly_view(context, request): if attr is None: response = view(request) else: response = getattr(view, attr)(request) - - if helper is not None: - if not is_response(response): - system = { - 'view':view, - 'renderer_name':renderer['name'], - 'renderer_info':renderer, - 'context':context, - 'request':request - } - response = helper.render_to_response(response, system, - request=request) + if self.renderer is not None and not is_response(response): + response = self.renderer.render_view(request, response, view, + context) return response return _requestonly_view - @wraps_view def map_attr(self, view): # its a function that has a __call__ which accepts both context and # request, but still has an attr - attr = self.kw.get('attr') - helper = self.helper - renderer = self.renderer + attr = self.attr def _attr_view(context, request): response = getattr(view, attr)(context, request) - if helper is not None: - if not is_response(response): - system = { - 'view':view, - 'renderer_name':renderer['name'], - 'renderer_info':renderer, - 'context':context, - 'request':request - } - response = helper.render_to_response(response, system, - request=request) + if self.renderer is not None and not is_response(response): + response = self.renderer.render_view(request, response, view, + context) return response return _attr_view - @wraps_view def map_rendered(self, view): # it's a function that has a __call__ that accepts both context and # request, but requires rendering - helper = self.helper - renderer = self.renderer def _rendered_view(context, request): response = view(context, request) - if not is_response(response): - system = { - 'view':view, - 'renderer_name':renderer['name'], # b/c - 'renderer_info':renderer, - 'context':context, - 'request':request - } - response = helper.render_to_response(response, system, - request=request) + if self.renderer is not None and not is_response(response): + response = self.renderer.render_view(request, response, view, + context) return response return _rendered_view @@ -3102,12 +3051,6 @@ class PyramidConfigurationMachine(ConfigurationMachine): self._seen_files.add(spec) return True -def is_response(ob): - if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and - hasattr(ob, 'status') ): - return True - return False - # b/c def _map_view(view, registry, attr=None, renderer=None): return DefaultViewMapper(registry=registry, attr=attr, @@ -3119,3 +3062,9 @@ def requestonly(view, attr=None): as opposed to something that accepts context, request """ return DefaultViewMapper(attr=attr).requestonly(view) +def is_response(ob): + if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and + hasattr(ob, 'status') ): + return True + return False + diff --git a/pyramid/renderers.py b/pyramid/renderers.py index c7fe86452..2e0514b01 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -282,6 +282,18 @@ class RendererHelper(object): def get_renderer(self): return self.renderer + def render_view(self, request, response, view, context): + system = { + 'view':view, + 'renderer_name':self.name, # b/c + 'renderer_info':{'name':self.name, 'package':self.package}, + 'context':context, + 'request':request + } + return self.render_to_response(response, system, + request=request) + + def render(self, value, system_values, request=None): renderer = self.renderer if system_values is None: diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index d237592d5..b2fa0e329 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -1426,6 +1426,29 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(result.name, fixture) self.assertEqual(result.settings, settings) + def test_add_view_with_default_renderer(self): + import pyramid.tests + from pyramid.interfaces import ISettings + class view(object): + def __init__(self, context, request): + self.request = request + self.context = context + + def __call__(self): + return {'a':'1'} + config = self._makeOne(autocommit=True) + class moo(object): + def __init__(self, *arg, **kw): + pass + def __call__(self, *arg, **kw): + return 'moo' + config.add_renderer(None, moo) + config.add_view(view=view) + wrapper = self._getViewCallable(config) + request = self._makeRequest(config) + result = wrapper(None, request) + self.assertEqual(result.body, 'moo') + def test_add_view_with_template_renderer_no_callable(self): import pyramid.tests from pyramid.interfaces import ISettings @@ -3547,16 +3570,17 @@ class Test__map_view(unittest.TestCase): def _registerRenderer(self, typ='.txt'): from pyramid.interfaces import IRendererFactory from pyramid.interfaces import ITemplateRenderer + from pyramid.renderers import RendererHelper from zope.interface import implements - class Renderer: + class DummyRenderer: implements(ITemplateRenderer) - spec = 'abc' + typ def __init__(self, path): self.__class__.path = path def __call__(self, *arg): return 'Hello!' - self.registry.registerUtility(Renderer, IRendererFactory, name=typ) - return Renderer + self.registry.registerUtility(DummyRenderer, IRendererFactory, name=typ) + renderer = RendererHelper(name='abc' + typ, registry=self.registry) + return renderer def _makeRequest(self): request = DummyRequest() @@ -3584,8 +3608,7 @@ class Test__map_view(unittest.TestCase): def test__map_view_as_function_with_attr_and_renderer(self): renderer = self._registerRenderer() view = lambda *arg: 'OK' - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, attr='__name__', renderer=info) + result = self._callFUT(view, attr='__name__', renderer=renderer) self.failIf(result is view) self.assertRaises(TypeError, result, None, None) @@ -3643,8 +3666,7 @@ class Test__map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, attr='index', renderer=info) + result = self._callFUT(view, attr='index', renderer=renderer) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -3685,8 +3707,7 @@ class Test__map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, attr='index', renderer=info) + result = self._callFUT(view, attr='index', renderer=renderer) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -3727,8 +3748,7 @@ class Test__map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, attr='index', renderer=info) + result = self._callFUT(view, attr='index', renderer=renderer) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -3769,8 +3789,7 @@ class Test__map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, attr='index', renderer=info) + result = self._callFUT(view, attr='index', renderer=renderer) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -3802,8 +3821,7 @@ class Test__map_view(unittest.TestCase): def index(self, context, request): return {'a':'1'} view = View() - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, attr='index', renderer=info) + result = self._callFUT(view, attr='index', renderer=renderer) self.failIf(result is view) request = self._makeRequest() self.assertEqual(result(None, request).body, 'Hello!') @@ -3838,8 +3856,7 @@ class Test__map_view(unittest.TestCase): def index(self, request): return {'a':'1'} view = View() - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, attr='index', renderer=info) + result = self._callFUT(view, attr='index', renderer=renderer) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -3851,8 +3868,7 @@ class Test__map_view(unittest.TestCase): renderer = self._registerRenderer() def view(context, request): return {'a':'1'} - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, renderer=info) + result = self._callFUT(view, renderer=renderer) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -3863,8 +3879,7 @@ class Test__map_view(unittest.TestCase): renderer = self._registerRenderer() def view(context, request): return {'a':'1'} - info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, renderer=info) + result = self._callFUT(view, renderer=renderer) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 79e363756..7fc066319 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -317,10 +317,9 @@ class TestViewConfigDecorator(unittest.TestCase): settings = call_venusian(venusian) self.assertEqual(len(settings), 1) renderer = settings[0]['renderer'] - self.assertEqual(renderer, - {'name':'fixtures/minimal.pt', - 'package':pyramid.tests, - }) + self.assertEqual(renderer.name, 'fixtures/minimal.pt') + self.assertEqual(renderer.package, pyramid.tests) + self.assertEqual(renderer.registry.__class__, DummyRegistry) def test_call_with_renderer_dict(self): decorator = self._makeOne(renderer={'a':1}) @@ -494,9 +493,13 @@ class DummyVenusian(object): self.attachments.append((wrapped, callback, category)) return self.info +class DummyRegistry(object): + pass + class DummyConfig(object): def __init__(self): self.settings = [] + self.registry = DummyRegistry() def add_view(self, **kw): self.settings.append(kw) diff --git a/pyramid/view.py b/pyramid/view.py index 3dc110863..776185d8b 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -17,8 +17,10 @@ from zope.interface import providedBy from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier +from pyramid.interfaces import IRendererFactory from pyramid.httpexceptions import HTTPFound +from pyramid.renderers import RendererHelper from pyramid.static import static_view from pyramid.threadlocal import get_current_registry @@ -404,6 +406,12 @@ class view_config(object): settings = self.__dict__.copy() def callback(context, name, ob): + renderer = settings.get('renderer') + if isinstance(renderer, basestring): + renderer = RendererHelper(name=renderer, + package=info.module, + registry=context.config.registry) + settings['renderer'] = renderer context.config.add_view(view=ob, **settings) info = self.venusian.attach(wrapped, callback, category='pyramid') @@ -415,10 +423,6 @@ class view_config(object): if settings['attr'] is None: settings['attr'] = wrapped.__name__ - renderer_name = settings.get('renderer') - if renderer_name is not None and not isinstance(renderer_name, dict): - settings['renderer'] = {'name':renderer_name, - 'package':info.module} settings['_info'] = info.codeinfo return wrapped diff --git a/pyramid/zcml.py b/pyramid/zcml.py index f668e3b4b..a2088e505 100644 --- a/pyramid/zcml.py +++ b/pyramid/zcml.py @@ -161,12 +161,7 @@ def view( cacheable=True, # not used, here for b/w compat < 0.8 ): - if renderer is not None: - package = getattr(_context, 'package', None) - renderer = {'name':renderer, 'package':package} - context = context or for_ - config = Configurator.with_context(_context) config.add_view( permission=permission, context=context, view=view, name=name, -- cgit v1.2.3