summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-01-16 18:58:16 +0000
committerChris McDonough <chrism@agendaless.com>2009-01-16 18:58:16 +0000
commit5a7f9a4d57424f14a1e072cc06b6bf7a191a7d08 (patch)
treeb44448198ddf8031b3e09b83dd731f2ae1d6623a
parent4856cc54bcd5feb97db49f1cca923afb01c0bf02 (diff)
downloadpyramid-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.txt19
-rw-r--r--repoze/bfg/includes/meta.zcml6
-rw-r--r--repoze/bfg/push.py16
-rw-r--r--repoze/bfg/tests/grokkedapp/__init__.py11
-rw-r--r--repoze/bfg/tests/grokkedapp/configure.zcml6
-rw-r--r--repoze/bfg/tests/test_integration.py152
-rw-r--r--repoze/bfg/tests/test_push.py2
-rw-r--r--repoze/bfg/tests/test_view.py48
-rw-r--r--repoze/bfg/tests/test_wsgi.py55
-rw-r--r--repoze/bfg/tests/test_zcml.py142
-rw-r--r--repoze/bfg/view.py89
-rw-r--r--repoze/bfg/wsgi.py24
-rw-r--r--repoze/bfg/zcml.py58
-rw-r--r--setup.py1
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
diff --git a/setup.py b/setup.py
index 5cc6d3f64..9fb8c03c8 100644
--- a/setup.py
+++ b/setup.py
@@ -41,6 +41,7 @@ install_requires=[
'zope.hookable',
'zope.testing',
'repoze.zcml',
+ 'martian',
]
setup(name='repoze.bfg',