summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2010-12-30 02:07:36 -0500
committerChris McDonough <chrism@plope.com>2010-12-30 02:07:36 -0500
commit95c9f6f331bd3294699969ae399045891c0dc6ad (patch)
tree1d7a5f503d7119dfa6edda4365255319b6cc3ca7
parent2953baaebadfb267e1fa98d35605b88ff2274052 (diff)
downloadpyramid-95c9f6f331bd3294699969ae399045891c0dc6ad.tar.gz
pyramid-95c9f6f331bd3294699969ae399045891c0dc6ad.tar.bz2
pyramid-95c9f6f331bd3294699969ae399045891c0dc6ad.zip
- 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).
-rw-r--r--CHANGES.txt32
-rw-r--r--TODO.txt6
-rw-r--r--pyramid/config.py187
-rw-r--r--pyramid/renderers.py12
-rw-r--r--pyramid/tests/test_config.py59
-rw-r--r--pyramid/tests/test_view.py11
-rw-r--r--pyramid/view.py12
-rw-r--r--pyramid/zcml.py5
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,