From e4610566d881f707c01d266a7e336084029c83e4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 20 Sep 2009 22:13:11 +0000 Subject: - The way ``bfg_view`` declarations are scanned for has been modified. This should have no external effects. - An object implementing the ``IRenderer`` interface (and ``ITemplateRenderer`, which is a subclass of ``IRenderer``) must now accept an extra ``system`` argument in its ``__call__`` method implementation. Values computed by the system (as opposed to by the view) are passed by the system in the ``system`` parameter, which will always be a dictionary. Keys in the dictionary include: ``view`` (the view object that returned the value), ``renderer_name`` (the template name or simple name of the renderer), ``context`` (the context object passed to the view), and ``request`` (the request object passed to the view). Previously only ITemplateRenderers received system arguments as elements inside the main ``value`` dictionary. --- CHANGES.txt | 32 ++++++- repoze/bfg/chameleon_text.py | 13 ++- repoze/bfg/chameleon_zpt.py | 13 ++- repoze/bfg/interfaces.py | 11 ++- repoze/bfg/renderers.py | 12 +-- repoze/bfg/testing.py | 2 +- repoze/bfg/tests/grokkedapp/__init__.py | 39 +++++--- repoze/bfg/tests/grokkedapp/another.py | 39 ++++++++ repoze/bfg/tests/test_chameleon_text.py | 23 ++--- repoze/bfg/tests/test_chameleon_zpt.py | 19 ++-- repoze/bfg/tests/test_integration.py | 117 +++++++++++++++++------- repoze/bfg/tests/test_renderers.py | 12 +-- repoze/bfg/tests/test_testing.py | 2 +- repoze/bfg/tests/test_traversal.py | 5 ++ repoze/bfg/tests/test_view.py | 152 +++++++++++--------------------- repoze/bfg/tests/test_zcml.py | 19 ++-- repoze/bfg/traversal.py | 5 +- repoze/bfg/view.py | 70 ++++++++------- repoze/bfg/zcml.py | 77 ++++++++++------ 19 files changed, 394 insertions(+), 268 deletions(-) create mode 100644 repoze/bfg/tests/grokkedapp/another.py diff --git a/CHANGES.txt b/CHANGES.txt index 1de84f7d0..e6b44519c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -31,6 +31,28 @@ Features ``view_renderer`` parameters (bring up to speed with 1.1a3 features). These can also be spelled as ``attr`` and ``renderer``. +Backwards Incompatibilities +--------------------------- + +- An object implementing the ``IRenderer`` interface (and + ``ITemplateRenderer`, which is a subclass of ``IRenderer``) must now + accept an extra ``system`` argument in its ``__call__`` method + implementation. Values computed by the system (as opposed to by the + view) are passed by the system in the ``system`` parameter, which + will always be a dictionary. Keys in the dictionary include: + ``view`` (the view object that returned the value), + ``renderer_name`` (the template name or simple name of the + renderer), ``context`` (the context object passed to the view), and + ``request`` (the request object passed to the view). Previously + only ITemplateRenderers received system arguments as elements inside + the main ``value`` dictionary. + +Internal +-------- + +- The way ``bfg_view`` declarations are scanned for has been modified. + This should have no external effects. + - Speed: do not register an ITraverserFactory in configure.zcml; instead rely on queryAdapter and a manual default to ModelGraphTraverser. @@ -41,6 +63,7 @@ Features - General speed microimprovements for helloworld benchmark: replace try/excepts with statements which use 'in' keyword. + 1.1a3 (2009-09-16) ================== @@ -115,12 +138,15 @@ Backwards Incompatibilities - The ``ITemplateRenderer`` interface has been changed. Previously its ``__call__`` method accepted ``**kw``. It now accepts a single - positional parameter named ``kw``. This is mostly an internal - change, but it was exposed in APIs in one place: if you've used the + positional parameter named ``kw`` (REVISED: it accepts two + positional parameters as of 1.1a4: ``value`` and ``system``). This + is mostly an internal change, but it was exposed in APIs in one + place: if you've used the ``repoze.bfg.testing.registerDummyRenderer`` API in your tests with a custom "renderer" argument with your own renderer implementation, you will need to change that renderer implementation to accept - ``kw`` instead of ``**kw`` in its ``__call__`` method. + ``kw`` instead of ``**kw`` in its ``__call__`` method (REVISED: make + it accept ``value`` and ``system`` positional arguments as of 1.1a4). - The ``ITemplateRendererFactory`` interface has been changed. Previously its ``__call__`` method accepted an ``auto_reload`` diff --git a/repoze/bfg/chameleon_text.py b/repoze/bfg/chameleon_text.py index 7910746ac..9b81c4469 100644 --- a/repoze/bfg/chameleon_text.py +++ b/repoze/bfg/chameleon_text.py @@ -35,8 +35,13 @@ class TextTemplateRenderer(object): def implementation(self): return self.template - def __call__(self, kw): - return self.template(**kw) + def __call__(self, value, system): + try: + system.update(value) + except TypeError: + raise ValueError('renderer was passed non-dictionary as value') + result = self.template(**system) + return result def get_renderer(path): """ Return a callable ``ITemplateRenderer`` object representing a @@ -55,7 +60,7 @@ def render_template(path, **kw): path (may also be absolute) using the kwargs in ``*kw`` as top-level names and return a string.""" renderer = renderer_factory(path) - return renderer(kw) + return renderer(kw, {}) def render_template_to_response(path, **kw): """ Render a ``chameleon`` text template at the package-relative @@ -63,6 +68,6 @@ def render_template_to_response(path, **kw): top-level names and return a Response object with the body as the template result.""" renderer = renderer_factory(path) - result = renderer(kw) + result = renderer(kw, {}) response_factory = queryUtility(IResponseFactory, default=Response) return response_factory(result) diff --git a/repoze/bfg/chameleon_zpt.py b/repoze/bfg/chameleon_zpt.py index 6fcdd9dad..ca9e743e0 100644 --- a/repoze/bfg/chameleon_zpt.py +++ b/repoze/bfg/chameleon_zpt.py @@ -24,8 +24,13 @@ class ZPTTemplateRenderer(object): def implementation(self): return self.template - def __call__(self, kw): - return self.template(**kw) + def __call__(self, value, system): + try: + system.update(value) + except TypeError: + raise ValueError('renderer was passed non-dictionary as value') + result = self.template(**system) + return result def get_renderer(path): """ Return a callable ``ITemplateRenderer`` object representing a @@ -44,7 +49,7 @@ def render_template(path, **kw): path (may also be absolute) using the kwargs in ``*kw`` as top-level names and return a string.""" renderer = renderer_factory(path) - return renderer(kw) + return renderer(kw, {}) def render_template_to_response(path, **kw): """ Render a ``chameleon.zpt`` template at the package-relative @@ -52,7 +57,7 @@ def render_template_to_response(path, **kw): top-level names and return a Response object with the body as the template result. """ renderer = renderer_factory(path) - result = renderer(kw) + result = renderer(kw, {}) response_factory = queryUtility(IResponseFactory, default=Response) return response_factory(result) diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 928ee6c54..8896c883e 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -75,10 +75,17 @@ class ITraverserFactory(Interface): """ Return an object that implements ITraverser """ class IRenderer(Interface): - def __call__(value): + def __call__(value, system): """ Call a the renderer implementation with the result of the view (``value``) passed in and return a result (a string or - unicode object useful as a response body)""" + unicode object useful as a response body). Values computed by + the system are passed by the system in the ``system`` + parameter, which is a dictionary. Keys in the dictionary + include: ``view`` (the view object that returned the value), + ``renderer_name`` (the template name or simple name of the + renderer), ``context`` (the context object passed to the + view), and ``request`` (the request object passed to the + view).""" class IRendererFactory(Interface): def __call__(name): diff --git a/repoze/bfg/renderers.py b/repoze/bfg/renderers.py index 0d14279b0..1e7babcbc 100644 --- a/repoze/bfg/renderers.py +++ b/repoze/bfg/renderers.py @@ -19,12 +19,12 @@ except ImportError: # concrete renderer factory implementations def json_renderer_factory(name): - def _render(value): + def _render(value, system): return json.dumps(value) return _render def string_renderer_factory(name): - def _render(value): + def _render(value, system): if not isinstance(value, basestring): value = str(value) return value @@ -49,11 +49,11 @@ def template_renderer_factory(path, impl, level=3): spec = resource_spec(path, caller_package(level=level).__name__) renderer = queryUtility(ITemplateRenderer, name=spec) if renderer is None: - # service unit tests here by trying the relative path - # string as the utility name directly + # service unit tests by trying the relative path string as + # the utility name directly renderer = queryUtility(ITemplateRenderer, name=path) - pkg, path = spec.split(':', 1) if renderer is None: + pkg, path = spec.split(':', 1) if not pkg_resources.resource_exists(pkg, path): raise ValueError('Missing template resource: %s' % spec) abspath = pkg_resources.resource_filename(pkg, path) @@ -66,7 +66,7 @@ def template_renderer_factory(path, impl, level=3): return renderer -def renderer_from_name(path, level=4): +def renderer_from_name(path): name = os.path.splitext(path)[1] if not name: name = path diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py index d4aafa083..1c7693306 100644 --- a/repoze/bfg/testing.py +++ b/repoze/bfg/testing.py @@ -270,7 +270,7 @@ class DummyTemplateRenderer: return self(kw) return callit - def __call__(self, kw): + def __call__(self, kw, system=None): self._received.update(kw) return self.string_response diff --git a/repoze/bfg/tests/grokkedapp/__init__.py b/repoze/bfg/tests/grokkedapp/__init__.py index 598d3be64..c58ee428a 100644 --- a/repoze/bfg/tests/grokkedapp/__init__.py +++ b/repoze/bfg/tests/grokkedapp/__init__.py @@ -2,25 +2,38 @@ from repoze.bfg.view import bfg_view @bfg_view() def grokked(context, request): - """ """ + return 'grokked' -@bfg_view(request_type='POST') +@bfg_view(request_method='POST') def grokked_post(context, request): - """ """ + return 'grokked_post' -class grokked_klass(object): - """ """ +class oldstyle_grokked_class: def __init__(self, context, request): self.context = context self.request = request def __call__(self): - """ """ + return 'oldstyle_grokked_class' + +oldstyle_grokked_class = bfg_view(name='oldstyle_grokked_class')( + oldstyle_grokked_class) + +class grokked_class(object): + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self): + return 'grokked_class' -# in 2.6+ the below can be spelled as a class decorator: -# -# @bfg_view(for_=INothing, name='grokked_klass') -# class grokked_class(object): -# .... -# -grokked_klass = bfg_view(name='grokked_klass')(grokked_klass) +grokked_class = bfg_view(name='grokked_class')(grokked_class) + +# ungrokkable + +A = 1 +B = {} + +def stuff(): + """ """ + diff --git a/repoze/bfg/tests/grokkedapp/another.py b/repoze/bfg/tests/grokkedapp/another.py new file mode 100644 index 000000000..b97110ab0 --- /dev/null +++ b/repoze/bfg/tests/grokkedapp/another.py @@ -0,0 +1,39 @@ +from repoze.bfg.view import bfg_view + +@bfg_view(name='another') +def grokked(context, request): + return 'another_grokked' + +@bfg_view(request_method='POST', name='another') +def grokked_post(context, request): + return 'another_grokked_post' + +class oldstyle_grokked_class: + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self): + return 'another_oldstyle_grokked_class' + +oldstyle_grokked_class = bfg_view(name='another_oldstyle_grokked_class')( + oldstyle_grokked_class) + +class grokked_class(object): + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self): + return 'another_grokked_class' + +grokked_class = bfg_view(name='another_grokked_class')(grokked_class) + +# ungrokkable + +A = 1 +B = {} + +def stuff(): + """ """ + diff --git a/repoze/bfg/tests/test_chameleon_text.py b/repoze/bfg/tests/test_chameleon_text.py index ddb6f717a..a6384887f 100644 --- a/repoze/bfg/tests/test_chameleon_text.py +++ b/repoze/bfg/tests/test_chameleon_text.py @@ -11,17 +11,10 @@ class Base: os.unlink(self._getTemplatePath('minimal.txt.py')) except: pass - def tearDown(self): cleanUp() - def _zcmlConfigure(self): - import repoze.bfg.includes - import zope.configuration.xmlconfig - zope.configuration.xmlconfig.file('configure.zcml', - package=repoze.bfg.includes) - def _getTemplatePath(self, name): import os here = os.path.abspath(os.path.dirname(__file__)) @@ -48,23 +41,25 @@ class TextTemplateRendererTests(Base, unittest.TestCase): verifyClass(ITemplateRenderer, self._getTargetClass()) def test_call(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.txt') instance = self._makeOne(minimal) - result = instance({}) + result = instance({}, {}) self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') + def test_call_with_nondict_value(self): + minimal = self._getTemplatePath('minimal.txt') + instance = self._makeOne(minimal) + self.assertRaises(ValueError, instance, None, {}) + def test_call_nonminimal(self): - self._zcmlConfigure() nonminimal = self._getTemplatePath('nonminimal.txt') instance = self._makeOne(nonminimal) - result = instance({'name':'Chris'}) + result = instance({'name':'Chris'}, {}) self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello, Chris!\n') def test_implementation(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.txt') instance = self._makeOne(minimal) result = instance.implementation()() @@ -77,7 +72,6 @@ class RenderTemplateTests(Base, unittest.TestCase): return render_template(name, **kw) def test_it(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) self.failUnless(isinstance(result, str)) @@ -89,7 +83,6 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): return render_template_to_response(name, **kw) def test_minimal(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) from webob import Response @@ -165,7 +158,6 @@ class GetTemplateTests(unittest.TestCase, Base): return get_template(name) def test_nonabs_registered(self): - self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility from repoze.bfg.chameleon_text import TextTemplateRenderer @@ -179,7 +171,6 @@ class GetTemplateTests(unittest.TestCase, Base): self.assertEqual(queryUtility(ITemplateRenderer, minimal), utility) def test_nonabs_unregistered(self): - self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility from repoze.bfg.chameleon_text import TextTemplateRenderer diff --git a/repoze/bfg/tests/test_chameleon_zpt.py b/repoze/bfg/tests/test_chameleon_zpt.py index f6d7c0da5..381a1d0bf 100644 --- a/repoze/bfg/tests/test_chameleon_zpt.py +++ b/repoze/bfg/tests/test_chameleon_zpt.py @@ -9,12 +9,6 @@ class Base(object): def tearDown(self): cleanUp() - def _zcmlConfigure(self): - import repoze.bfg.includes - import zope.configuration.xmlconfig - zope.configuration.xmlconfig.file('configure.zcml', - package=repoze.bfg.includes) - def _getTemplatePath(self, name): import os here = os.path.abspath(os.path.dirname(__file__)) @@ -41,16 +35,19 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): verifyClass(ITemplateRenderer, self._getTargetClass()) def test_call(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.pt') instance = self._makeOne(minimal) - result = instance({}) + result = instance({}, {}) self.failUnless(isinstance(result, unicode)) self.assertEqual(result, '
\n
') + def test_call_with_nondict_value(self): + minimal = self._getTemplatePath('minimal.pt') + instance = self._makeOne(minimal) + self.assertRaises(ValueError, instance, None, {}) + def test_implementation(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.pt') instance = self._makeOne(minimal) result = instance.implementation()() @@ -65,7 +62,6 @@ class RenderTemplateTests(Base, unittest.TestCase): return render_template(name, **kw) def test_it(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) self.failUnless(isinstance(result, unicode)) @@ -78,7 +74,6 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): return render_template_to_response(name, **kw) def test_it(self): - self._zcmlConfigure() minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) from webob import Response @@ -149,7 +144,6 @@ class GetTemplateTests(Base, unittest.TestCase): return get_template(name) def test_nonabs_registered(self): - self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility from repoze.bfg.chameleon_zpt import ZPTTemplateRenderer @@ -163,7 +157,6 @@ class GetTemplateTests(Base, unittest.TestCase): self.assertEqual(queryUtility(ITemplateRenderer, minimal), utility) def test_nonabs_unregistered(self): - self._zcmlConfigure() from zope.component import getGlobalSiteManager from zope.component import queryUtility from repoze.bfg.chameleon_zpt import ZPTTemplateRenderer diff --git a/repoze/bfg/tests/test_integration.py b/repoze/bfg/tests/test_integration.py index 681dcf043..bcd29d294 100644 --- a/repoze/bfg/tests/test_integration.py +++ b/repoze/bfg/tests/test_integration.py @@ -39,7 +39,7 @@ class WGSIAppPlusBFGViewTests(unittest.TestCase): from repoze.bfg.interfaces import IRequest from repoze.bfg.interfaces import IView from repoze.bfg.zcml import scan - context = DummyContext() + context = DummyZCMLContext() from repoze.bfg.tests import test_integration scan(context, test_integration) actions = context.actions @@ -99,39 +99,96 @@ class TestGrokkedApp(unittest.TestCase): cleanUp() def test_it(self): - import inspect + from repoze.bfg.view import render_view_to_response + from zope.interface import directlyProvides + from repoze.bfg.zcml import zcml_configure from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IRequest import repoze.bfg.tests.grokkedapp as package - from zope.configuration import config - from zope.configuration import xmlconfig - context = config.ConfigurationMachine() - xmlconfig.registerCommonDirectives(context) - context.package = package - xmlconfig.include(context, 'configure.zcml', package) - actions = context.actions + actions = zcml_configure('configure.zcml', package) - postview = actions[-1] - self.assertEqual(postview[0][1], None) - self.assertEqual(postview[0][2], '') - self.assertEqual(postview[0][3], IRequest) - self.assertEqual(postview[0][4], IView) + action = actions[-1] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], 'another_oldstyle_grokked_class') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) + + action = actions[-2] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], 'another') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) + + action = actions[-3] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], 'another_grokked_class') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) + + action = actions[-4] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], 'another') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) + + action = actions[-5] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], 'oldstyle_grokked_class') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) - klassview = actions[-2] - self.assertEqual(klassview[0][1], None) - self.assertEqual(klassview[0][2], 'grokked_klass') - self.assertEqual(klassview[0][3], IRequest) - self.assertEqual(klassview[0][4], IView) - self.failUnless(inspect.isfunction(package.grokked_klass)) - self.assertEqual(package.grokked_klass(None, None), None) - - funcview = actions[-3] - self.assertEqual(funcview[0][1], None) - self.assertEqual(funcview[0][2], '') - self.assertEqual(funcview[0][3], IRequest) - self.assertEqual(funcview[0][4], IView) - -class DummyContext: + action = actions[-6] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], '') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) + + action = actions[-7] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], 'grokked_class') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) + + action = actions[-8] + self.assertEqual(action[0][1], None) + self.assertEqual(action[0][2], '') + self.assertEqual(action[0][3], IRequest) + self.assertEqual(action[0][4], IView) + + ctx = DummyContext() + req = DummyRequest() + directlyProvides(req, IRequest) + + req.method = 'GET' + result = render_view_to_response(ctx, req, '') + self.assertEqual(result, 'grokked') + + req.method = 'POST' + result = render_view_to_response(ctx, req, '') + self.assertEqual(result, 'grokked_post') + + result= render_view_to_response(ctx, req, 'grokked_class') + self.assertEqual(result, 'grokked_class') + + result= render_view_to_response(ctx, req, 'oldstyle_grokked_class') + self.assertEqual(result, 'oldstyle_grokked_class') + + req.method = 'GET' + result = render_view_to_response(ctx, req, 'another') + self.assertEqual(result, 'another_grokked') + + req.method = 'POST' + result = render_view_to_response(ctx, req, 'another') + self.assertEqual(result, 'another_grokked_post') + + result= render_view_to_response(ctx, req, 'another_grokked_class') + self.assertEqual(result, 'another_grokked_class') + + result= render_view_to_response(ctx, req, + 'another_oldstyle_grokked_class') + self.assertEqual(result, 'another_oldstyle_grokked_class') + +class DummyContext(object): pass class DummyRequest: @@ -141,7 +198,7 @@ class DummyRequest: def get_response(self, application): return application(None, None) -class DummyContext: +class DummyZCMLContext: def __init__(self): self.actions = [] self.info = None diff --git a/repoze/bfg/tests/test_renderers.py b/repoze/bfg/tests/test_renderers.py index d1acc0285..a330d19da 100644 --- a/repoze/bfg/tests/test_renderers.py +++ b/repoze/bfg/tests/test_renderers.py @@ -149,9 +149,9 @@ class TestRendererFromName(unittest.TestCase): def tearDown(self): cleanUp() - def _callFUT(self, path, level=4): + def _callFUT(self, path): from repoze.bfg.renderers import renderer_from_name - return renderer_from_name(path, level) + return renderer_from_name(path) def test_it(self): from repoze.bfg.interfaces import ITemplateRendererFactory @@ -176,7 +176,7 @@ class Test_json_renderer_factory(unittest.TestCase): def test_it(self): renderer = self._callFUT(None) - result = renderer({'a':1}) + result = renderer({'a':1}, {}) self.assertEqual(result, '{"a": 1}') class Test_string_renderer_factory(unittest.TestCase): @@ -187,19 +187,19 @@ class Test_string_renderer_factory(unittest.TestCase): def test_it_unicode(self): renderer = self._callFUT(None) value = unicode('La Pe\xc3\xb1a', 'utf-8') - result = renderer(value) + result = renderer(value, {}) self.assertEqual(result, value) def test_it_str(self): renderer = self._callFUT(None) value = 'La Pe\xc3\xb1a' - result = renderer(value) + result = renderer(value, {}) self.assertEqual(result, value) def test_it_other(self): renderer = self._callFUT(None) value = None - result = renderer(value) + result = renderer(value, {}) self.assertEqual(result, 'None') class DummyFactory: diff --git a/repoze/bfg/tests/test_testing.py b/repoze/bfg/tests/test_testing.py index 6c6b55337..c302bd9f5 100644 --- a/repoze/bfg/tests/test_testing.py +++ b/repoze/bfg/tests/test_testing.py @@ -65,7 +65,7 @@ class TestTestingFunctions(unittest.TestCase): def test_registerTemplateRenderer_explicitrenderer(self): from repoze.bfg import testing - def renderer(kw): + def renderer(kw, system): raise ValueError renderer = testing.registerTemplateRenderer('templates/foo', renderer) from repoze.bfg.chameleon_zpt import render_template_to_response diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py index 0fb0becfd..3f347dc22 100644 --- a/repoze/bfg/tests/test_traversal.py +++ b/repoze/bfg/tests/test_traversal.py @@ -971,6 +971,11 @@ class UnderTraverseTests(unittest.TestCase): from zope.interface import Interface gsm.registerAdapter(traverser, (Interface,), ITraverserFactory) + def test_default_traverser_factory(self): + context = DummyContext() + result = self._callFUT(context, {}) + self.assertEqual(result['view_name'], '') + def test_isdict(self): traverser = make_traverser({}) self._registerTraverserFactory(traverser) diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py index 959ad939b..42d14f83a 100644 --- a/repoze/bfg/tests/test_view.py +++ b/repoze/bfg/tests/test_view.py @@ -382,52 +382,26 @@ class TestBFGViewDecorator(unittest.TestCase): self.assertEqual(wrapped.__request_type__, None) def test_call_oldstyle_class(self): - import inspect decorator = self._makeOne() class foo: """ docstring """ - def __init__(self, context, request): - self.context = context - self.request = request - def __call__(self): - return self wrapped = decorator(foo) - self.failIf(wrapped is foo) - self.failUnless(inspect.isfunction(wrapped)) + self.failUnless(wrapped is foo) self.assertEqual(wrapped.__is_bfg_view__, True) self.assertEqual(wrapped.__permission__, None) self.assertEqual(wrapped.__for__, None) self.assertEqual(wrapped.__request_type__, None) - self.assertEqual(wrapped.__module__, foo.__module__) - self.assertEqual(wrapped.__name__, foo.__name__) - self.assertEqual(wrapped.__doc__, foo.__doc__) - result = wrapped(None, None) - self.assertEqual(result.context, None) - self.assertEqual(result.request, None) def test_call_newstyle_class(self): - import inspect decorator = self._makeOne() class foo(object): """ docstring """ - def __init__(self, context, request): - self.context = context - self.request = request - def __call__(self): - return self wrapped = decorator(foo) - self.failIf(wrapped is foo) - self.failUnless(inspect.isfunction(wrapped)) + self.failUnless(wrapped is foo) self.assertEqual(wrapped.__is_bfg_view__, True) self.assertEqual(wrapped.__permission__, None) self.assertEqual(wrapped.__for__, None) self.assertEqual(wrapped.__request_type__, None) - self.assertEqual(wrapped.__module__, foo.__module__) - self.assertEqual(wrapped.__name__, foo.__name__) - self.assertEqual(wrapped.__doc__, foo.__doc__) - result = wrapped(None, None) - self.assertEqual(result.context, None) - self.assertEqual(result.request, None) class TestDefaultForbiddenView(unittest.TestCase): def _callFUT(self, context, request): @@ -638,17 +612,12 @@ class Test_map_view(unittest.TestCase): from zope.component import getSiteManager class Renderer: implements(ITemplateRenderer) + def __init__(self, path): + pass def __call__(self, *arg): return 'Hello!' - - class RendererFactory: - def __call__(self, path): - self.path = path - return Renderer() - - factory = RendererFactory() sm = getSiteManager() - sm.registerUtility(factory, IRendererFactory, name=name) + sm.registerUtility(Renderer, IRendererFactory, name=name) def test_view_as_function_context_and_request(self): def view(context, request): @@ -669,7 +638,7 @@ class Test_map_view(unittest.TestCase): def view(context, request): """ """ result = self._callFUT(view, attr='__name__', - renderer='fixtures/minimal.txt') + renderer_name='fixtures/minimal.txt') self.failIf(result is view) self.assertRaises(TypeError, result, None, None) @@ -727,8 +696,9 @@ class Test_map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - result = self._callFUT(view, attr='index', - renderer='repoze.bfg.tests:fixtures/minimal.txt') + result = self._callFUT( + view, attr='index', + renderer_name='repoze.bfg.tests:fixtures/minimal.txt') self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -769,8 +739,9 @@ class Test_map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - result = self._callFUT(view, attr='index', - renderer='repoze.bfg.tests:fixtures/minimal.txt') + result = self._callFUT( + view, attr='index', + renderer_name='repoze.bfg.tests:fixtures/minimal.txt') self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -812,8 +783,9 @@ class Test_map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - result = self._callFUT(view, attr='index', - renderer='repoze.bfg.tests:fixtures/minimal.txt') + result = self._callFUT( + view, attr='index', + renderer_name='repoze.bfg.tests:fixtures/minimal.txt') self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -854,8 +826,9 @@ class Test_map_view(unittest.TestCase): pass def index(self): return {'a':'1'} - result = self._callFUT(view, attr='index', - renderer='repoze.bfg.tests:fixtures/minimal.txt') + result = self._callFUT( + view, attr='index', + renderer_name='repoze.bfg.tests:fixtures/minimal.txt') self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -887,8 +860,9 @@ class Test_map_view(unittest.TestCase): def index(self, context, request): return {'a':'1'} view = View() - result = self._callFUT(view, attr='index', - renderer='repoze.bfg.tests:fixtures/minimal.txt') + result = self._callFUT( + view, attr='index', + renderer_name='repoze.bfg.tests:fixtures/minimal.txt') self.failIf(result is view) request = DummyRequest() self.assertEqual(result(None, request).body, 'Hello!') @@ -923,8 +897,9 @@ class Test_map_view(unittest.TestCase): def index(self, request): return {'a':'1'} view = View() - result = self._callFUT(view, attr='index', - renderer='repoze.bfg.tests:fixtures/minimal.txt') + result = self._callFUT( + view, attr='index', + renderer_name='repoze.bfg.tests:fixtures/minimal.txt') self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -936,8 +911,9 @@ class Test_map_view(unittest.TestCase): self._registerRenderer() def view(context, request): return {'a':'1'} - result = self._callFUT(view, - renderer='repoze.bfg.tests:fixtures/minimal.txt') + result = self._callFUT( + view, + renderer_name='repoze.bfg.tests:fixtures/minimal.txt') self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) @@ -1164,72 +1140,47 @@ class Test_rendered_response(unittest.TestCase): def tearDown(self): cleanUp() - def _callFUT(self, renderer_name, response, view=None, - context=None, request=None): + def _callFUT(self, renderer, response, view=None, + context=None, request=None, renderer_name=None): from repoze.bfg.view import rendered_response if request is None: request = DummyRequest() - return rendered_response(renderer_name, response, view, context, - request) - - def _registerRenderer(self): - from repoze.bfg.interfaces import IRendererFactory - from repoze.bfg.interfaces import ITemplateRenderer - from zope.interface import implements - from zope.component import getSiteManager - class Renderer: - implements(ITemplateRenderer) - def __call__(self, *arg): - return 'Hello!' - - class RendererFactory: - def __call__(self, path): - self.path = path - return Renderer() + return rendered_response(renderer, response, view, + context, request, renderer_name) - factory = RendererFactory() - sm = getSiteManager() - sm.registerUtility(factory, IRendererFactory, name='.txt') + def _makeRenderer(self): + def renderer(*arg): + return 'Hello!' + return renderer def test_is_response(self): - self._registerRenderer() + renderer = self._makeRenderer() response = DummyResponse() - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response) + result = self._callFUT(renderer, response) self.assertEqual(result, response) - def test_is_not_valid_dict(self): - self._registerRenderer() - response = None - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response) - self.assertEqual(result, response) - - def test_valid_dict(self): - self._registerRenderer() + def test_calls_renderer(self): + renderer = self._makeRenderer() response = {'a':'1'} - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response) + result = self._callFUT(renderer, response) self.assertEqual(result.body, 'Hello!') def test_with_content_type(self): - self._registerRenderer() + renderer = self._makeRenderer() response = {'a':'1'} request = DummyRequest() attrs = {'response_content_type':'text/nonsense'} request.environ['webob.adhoc_attrs'] = attrs - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request) + result = self._callFUT(renderer, response, request=request) self.assertEqual(result.content_type, 'text/nonsense') def test_with_headerlist(self): - self._registerRenderer() + renderer = self._makeRenderer() response = {'a':'1'} request = DummyRequest() attrs = {'response_headerlist':[('a', '1'), ('b', '2')]} request.environ['webob.adhoc_attrs'] = attrs - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request) + result = self._callFUT(renderer, response, request=request) self.assertEqual(result.headerlist, [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '6'), @@ -1237,33 +1188,30 @@ class Test_rendered_response(unittest.TestCase): ('b', '2')]) def test_with_status(self): - self._registerRenderer() + renderer = self._makeRenderer() response = {'a':'1'} request = DummyRequest() attrs = {'response_status':'406 You Lose'} request.environ['webob.adhoc_attrs'] = attrs - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request) + result = self._callFUT(renderer, response, request=request) self.assertEqual(result.status, '406 You Lose') def test_with_charset(self): - self._registerRenderer() + renderer = self._makeRenderer() response = {'a':'1'} request = DummyRequest() attrs = {'response_charset':'UTF-16'} request.environ['webob.adhoc_attrs'] = attrs - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request) + result = self._callFUT(renderer, response, request=request) self.assertEqual(result.charset, 'UTF-16') def test_with_cache_for(self): - self._registerRenderer() + renderer = self._makeRenderer() response = {'a':'1'} request = DummyRequest() attrs = {'response_cache_for':100} request.environ['webob.adhoc_attrs'] = attrs - result = self._callFUT( - 'repoze.bfg.tests:fixtures/minimal.txt', response, request=request) + result = self._callFUT(renderer, response, request=request) self.assertEqual(result.cache_control.max_age, 100) class TestDeriveView(unittest.TestCase): diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 0dcda485f..79354417f 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -1845,6 +1845,8 @@ class TestBFGViewFunctionGrokker(unittest.TestCase): obj.__attr__ = None obj.__template__ = None obj.__wrapper_viewname__ = None + obj.__renderer__ = None + obj.__attr__ = None context = DummyContext() result = grokker.grok('name', obj, context=context) self.assertEqual(result, True) @@ -1879,13 +1881,15 @@ class TestZCMLScanDirective(unittest.TestCase): return scan(context, package, martian) def test_it(self): + from repoze.bfg.zcml import SimpleMultiGrokker + from repoze.bfg.zcml import exclude martian = DummyMartianModule() module_grokker = DummyModuleGrokker() dummy_module = DummyModule() - from repoze.bfg.zcml import exclude self._callFUT(None, dummy_module, martian) self.assertEqual(martian.name, 'dummy') - self.assertEqual(len(martian.module_grokker.registered), 1) + multi_grokker = martian.module_grokker.multi_grokker + self.assertEqual(multi_grokker.__class__, SimpleMultiGrokker) self.assertEqual(martian.context, None) self.assertEqual(martian.exclude_filter, exclude) @@ -1910,11 +1914,8 @@ class DummyModule: __file__ = '' class DummyModuleGrokker: - def __init__(self): - self.registered = [] - - def register(self, other): - self.registered.append(other) + def __init__(self, grokker=None): + self.multi_grokker = grokker class DummyMartianModule: def grok_dotted_name(self, name, grokker, context, exclude_filter=None): @@ -1923,8 +1924,8 @@ class DummyMartianModule: self.exclude_filter = exclude_filter return True - def ModuleGrokker(self): - self.module_grokker = DummyModuleGrokker() + def ModuleGrokker(self, grokker=None): + self.module_grokker = DummyModuleGrokker(grokker) return self.module_grokker class DummyContext: diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index 16c1cc764..cec87ebc8 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -2,6 +2,7 @@ import re import urllib from zope.component import queryMultiAdapter +from zope.component import queryAdapter from zope.interface import classProvides from zope.interface import implements @@ -272,7 +273,9 @@ def traverse(model, path): def _traverse(model, environ, traverser=None): if traverser is None: - traverser = ITraverserFactory(model) + traverser = queryAdapter(model, ITraverserFactory) + if traverser is None: + traverser = ModelGraphTraverser(model) result = traverser(environ) diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 1e25755b4..c710829ca 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -357,7 +357,7 @@ class bfg_view(object): self.wrapper_viewname = wrapper def __call__(self, wrapped): - _bfg_view = map_view(wrapped, self.attr, self.renderer) + _bfg_view = wrapped #map_view(wrapped, self.attr, self.renderer) _bfg_view.__is_bfg_view__ = True _bfg_view.__permission__ = self.permission _bfg_view.__for__ = self.for_ @@ -368,6 +368,8 @@ class bfg_view(object): _bfg_view.__request_param__ = self.request_param _bfg_view.__containment__ = self.containment _bfg_view.__wrapper_viewname__ = self.wrapper_viewname + _bfg_view.__attr__ = self.attr + _bfg_view.__renderer__ = self.renderer return _bfg_view def default_view(context, request, status): @@ -438,20 +440,12 @@ class MultiView(object): continue raise NotFound(self.name) -def rendered_response(renderer_name, response, view, context, request): - if is_response(response): +def rendered_response(renderer, response, view, context,request, renderer_name): + if ( hasattr(response, 'app_iter') and hasattr(response, 'headerlist') and + hasattr(response, 'status') ): return response - renderer = renderer_from_name(renderer_name) - if ITemplateRenderer.providedBy(renderer): - kw = {'view':view, 'template_name':renderer_name, 'context':context, - 'request':request} - try: - kw.update(response) - except TypeError: - return response - result = renderer(kw) - else: - result = renderer(response) + result = renderer(response, {'view':view, 'renderer_name':renderer_name, + 'context':context, 'request':request}) response_factory = queryUtility(IResponseFactory, default=Response) response = response_factory(result) attrs = request.environ.get('webob.adhoc_attrs', {}) @@ -473,12 +467,18 @@ def rendered_response(renderer_name, response, view, context, request): response.cache_expires = cache_for return response -def map_view(view, attr=None, renderer=None): +def map_view(view, attr=None, renderer_name=None): wrapped_view = view - if renderer is None: - if queryUtility(IRendererFactory) is not None: # default renderer - renderer = '' + renderer = None + + if renderer_name is None: + factory = queryUtility(IRendererFactory) # global default renderer + if factory is not None: + renderer_name = '' + renderer = factory(renderer_name) + else: + renderer = renderer_from_name(renderer_name) if inspect.isclass(view): # If the object we've located is a class, turn it into a @@ -497,8 +497,10 @@ def map_view(view, attr=None, renderer=None): else: response = getattr(inst, attr)() if renderer is not None: - response = rendered_response(renderer, response, inst, - context, request) + response = rendered_response(renderer, + response, inst, + context, request, + renderer_name) return response wrapped_view = _bfg_class_requestonly_view else: @@ -510,8 +512,10 @@ def map_view(view, attr=None, renderer=None): else: response = getattr(inst, attr)() if renderer is not None: - response = rendered_response(renderer, response, inst, - context, request) + response = rendered_response(renderer, + response, inst, + context, request, + renderer_name) return response wrapped_view = _bfg_class_view @@ -525,8 +529,10 @@ def map_view(view, attr=None, renderer=None): response = getattr(view, attr)(request) if renderer is not None: - response = rendered_response(renderer, response, view, - context, request) + response = rendered_response(renderer, + response, view, + context, request, + renderer_name) return response wrapped_view = _bfg_requestonly_view @@ -534,16 +540,20 @@ def map_view(view, attr=None, renderer=None): def _bfg_attr_view(context, request): response = getattr(view, attr)(context, request) if renderer is not None: - response = rendered_response(renderer, response, view, - context, request) + response = rendered_response(renderer, + response, view, + context, request, + renderer_name) return response wrapped_view = _bfg_attr_view elif renderer is not None: def _rendered_view(context, request): response = view(context, request) - response = rendered_response(renderer, response, view, - context, request) + response = rendered_response(renderer, + response, view, + context, request, + renderer_name) return response wrapped_view = _rendered_view @@ -617,8 +627,8 @@ def decorate_view(wrapped_view, original_view): return False def derive_view(original_view, permission=None, predicates=(), attr=None, - renderer=None, wrapper_viewname=None, viewname=None): - mapped_view = map_view(original_view, attr, renderer) + renderer_name=None, wrapper_viewname=None, viewname=None): + mapped_view = map_view(original_view, attr, renderer_name) owrapped_view = owrap_view(mapped_view, viewname, wrapper_viewname) secured_view = secure_view(owrapped_view, permission) debug_view = authdebug_view(secured_view, permission) diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index a3ddd8527..d5e7ffd67 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -1,3 +1,4 @@ +import inspect import sys import types @@ -20,6 +21,7 @@ from zope.schema import Int from zope.schema import TextLine import martian +import martian.core from repoze.bfg.interfaces import IAuthenticationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy @@ -557,20 +559,6 @@ def renderer(_context, factory, name=''): sm.registerUtility(factory, IRendererFactory, name=name) _context.action(discriminator=(IRendererFactory, name)) -class IScanDirective(Interface): - package = GlobalObject( - title=u"The package we'd like to scan.", - required=True, - ) - -def scan(_context, package, martian=martian): - # martian overrideable only for unit tests - module_grokker = martian.ModuleGrokker() - module_grokker.register(BFGViewFunctionGrokker()) - martian.grok_dotted_name(package.__name__, grokker=module_grokker, - context=_context, exclude_filter=exclude) - - class IStaticDirective(Interface): name = TextLine( title=u"The URL prefix of the static view", @@ -598,21 +586,25 @@ def static(_context, name, path, cache_max_age=3600): route(_context, name, "%s*subpath" % name, view=view, view_for=StaticRootFactory, factory=StaticRootFactory(path)) -################# utility stuff #################### - -def zcml_configure(name, package): - context = zope.configuration.config.ConfigurationMachine() - xmlconfig.registerCommonDirectives(context) - context.package = package - xmlconfig.include(context, name, package) - context.execute_actions(clear=False) - return context.actions +class IScanDirective(Interface): + package = GlobalObject( + title=u"The package we'd like to scan.", + required=True, + ) -file_configure = zcml_configure # backwards compat (>0.8.1) +def scan(_context, package, martian=martian): + # martian overrideable only for unit tests + multi_grokker = SimpleMultiGrokker() + multi_grokker.register(BFGViewFunctionGrokker()) + multi_grokker.register(BFGViewNewStyleClassGrokker()) + multi_grokker.register(BFGViewOldStyleClassGrokker()) + module_grokker = martian.ModuleGrokker(grokker=multi_grokker) + martian.grok_dotted_name(package.__name__, grokker=module_grokker, + context=_context, exclude_filter=exclude) -class BFGViewFunctionGrokker(martian.InstanceGrokker): - martian.component(types.FunctionType) +################# utility stuff #################### +class BFGViewGrokker(object): def grok(self, name, obj, **kw): if hasattr(obj, '__is_bfg_view__'): permission = obj.__permission__ @@ -624,21 +616,52 @@ class BFGViewFunctionGrokker(martian.InstanceGrokker): request_param = obj.__request_param__ containment = obj.__containment__ wrapper = obj.__wrapper_viewname__ + attr = obj.__attr__ + renderer = obj.__renderer__ context = kw['context'] view(context, permission=permission, for_=for_, view=obj, name=name, request_type=request_type, route_name=route_name, request_method=request_method, request_param=request_param, containment=containment, - wrapper=wrapper) + attr=attr, renderer=renderer, wrapper=wrapper) return True return False +class BFGViewFunctionGrokker(BFGViewGrokker, martian.InstanceGrokker): + martian.component(types.FunctionType) + +class BFGViewOldStyleClassGrokker(BFGViewGrokker, martian.InstanceGrokker): + martian.component(types.ClassType) + +class BFGViewNewStyleClassGrokker(BFGViewGrokker, martian.InstanceGrokker): + martian.component(type) + def exclude(name): if name.startswith('.'): return True return False +class SimpleMultiGrokker(martian.core.MultiInstanceOrClassGrokkerBase): + # this is an amalgam of martian.core.MultiInstanceGrokker and + # martian.core.MultiClassGrokker. + def get_bases(self, obj): + if type(obj) is types.ModuleType: + return [] + if hasattr(obj, '__class__'): + return inspect.getmro(obj.__class__) + return [types.ClassType] + class Uncacheable(object): """ Include in discriminators of actions which are not cacheable; this class only exists for backwards compatibility (<0.8.1)""" +def zcml_configure(name, package): + context = zope.configuration.config.ConfigurationMachine() + xmlconfig.registerCommonDirectives(context) + context.package = package + xmlconfig.include(context, name, package) + context.execute_actions(clear=False) + return context.actions + +file_configure = zcml_configure # backwards compat (>0.8.1) + -- cgit v1.2.3