summaryrefslogtreecommitdiff
path: root/repoze
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-10-21 21:48:19 +0000
committerChris McDonough <chrism@agendaless.com>2009-10-21 21:48:19 +0000
commitb2f9291bfb55f9305f59b8ba0e3fe04ff6f31cc6 (patch)
tree5afe13809e933cc16ea5e2cca7908a0b6c0caab2 /repoze
parentb06b260b83bfc2f40105c6e730f5e7e8eccd61d9 (diff)
downloadpyramid-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.py19
-rw-r--r--repoze/bfg/tests/test_zcml.py134
-rw-r--r--repoze/bfg/view.py19
-rw-r--r--repoze/bfg/zcml.py59
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