diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-01-16 18:58:16 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-01-16 18:58:16 +0000 |
| commit | 5a7f9a4d57424f14a1e072cc06b6bf7a191a7d08 (patch) | |
| tree | b44448198ddf8031b3e09b83dd731f2ae1d6623a | |
| parent | 4856cc54bcd5feb97db49f1cca923afb01c0bf02 (diff) | |
| download | pyramid-5a7f9a4d57424f14a1e072cc06b6bf7a191a7d08.tar.gz pyramid-5a7f9a4d57424f14a1e072cc06b6bf7a191a7d08.tar.bz2 pyramid-5a7f9a4d57424f14a1e072cc06b6bf7a191a7d08.zip | |
Features
--------
- The functionality of ``repoze.bfg.convention`` has been merged into
the core. Applications which make use of ``repoze.bfg.convention``
will continue to work indefinitely, but it is recommended that apps
stop depending upon it. To do so, substitute imports of
``repoze.bfg.convention.bfg_view`` with imports of
``repoze.bfg.view.bfg_view``, and change the stanza in ZCML from
``<convention package=".">`` to ``<grok package=".">``. As a result
of the merge, bfg has grown a new dependency: ``martian``.
- View functions which use the pushpage decorator are now pickleable
(meaning their use won't prevent a ``configure.zcml.cache`` file
from being written to disk).
Implementation Changes
----------------------
- The ``wsgiapp`` decorator now uses ``webob.Request.get_response`` to
do its work rather than relying on howgrown WSGI code.
| -rw-r--r-- | CHANGES.txt | 19 | ||||
| -rw-r--r-- | repoze/bfg/includes/meta.zcml | 6 | ||||
| -rw-r--r-- | repoze/bfg/push.py | 16 | ||||
| -rw-r--r-- | repoze/bfg/tests/grokkedapp/__init__.py | 11 | ||||
| -rw-r--r-- | repoze/bfg/tests/grokkedapp/configure.zcml | 6 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_integration.py | 152 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_push.py | 2 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_view.py | 48 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_wsgi.py | 55 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 142 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 89 | ||||
| -rw-r--r-- | repoze/bfg/wsgi.py | 24 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 58 | ||||
| -rw-r--r-- | setup.py | 1 |
14 files changed, 521 insertions, 108 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 2f5b14e84..d7aa363f8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,12 +4,31 @@ After 0.6.2 Features -------- +- The functionality of ``repoze.bfg.convention`` has been merged into + the core. Applications which make use of ``repoze.bfg.convention`` + will continue to work indefinitely, but it is recommended that apps + stop depending upon it. To do so, substitute imports of + ``repoze.bfg.convention.bfg_view`` with imports of + ``repoze.bfg.view.bfg_view``, and change the stanza in ZCML from + ``<convention package=".">`` to ``<grok package=".">``. As a result + of the merge, bfg has grown a new dependency: ``martian``. + +- View functions which use the pushpage decorator are now pickleable + (meaning their use won't prevent a ``configure.zcml.cache`` file + from being written to disk). + - Instead of invariably using ``webob.Request`` as the "request factory" (e.g. in the ``Router`` class) and ``webob.Response`` and the "response factory" (e.g. in ``render_template_to_response``), allow both to be overridden via a ZCML utility hook. See the "Using ZCML Hooks" chapter of the documentation for more information. +Implementation Changes +---------------------- + +- The ``wsgiapp`` decorator now uses ``webob.Request.get_response`` to + do its work rather than relying on howgrown WSGI code. + 0.6.2 (2009-01-13) ================== diff --git a/repoze/bfg/includes/meta.zcml b/repoze/bfg/includes/meta.zcml index 591a7be8a..7ca34a5fb 100644 --- a/repoze/bfg/includes/meta.zcml +++ b/repoze/bfg/includes/meta.zcml @@ -10,6 +10,12 @@ handler="repoze.bfg.zcml.view" /> + <meta:directive + name="grok" + schema="repoze.bfg.zcml.IGrokDirective" + handler="repoze.bfg.zcml.grok" + /> + </meta:directives> </configure> diff --git a/repoze/bfg/push.py b/repoze/bfg/push.py index abc81adb5..928b25280 100644 --- a/repoze/bfg/push.py +++ b/repoze/bfg/push.py @@ -1,8 +1,17 @@ import os.path + from repoze.bfg.chameleon_zpt import render_template_to_response +try: + from functools import wraps +except ImportError: + # < 2.5 + from repoze.bfg.functional import wraps + class pushpage(object): - """ Decorator for functions which return Chameleon template namespaces. + """ Decorator for a function which returns a response object after + running the namespace the wrapped function returns through a + Chameleon ZPT template. E.g.:: @@ -27,7 +36,4 @@ class pushpage(object): def _curried(context, request): kw = wrapped(context, request) return render_template_to_response(path, **kw) - _curried.__name__ = wrapped.__name__ - _curried.__grok_module__ = wrapped.__module__ # r.bfg.convention support - - return _curried + return wraps(wrapped)(_curried) # pickleability and grokkability diff --git a/repoze/bfg/tests/grokkedapp/__init__.py b/repoze/bfg/tests/grokkedapp/__init__.py new file mode 100644 index 000000000..9d91eb80f --- /dev/null +++ b/repoze/bfg/tests/grokkedapp/__init__.py @@ -0,0 +1,11 @@ +from repoze.bfg.view import bfg_view +from zope.interface import Interface + +class INothing(Interface): + pass + +@bfg_view(for_=INothing) +def grokked(context, request): + """ """ + + diff --git a/repoze/bfg/tests/grokkedapp/configure.zcml b/repoze/bfg/tests/grokkedapp/configure.zcml new file mode 100644 index 000000000..be7226afd --- /dev/null +++ b/repoze/bfg/tests/grokkedapp/configure.zcml @@ -0,0 +1,6 @@ +<configure xmlns="http://namespaces.repoze.org/bfg"> + + <include package="repoze.bfg.includes" /> + <grok package="."/> + +</configure> diff --git a/repoze/bfg/tests/test_integration.py b/repoze/bfg/tests/test_integration.py new file mode 100644 index 000000000..76004af7b --- /dev/null +++ b/repoze/bfg/tests/test_integration.py @@ -0,0 +1,152 @@ +import unittest + +from repoze.bfg.wsgi import wsgiapp +from repoze.bfg.view import bfg_view +from repoze.bfg.push import pushpage + +from zope.interface import Interface + +from zope.testing.cleanup import cleanUp + +class INothing(Interface): + pass + +@bfg_view(for_=INothing) +@wsgiapp +def wsgiapptest(environ, start_response): + """ """ + return '123' + +class WGSIAppPlusBFGViewTests(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def test_it(self): + import types + self.assertEqual(wsgiapptest.__is_bfg_view__, True) + self.failUnless(type(wsgiapptest) is types.FunctionType) + context = DummyContext() + request = DummyRequest() + result = wsgiapptest(context, request) + self.assertEqual(result, '123') + + def test_grokkage(self): + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.zcml import grok + context = DummyContext() + from repoze.bfg.tests import test_integration + grok(context, test_integration) + actions = context.actions + self.assertEqual(len(actions), 2) + action = actions[1] + self.assertEqual(action['args'], + ('registerAdapter', + wsgiapptest, (INothing, IRequest), IView, '', None)) + +@bfg_view(for_=INothing) +@pushpage('fake.pt') +def pushtest(context, request): + """ """ + return {'a':1} + +class PushPagePlusBFGViewTests(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def test_it(self): + import types + import os + from repoze.bfg.testing import registerDummyRenderer + path = os.path.join(os.path.dirname(__file__), 'fake.pt') + renderer = registerDummyRenderer(path) + self.assertEqual(pushtest.__is_bfg_view__, True) + self.failUnless(type(pushtest) is types.FunctionType) + context = DummyContext() + request = DummyRequest() + result = pushtest(context, request) + self.assertEqual(result.status, '200 OK') + + def test_grokkage(self): + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.zcml import grok + context = DummyContext() + from repoze.bfg.tests import test_integration + grok(context, test_integration) + actions = context.actions + self.assertEqual(len(actions), 2) + action = actions[0] + self.assertEqual(action['args'], + ('registerAdapter', + pushtest, (INothing, IRequest), IView, '', None)) + +class TestFixtureApp(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def test_registry_actions_can_be_pickled_and_unpickled(self): + import repoze.bfg.tests.fixtureapp 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) + context.execute_actions(clear=False) + actions = context.actions + import cPickle + dumped = cPickle.dumps(actions, -1) + new = cPickle.loads(dumped) + self.assertEqual(len(actions), len(new)) + +class TestGrokkedApp(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def test_registry_actions_cannot_be_pickled_and_unpickled(self): + 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 + import cPickle + self.assertRaises(cPickle.PicklingError, cPickle.dumps, actions, -1) + self.assertEqual(len(actions), 5) + +class DummyContext: + pass + +class DummyRequest: + subpath = ('__init__.py',) + environ = {'REQUEST_METHOD':'GET', 'wsgi.version':(1,0)} + def get_response(self, application): + return application(None, None) + +class DummyContext: + def __init__(self): + self.actions = [] + self.info = None + + def action(self, discriminator, callable, args): + self.actions.append( + {'discriminator':discriminator, + 'callable':callable, + 'args':args} + ) diff --git a/repoze/bfg/tests/test_push.py b/repoze/bfg/tests/test_push.py index 4220d9a5a..3cfe5a4c4 100644 --- a/repoze/bfg/tests/test_push.py +++ b/repoze/bfg/tests/test_push.py @@ -26,7 +26,7 @@ class Test_pushpage(unittest.TestCase): pp = self._makeOne('pp.pt') wrapped = pp(to_wrap) self.assertEqual(wrapped.__name__, 'to_wrap') - self.assertEqual(wrapped.__grok_module__, to_wrap.__module__) + self.assertEqual(wrapped.__module__, to_wrap.__module__) def test___call___passes_names_from_wrapped(self): self._zcmlConfigure() diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py index 3210e6940..6fa7a920d 100644 --- a/repoze/bfg/tests/test_view.py +++ b/repoze/bfg/tests/test_view.py @@ -471,6 +471,54 @@ class TestStaticView(unittest.TestCase, BaseTest): response = view(context, request) self.failUnless(isinstance(response, Response2)) +class TestBFGViewDecorator(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _getTargetClass(self): + from repoze.bfg.view import bfg_view + return bfg_view + + def _makeOne(self, *arg, **kw): + return self._getTargetClass()(*arg, **kw) + + def test_create_defaults(self): + from repoze.bfg.interfaces import IRequest + from zope.interface import Interface + decorator = self._makeOne() + self.assertEqual(decorator.name, '') + self.assertEqual(decorator.request_type, IRequest) + self.assertEqual(decorator.for_, Interface) + self.assertEqual(decorator.permission, None) + + def test_create_nondefaults(self): + decorator = self._makeOne(name=None, request_type=None, for_=None, + permission='foo') + self.assertEqual(decorator.name, None) + self.assertEqual(decorator.request_type, None) + self.assertEqual(decorator.for_, None) + self.assertEqual(decorator.permission, 'foo') + + def test_call(self): + from repoze.bfg.interfaces import IRequest + from zope.interface import Interface + decorator = self._makeOne() + class foo: + """ docstring """ + wrapped = decorator(foo) + self.assertEqual(wrapped.__is_bfg_view__, True) + self.assertEqual(wrapped.__permission__, None) + self.assertEqual(wrapped.__for__, Interface) + self.assertEqual(wrapped.__request_type__, IRequest) + self.assertEqual(wrapped.__grok_module__, foo.__module__) + self.assertEqual(wrapped.__name__, foo.__name__) + self.assertEqual(wrapped.__doc__, foo.__doc__) + for k, v in foo.__dict__.items(): + self.assertEqual(v, wrapped.__dict__[k]) + class DummyContext: pass diff --git a/repoze/bfg/tests/test_wsgi.py b/repoze/bfg/tests/test_wsgi.py index ac02ec49f..e19e65044 100644 --- a/repoze/bfg/tests/test_wsgi.py +++ b/repoze/bfg/tests/test_wsgi.py @@ -9,63 +9,22 @@ class WSGIAppTests(unittest.TestCase): cleanUp() def test_decorator(self): - body = 'Unauthorized' - headerlist = [ ('Content-Type', 'text/plain'), - ('Content-Length', len(body)) ] - status = '401 Unauthorized' - def real_wsgiapp(environ, start_response): - start_response(status, headerlist) - return [body] from repoze.bfg.wsgi import wsgiapp - wrapped = wsgiapp(real_wsgiapp) + wrapped = wsgiapp(dummyapp) context = DummyContext() - request = DummyRequest({}) + request = DummyRequest() response = wrapped(context, request) - self.assertEqual(response.status, status) - self.assertEqual(response.headerlist, headerlist) - self.assertEqual(response.app_iter, [body]) + self.assertEqual(response, dummyapp) - def test_decorator_alternate_iresponsefactory(self): - body = 'Unauthorized' - headerlist = [ ('Content-Type', 'text/plain'), - ('Content-Length', len(body)) ] - status = '401 Unauthorized' - def real_wsgiapp(environ, start_response): - start_response(status, headerlist) - return [body] - from repoze.bfg.wsgi import wsgiapp - wrapped = wsgiapp(real_wsgiapp) - context = DummyContext() - request = DummyRequest({}) - from repoze.bfg.interfaces import IResponseFactory - from zope.component import getGlobalSiteManager - from webob import Response - class Response2(Response): - pass - gsm = getGlobalSiteManager() - gsm.registerUtility(Response2, IResponseFactory) - response = wrapped(context, request) - self.failUnless(isinstance(response, Response2)) - - def test_decorator_startresponse_uncalled(self): - body = 'Unauthorized' - headerlist = [ ('Content-Type', 'text/plain'), - ('Content-Length', len(body)) ] - status = '401 Unauthorized' - def real_wsgiapp(environ, start_response): - return [body] - from repoze.bfg.wsgi import wsgiapp - wrapped = wsgiapp(real_wsgiapp) - context = DummyContext() - request = DummyRequest({}) - self.assertRaises(RuntimeError, wrapped, context, request) +def dummyapp(environ, start_response): + """ """ class DummyContext: pass class DummyRequest: - def __init__(self, environ): - self.environ = environ + def get_response(self, application): + return application diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 260a73c02..1d7f42098 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -229,28 +229,6 @@ class TestViewDirective(unittest.TestCase): self.assertEqual(regadapt['args'][5], None) -class TestFixtureApp(unittest.TestCase): - def setUp(self): - cleanUp() - - def tearDown(self): - cleanUp() - - def test_registry_actions_can_be_pickled_and_unpickled(self): - import repoze.bfg.tests.fixtureapp 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) - context.execute_actions(clear=False) - actions = context.actions - import cPickle - dumped = cPickle.dumps(actions, -1) - new = cPickle.loads(dumped) - self.assertEqual(len(actions), len(new)) - class TestZCMLPickling(unittest.TestCase): i = 0 def setUp(self): @@ -456,8 +434,121 @@ class TestZCMLPickling(unittest.TestCase): cPickle.dump(actions, open(picklename, 'wb')) self.assertEqual(True, zcml_configure('configure.zcml', self.module)) -class Dummy: - pass +class TestRemove(unittest.TestCase): + def _callFUT(self, name, os): + from repoze.bfg.zcml import remove + return remove(name, os) + + def test_fail(self): + class FakeOS: + def remove(self, name): + raise IOError('foo') + self.assertEqual(self._callFUT('name', FakeOS()), False) + + def test_succeed(self): + class FakeOS: + def remove(self, name): + pass + self.assertEqual(self._callFUT('name', FakeOS()), True) + +class TestBFGViewFunctionGrokker(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _getTargetClass(self): + from repoze.bfg.zcml import BFGViewFunctionGrokker + return BFGViewFunctionGrokker + + def _makeOne(self, *arg, **kw): + return self._getTargetClass()(*arg, **kw) + + def test_grok_is_bfg_view(self): + from repoze.bfg.interfaces import IRequest + from zope.interface import Interface + grokker = self._makeOne() + class obj: + pass + obj.__is_bfg_view__ = True + obj.__permission__ = 'foo' + obj.__for__ = Interface + obj.__view_name__ = 'foo.html' + obj.__request_type__ = IRequest + context = DummyContext() + result = grokker.grok('name', obj, context=context) + self.assertEqual(result, True) + actions = context.actions + self.assertEqual(len(actions), 2) + + def test_grok_is_not_bfg_view(self): + grokker = self._makeOne() + class obj: + pass + context = DummyContext() + result = grokker.grok('name', obj, context=context) + self.assertEqual(result, False) + actions = context.actions + self.assertEqual(len(actions), 0) + +class TestZCMLGrokFunction(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, context, package, martian): + from repoze.bfg.zcml import grok + return grok(context, package, martian) + + def test_it(self): + 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) + self.assertEqual(martian.context, None) + self.assertEqual(martian.exclude_filter, exclude) + +class TestExcludeFunction(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, name): + from repoze.bfg.zcml import exclude + return exclude(name) + + def test_it(self): + self.assertEqual(self._callFUT('.foo'), True) + self.assertEqual(self._callFUT('foo'), False) + +class DummyModule: + __name__ = 'dummy' + +class DummyModuleGrokker: + def __init__(self): + self.registered = [] + + def register(self, other): + self.registered.append(other) + +class DummyMartianModule: + def grok_dotted_name(self, name, grokker, context, exclude_filter=None): + self.name = name + self.context = context + self.exclude_filter = exclude_filter + return True + + def ModuleGrokker(self): + self.module_grokker = DummyModuleGrokker() + return self.module_grokker class DummyContext: def __init__(self): @@ -471,6 +562,9 @@ class DummyContext: 'args':args} ) +class Dummy: + pass + from zope.interface import Interface class IDummy(Interface): pass diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 3f9216b0d..f580a4fb1 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -12,6 +12,10 @@ from repoze.bfg.interfaces import IView from repoze.bfg.security import Unauthorized from repoze.bfg.security import Allowed +from zope.interface import Interface + +from repoze.bfg.interfaces import IRequest + _marker = () def view_execution_permitted(context, request, name=''): @@ -162,3 +166,88 @@ class static(object): response.headerlist = headers return response +class bfg_view(object): + """ Decorator which allows Python code to make view registrations + instead of using ZCML for the same purpose. + + E.g. in the module ``views.py``:: + + from models import IMyModel + from repoze.bfg.interfaces import IRequest + + @bfg_view(name='my_view', request_type=IRequest, for_=IMyModel, + permission='read')) + def my_view(context, request): + return render_template_to_response('templates/my.pt') + + Equates to the ZCML:: + + <bfg:view + for='.models.IMyModel' + view='.views.my_view' + name='my_view' + permission='read' + /> + + If ``name`` is not supplied, the empty string is used (implying + the default view). + + If ``request_type`` is not supplied, the interface + ``repoze.bfg.interfaces.IRequest`` is used. + + If ``for_`` is not supplied, the interface + ``zope.interface.Interface`` (implying *all* interfaces) is used. + + If ``permission`` is not supplied, no permission is registered for + this view (it's accessible by any caller). + + Any individual or all parameters can be omitted. The simplest + bfg_view declaration then becomes:: + + @bfg_view() + def my_view(...): + ... + + Such a registration implies that the view name will be + ``my_view``, registered for models with the + ``zope.interface.Interface`` interface, using no permission, + registered against requests which implement the default IRequest + interface. + + To make use of bfg_view declarations, insert the following + boilerplate into your application registry's ZCML:: + + <grok package="."/> + """ + def __init__(self, name='', request_type=IRequest, for_=Interface, + permission=None): + self.name = name + self.request_type = request_type + self.for_ = for_ + self.permission = permission + + def __call__(self, wrapped): + # We intentionally return a do-little un-functools-wrapped + # decorator here so as to make the decorated function + # unpickleable; applications which use bfg_view decorators + # should never be able to load actions from an actions cache; + # instead they should rerun the file_configure function each + # time the application starts in case any of the decorators + # has been changed. Disallowing these functions from being + # pickled enforces that. + def decorator(context, request): + return wrapped(context, request) + decorator.__is_bfg_view__ = True + decorator.__permission__ = self.permission + decorator.__for__ = self.for_ + decorator.__view_name__ = self.name + decorator.__request_type__ = self.request_type + # we assign to __grok_module__ here rather than __module__ to + # make it unpickleable but allow for the grokker to be able to + # find it + decorator.__grok_module__ = wrapped.__module__ + decorator.__name__ = wrapped.__name__ + decorator.__doc__ = wrapped.__doc__ + decorator.__dict__.update(wrapped.__dict__) + return decorator + diff --git a/repoze/bfg/wsgi.py b/repoze/bfg/wsgi.py index 93b2c143b..b0feef29e 100644 --- a/repoze/bfg/wsgi.py +++ b/repoze/bfg/wsgi.py @@ -1,8 +1,3 @@ -from zope.component import queryUtility - -from repoze.bfg.interfaces import IResponseFactory - -from webob import Response try: from functools import wraps except ImportError: @@ -34,20 +29,5 @@ def wsgiapp(wrapped): WSGI app were a repoze.bfg view. """ def decorator(context, request): - caught = [] - def catch_start_response(status, headers, exc_info=None): - caught[:] = (status, headers, exc_info) - environ = request.environ - body = wrapped(environ, catch_start_response) - if caught: - status, headers, exc_info = caught - response_factory = queryUtility(IResponseFactory, default=Response) - response = response_factory() - response.app_iter = body - response.status = status - response.headerlist = headers - return response - else: - raise RuntimeError('WSGI start_response not called') - return wraps(wrapped)(decorator) # for pickleability - + return request.get_response(wrapped) + return wraps(wrapped)(decorator) # pickleability diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 9f88bd6fe..f1bc9eb94 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -24,6 +24,8 @@ from repoze.bfg.path import package_path from repoze.bfg.security import ViewPermissionFactory +import martian + def handler(methodName, *args, **kwargs): method = getattr(getSiteManager(), methodName) method(*args, **kwargs) @@ -176,6 +178,14 @@ def zcml_configure(name, package, load=cPickle.load): context.execute_actions() return True +def remove(name, os=os): # os parameterized for unit tests + try: + os.remove(name) + return True + except: + pass + return False + def file_configure(name, package, dump=cPickle.dump): context = zope.configuration.config.ConfigurationMachine() xmlconfig.registerCommonDirectives(context) @@ -191,20 +201,52 @@ def file_configure(name, package, dump=cPickle.dump): discriminator = action[0] if discriminator and Uncacheable in discriminator: - try: - os.remove(pckname) - except: - pass + remove(pckname) return False try: data = (PVERSION, time.time(), actions) dump(data, open(pckname, 'wb'), -1) except (OSError, IOError, TypeError, cPickle.PickleError): - try: - os.remove(pckname) - except: - pass + remove(pckname) + + return False +def exclude(name): + if name.startswith('.'): + return True return False +def grok(_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 IGrokDirective(Interface): + package = GlobalObject( + title=u"The package we'd like to grok.", + required=True, + ) + +class BFGViewFunctionGrokker(martian.InstanceGrokker): + martian.component(types.FunctionType) + + def grok(self, name, obj, **kw): + if hasattr(obj, '__is_bfg_view__'): + permission = obj.__permission__ + for_ = obj.__for__ + name = obj.__view_name__ + request_type = obj.__request_type__ + context = kw['context'] + # we dont tecnically need to pass "Uncacheable" here; any + # view function decorated with an __is_bfg_view__ attr via + # repoze.bfg.view.bfg_view is unpickleable; but the + # uncacheable bit helps pickling fail more quickly + # (pickling is never attempted) + view(context, permission=permission, for_=for_, + view=obj, name=name, request_type=request_type, + cacheable=Uncacheable) + return True + return False @@ -41,6 +41,7 @@ install_requires=[ 'zope.hookable', 'zope.testing', 'repoze.zcml', + 'martian', ] setup(name='repoze.bfg', |
