diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-10-21 21:48:19 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-10-21 21:48:19 +0000 |
| commit | b2f9291bfb55f9305f59b8ba0e3fe04ff6f31cc6 (patch) | |
| tree | 5afe13809e933cc16ea5e2cca7908a0b6c0caab2 /repoze | |
| parent | b06b260b83bfc2f40105c6e730f5e7e8eccd61d9 (diff) | |
| download | pyramid-b2f9291bfb55f9305f59b8ba0e3fe04ff6f31cc6.tar.gz pyramid-b2f9291bfb55f9305f59b8ba0e3fe04ff6f31cc6.tar.bz2 pyramid-b2f9291bfb55f9305f59b8ba0e3fe04ff6f31cc6.zip | |
- Change how ``bfg_view`` decorator works when used as a class method
decorator. In 1.1a7, it actually tried to grope every class in
scanned package at startup time looking for methods, which led to
some strange symptoms (e.g. ``AttributeError: object has no
attribute __provides__``). Now, instead of groping methods at
startup time, we just cause the ``bfg_view`` decorator itself to
populate its class' __dict__ when its used inside a class as a
method decorator. This is essentially a reversion back to 1.1a6
"grokking" behavior plus some special magic for using the
``bfg_view`` decorator as method decorator inside the ``bfg_view``
class itself.
Diffstat (limited to 'repoze')
| -rw-r--r-- | repoze/bfg/tests/test_view.py | 19 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 134 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 19 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 59 |
4 files changed, 36 insertions, 195 deletions
diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py index 7b9597194..bbef24359 100644 --- a/repoze/bfg/tests/test_view.py +++ b/repoze/bfg/tests/test_view.py @@ -412,12 +412,25 @@ class TestBFGViewDecorator(unittest.TestCase): wrapped2 = decorator2(wrapped1) self.failUnless(wrapped1 is foo) self.failUnless(wrapped2 is foo) - self.assertEqual(len(wrapped2.__bfg_view_settings__), 2) - settings1 = wrapped2.__bfg_view_settings__[0] + self.assertEqual(len(foo.__bfg_view_settings__), 2) + settings1 = foo.__bfg_view_settings__[0] self.assertEqual(settings1['name'], '1') - settings2 = wrapped2.__bfg_view_settings__[1] + settings2 = foo.__bfg_view_settings__[1] self.assertEqual(settings2['name'], '2') + def test_call_as_method(self): + decorator = self._makeOne() + def foo(self): pass + def bar(self): pass + class foo(object): + """ docstring """ + foomethod = decorator(foo) + barmethod = decorator(bar) + settings = foo.__bfg_view_settings__ + self.assertEqual(len(settings), 2) + self.assertEqual(settings[0]['attr'], 'foo') + self.assertEqual(settings[1]['attr'], 'bar') + class TestDefaultForbiddenView(unittest.TestCase): def _callFUT(self, context, request): from repoze.bfg.view import default_forbidden_view diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index d6eb80c44..aed4758f4 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -2413,98 +2413,6 @@ class TestBFGViewGrokker(unittest.TestCase): actions = context.actions self.assertEqual(len(actions), 0) -class TestBFGClassGrokker(unittest.TestCase): - def setUp(self): - cleanUp() - - def tearDown(self): - cleanUp() - - def _getTargetClass(self): - from repoze.bfg.zcml import BFGClassGrokker - return BFGClassGrokker - - def _makeOne(self, *arg, **kw): - return self._getTargetClass()(*arg, **kw) - - def test_calls_bfg_view_grokker(self): - from zope.component import getSiteManager - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from zope.interface import Interface - class obj: - def __init__(self, context, request): - pass - def __call__(self): - return 'OK' - settings = dict(permission='foo', for_=Interface, name='foo.html', - request_type=IRequest, route_name=None, - request_method=None, request_param=None, - containment=None, attr=None, renderer=None, - wrapper=None, xhr=False, header=None, - accept=None) - obj.__bfg_view_settings__ = [settings] - grokker = self._makeOne() - context = DummyContext() - result = grokker.grok('name', obj, context=context) - self.assertEqual(result, True) - actions = context.actions - self.assertEqual(len(actions), 1) - register = actions[0]['callable'] - register() - sm = getSiteManager() - wrapped = sm.adapters.lookup((Interface, IRequest), IView, - name='foo.html') - self.assertEqual(wrapped(None, None), 'OK') - - def test_it(self): - from repoze.bfg.interfaces import IRequest - from zope.component import getSiteManager - from zope.interface import Interface - from repoze.bfg.interfaces import IView - class base(object): - def basemethod(self): - """ """ - basemethod.__bfg_view_settings__ = [ - dict(permission=None, for_=Interface, name='basemethod', - request_type=IRequest, route_name=None, - request_method=None, request_param=None, - containment=None, attr=None, renderer=None, - wrapper=None, xhr=False, header=None, - accept=None) - ] - - class obj(base): - def __init__(self, context, request): - pass - def foo(self): - return 'OK' - foo.__bfg_view_settings__ = [ - dict(permission=None, for_=Interface, name='foo', - request_type=IRequest, route_name=None, - request_method=None, request_param=None, - containment=None, attr=None, renderer=None, - wrapper=None, xhr=False, header=None, - accept=None) - ] - - grokker = self._makeOne() - context = DummyContext() - result = grokker.grok('name', obj, context=context) - self.assertEqual(result, True) - actions = context.actions - self.assertEqual(len(actions), 1) - register = actions[0]['callable'] - register() - sm = getSiteManager() - wrapped = sm.adapters.lookup((Interface, IRequest), IView, - name='foo') - self.assertEqual(wrapped(None, None), 'OK') - wrapped = sm.adapters.lookup((Interface, IRequest), IView, - name='basemethod') - # base methods not grokked - self.assertEqual(wrapped, None) - class TestZCMLScanDirective(unittest.TestCase): def setUp(self): cleanUp() @@ -2544,48 +2452,6 @@ class TestExcludeFunction(unittest.TestCase): self.assertEqual(self._callFUT('.foo'), True) self.assertEqual(self._callFUT('foo'), False) -class TestGetMembersFunction(unittest.TestCase): - def _callFUT(self, object, predicate=None): - from repoze.bfg.zcml import getmembers - return getmembers(object, predicate) - - def test_with_attribute_error(self): - class Dummy: - @property - def raises(self): - raise AttributeError('raises') - ob = Dummy() - result = self._callFUT(ob, None) - result.sort() - self.assertEqual(result, - [('__doc__', None), - ('__module__', 'repoze.bfg.tests.test_zcml')] - ) - - def test_without_attribute_error(self): - class Dummy: - def doesntraise(self): - pass - ob = Dummy() - result = self._callFUT(ob, None) - result.sort() - self.assertEqual(result, - [('__doc__', None), - ('__module__', 'repoze.bfg.tests.test_zcml'), - ('doesntraise', ob.doesntraise)] - ) - - def test_predicate(self): - class Dummy: - def doesntraise(self): - pass - ob = Dummy() - def predicate(testing): - return getattr(testing, '__name__', None) == 'doesntraise' - result = self._callFUT(ob, predicate) - result.sort() - self.assertEqual(result,[('doesntraise', ob.doesntraise)]) - class DummyModule: __path__ = "foo" __name__ = "dummy" diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 8c1106537..c1e560ea8 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -2,6 +2,7 @@ import cgi import inspect import mimetypes import os +import sys # See http://bugs.python.org/issue5853 which is a recursion bug # that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix @@ -23,6 +24,7 @@ from zope.component import providedBy from zope.component import queryUtility from zope.deprecation import deprecated from zope.interface import implements +from zope.interface.advice import getFrameInfo from repoze.bfg.interfaces import IAuthenticationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy @@ -435,9 +437,20 @@ class bfg_view(object): self.header = header def __call__(self, wrapped): - settings = getattr(wrapped, '__bfg_view_settings__', []) - settings.append(self.__dict__.copy()) - wrapped.__bfg_view_settings__ = settings + setting = self.__dict__.copy() + frame = sys._getframe(1) + scope, module, f_locals, f_globals = getFrameInfo(frame) + if scope == 'class': + # we're in the midst of a class statement; the setdefault + # below actually adds a __bfg_view_settings__ attr to the + # class __dict__ if one does not already exist + settings = f_locals.setdefault('__bfg_view_settings__', []) + if setting['attr'] is None: + setting['attr'] = wrapped.__name__ + else: + settings = getattr(wrapped, '__bfg_view_settings__', []) + wrapped.__bfg_view_settings__ = settings + settings.append(setting) return wrapped def default_view(context, request, status): diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 6bf180b3a..84b1243d1 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -1,8 +1,6 @@ -import inspect import re import sys -from zope.interface import implements from zope.component import getSiteManager from zope.component import getUtility from zope.component import queryUtility @@ -19,7 +17,6 @@ from zope.schema import Int from zope.schema import TextLine import martian -import martian.interfaces # pyflakes whines, but don't delete it from repoze.bfg.interfaces import IAuthenticationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy @@ -678,56 +675,23 @@ def scan(_context, package, martian=martian): # martian overrideable only for unit tests multi_grokker = BFGMultiGrokker() multi_grokker.register(BFGViewGrokker()) - multi_grokker.register(BFGClassGrokker()) module_grokker = martian.ModuleGrokker(grokker=multi_grokker) martian.grok_dotted_name(package.__name__, grokker=module_grokker, context=_context, exclude_filter=exclude) ################# utility stuff #################### -class Class(object): - pass - -class AnythingButAClass(object): +class BFGViewMarker(object): pass class BFGMultiGrokker(martian.core.MultiInstanceOrClassGrokkerBase): def get_bases(self, obj): - if inspect.isclass(obj): - return [Class] - elif hasattr(obj, '__bfg_view_settings__'): - return [AnythingButAClass] + if hasattr(obj, '__bfg_view_settings__'): + return [BFGViewMarker] return [] -class BFGClassGrokker(object): - implements(martian.interfaces.IGrokker) - martian.component(Class) - def grok(self, name, class_, module_info=None, **kw): - # The class itself may be decorated, so we feed it to BFGViewGrokker - found = BFGViewGrokker().grok(name, class_, **kw) - - # grok any decorations attached to the class' method (direct - # methods only, not methods of any base class); we can't use - # inspect.getmembers here because it doesn't deal with a - # corner case where objects that inherit from Persistent - # advertises a '__provides__' as per dir(ob) but getattr(ob, - # '__provides__') raises an AttributeError. Unknown why this - # happens, but it doesn't matter: need to deal with it. - methods = getmembers(class_, inspect.ismethod) - classmethods = class_.__dict__.keys() - for method_name, method in methods: - if method_name in classmethods: - # it's not an inherited method - config = getattr(method, '__bfg_view_settings__', []) - for settings in config: - settings = dict(settings) - settings['attr'] = settings['attr'] or method_name - view(kw['context'], view=class_, **settings) - found = True - return found - class BFGViewGrokker(martian.InstanceGrokker): - martian.component(AnythingButAClass) + martian.component(BFGViewMarker) def grok(self, name, obj, **kw): config = getattr(obj, '__bfg_view_settings__', []) for settings in config: @@ -745,18 +709,3 @@ class Uncacheable(object): file_configure = zcml_configure # backwards compat (>0.8.1) -def getmembers(object, predicate=None): - """Return all members of an object as (name, value) pairs sorted - by name. Optionally, only return members that satisfy a given - predicate. Differs from inspect.getmembers inasmuch as it ignores - AttributeErrors.""" - results = [] - for key in dir(object): - try: - value = getattr(object, key) - except AttributeError: - continue - if not predicate or predicate(value): - results.append((key, value)) - results.sort() - return results |
