summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-06-18 21:49:33 +0000
committerChris McDonough <chrism@agendaless.com>2009-06-18 21:49:33 +0000
commitf5c25ef97393f7b4bf1353b11eeb841c53e2feaf (patch)
tree7c820aaa1a769dfc096aed05c50116821e89707a
parent7687344b77fbc9c4bf998d20828b10a339b90eed (diff)
downloadpyramid-f5c25ef97393f7b4bf1353b11eeb841c53e2feaf.tar.gz
pyramid-f5c25ef97393f7b4bf1353b11eeb841c53e2feaf.tar.bz2
pyramid-f5c25ef97393f7b4bf1353b11eeb841c53e2feaf.zip
- Allow views to be *optionally* defined as callables that accept only
a request object, instead of both a context and a request (which still works, and always will). The following types work as views in this style: - functions that accept a single argument ``request``, e.g.:: def aview(request): pass - new and old-style classes that have an ``__init__`` method that accepts ``self, request``, e.g.:: def View(object): __init__(self, request): pass - Arbitrary callables that have a ``__call__`` method that accepts ``self, request``, e.g.:: def AView(object): def __call__(self, request): pass view = AView() This likely should have been the calling convention all along, as the request has ``context`` as an attribute already, and with views called as a result of URL dispatch, having the context in the arguments is not very useful. C'est la vie.
-rw-r--r--CHANGES.txt30
-rw-r--r--repoze/bfg/router.py11
-rw-r--r--repoze/bfg/tests/test_zcml.py422
-rw-r--r--repoze/bfg/zcml.py75
4 files changed, 524 insertions, 14 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index c54385b3c..476048dc5 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,36 @@ Next release
Features
--------
+- Allow views to be *optionally* defined as callables that accept only
+ a request object, instead of both a context and a request (which
+ still works, and always will). The following types work as views in
+ this style:
+
+ - functions that accept a single argument ``request``, e.g.::
+
+ def aview(request):
+ pass
+
+ - new and old-style classes that have an ``__init__`` method that
+ accepts ``self, request``, e.g.::
+
+ def View(object):
+ __init__(self, request):
+ pass
+
+ - Arbitrary callables that have a ``__call__`` method that accepts
+ ``self, request``, e.g.::
+
+ def AView(object):
+ def __call__(self, request):
+ pass
+ view = AView()
+
+ This likely should have been the calling convention all along, as
+ the request has ``context`` as an attribute already, and with views
+ called as a result of URL dispatch, having the context in the
+ arguments is not very useful. C'est la vie.
+
- Cache the absolute path in the caller's package globals within
``repoze.bfg.path`` to get rid of repeated (expensive) calls to
os.path.abspath.
diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py
index a5bea3f69..d85f599b2 100644
--- a/repoze/bfg/router.py
+++ b/repoze/bfg/router.py
@@ -2,6 +2,7 @@ from cgi import escape
import sys
from webob import Response
+from zope.interface import providedBy
from zope.component.event import dispatch
from zope.component import queryUtility
@@ -205,16 +206,15 @@ class Router(object):
msg = str(permitted)
else:
msg = 'Unauthorized: failed security policy check'
-
environ['repoze.bfg.message'] = msg
-
return respond(self.forbidden_view(context, request),
'<IForbiddenView>')
- response = registry.queryMultiAdapter(
- (context, request), IView, name=view_name)
+ view_callable = registry.adapters.lookup(
+ map(providedBy, (context, request)), IView, name=view_name,
+ default=None)
- if response is None:
+ if view_callable is None:
if self.debug_notfound:
msg = (
'debug_notfound of url %s; path_info: %r, context: %r, '
@@ -230,6 +230,7 @@ class Router(object):
return respond(self.notfound_view(context, request),
'<INotFoundView>')
+ response = view_callable(context, request)
return respond(response, view_name)
finally:
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index 2fad91150..c7a37cd35 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -58,6 +58,144 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(regadapt['args'][4], '')
self.assertEqual(regadapt['args'][5], None)
+ def test_view_as_function_requestonly(self):
+ context = DummyContext()
+
+ def view(request):
+ return 'OK'
+ class IFoo:
+ pass
+
+ self._callFUT(context, 'repoze.view', IFoo, view=view)
+ actions = context.actions
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+ from repoze.bfg.security import ViewPermissionFactory
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 2)
+
+ permission = actions[0]
+ permission_discriminator = ('permission', IFoo, '', IRequest,
+ IViewPermission)
+ self.assertEqual(permission['discriminator'], permission_discriminator)
+ self.assertEqual(permission['callable'], handler)
+ self.assertEqual(permission['args'][0], 'registerAdapter')
+ self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory))
+ self.assertEqual(permission['args'][1].permission_name, 'repoze.view')
+ self.assertEqual(permission['args'][2], (IFoo, IRequest))
+ self.assertEqual(permission['args'][3], IViewPermission)
+ self.assertEqual(permission['args'][4], '')
+ self.assertEqual(permission['args'][5], None)
+
+ regadapt = actions[1]
+ regadapt_discriminator = ('view', IFoo, '', IRequest, IView)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerAdapter')
+ wrapper = regadapt['args'][1]
+ self.failIfEqual(wrapper, view)
+ self.assertEqual(wrapper.__module__, view.__module__)
+ self.assertEqual(wrapper.__name__, view.__name__)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result, 'OK')
+ self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+ self.assertEqual(regadapt['args'][3], IView)
+ self.assertEqual(regadapt['args'][4], '')
+ self.assertEqual(regadapt['args'][5], None)
+
+ def test_view_as_instance(self):
+ context = DummyContext()
+ class AView:
+ def __call__(self, context, request):
+ """ """
+ view = AView()
+ class IFoo:
+ pass
+ self._callFUT(context, 'repoze.view', IFoo, view=view)
+ actions = context.actions
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+ from repoze.bfg.security import ViewPermissionFactory
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 2)
+
+ permission = actions[0]
+ permission_discriminator = ('permission', IFoo, '', IRequest,
+ IViewPermission)
+ self.assertEqual(permission['discriminator'], permission_discriminator)
+ self.assertEqual(permission['callable'], handler)
+ self.assertEqual(permission['args'][0], 'registerAdapter')
+ self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory))
+ self.assertEqual(permission['args'][1].permission_name, 'repoze.view')
+ self.assertEqual(permission['args'][2], (IFoo, IRequest))
+ self.assertEqual(permission['args'][3], IViewPermission)
+ self.assertEqual(permission['args'][4], '')
+ self.assertEqual(permission['args'][5], None)
+
+ regadapt = actions[1]
+ regadapt_discriminator = ('view', IFoo, '', IRequest, IView)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerAdapter')
+ self.assertEqual(regadapt['args'][1], view)
+ self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+ self.assertEqual(regadapt['args'][3], IView)
+ self.assertEqual(regadapt['args'][4], '')
+ self.assertEqual(regadapt['args'][5], None)
+
+ def test_view_as_instance_requestonly(self):
+ context = DummyContext()
+ class AView:
+ def __call__(self, request):
+ return 'OK'
+ view = AView()
+ class IFoo:
+ pass
+ self._callFUT(context, 'repoze.view', IFoo, view=view)
+ actions = context.actions
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+ from repoze.bfg.security import ViewPermissionFactory
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 2)
+
+ permission = actions[0]
+ permission_discriminator = ('permission', IFoo, '', IRequest,
+ IViewPermission)
+ self.assertEqual(permission['discriminator'], permission_discriminator)
+ self.assertEqual(permission['callable'], handler)
+ self.assertEqual(permission['args'][0], 'registerAdapter')
+ self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory))
+ self.assertEqual(permission['args'][1].permission_name, 'repoze.view')
+ self.assertEqual(permission['args'][2], (IFoo, IRequest))
+ self.assertEqual(permission['args'][3], IViewPermission)
+ self.assertEqual(permission['args'][4], '')
+ self.assertEqual(permission['args'][5], None)
+
+ regadapt = actions[1]
+ regadapt_discriminator = ('view', IFoo, '', IRequest, IView)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerAdapter')
+ wrapper = regadapt['args'][1]
+ self.failIfEqual(wrapper, view)
+ self.assertEqual(wrapper.__module__, view.__module__)
+ self.failUnless('instance' in wrapper.__name__)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result, 'OK')
+ self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+ self.assertEqual(regadapt['args'][3], IView)
+ self.assertEqual(regadapt['args'][4], '')
+ self.assertEqual(regadapt['args'][5], None)
+
def test_view_as_oldstyle_class(self):
context = DummyContext()
class IFoo:
@@ -109,6 +247,155 @@ class TestViewDirective(unittest.TestCase):
self.assertEqual(regadapt['args'][4], '')
self.assertEqual(regadapt['args'][5], None)
+ def test_view_as_oldstyle_class_requestonly(self):
+ context = DummyContext()
+ class IFoo:
+ pass
+ class view:
+ def __init__(self, request):
+ self.request = request
+
+ def __call__(self):
+ return self
+ self._callFUT(context, 'repoze.view', IFoo, view=view)
+ actions = context.actions
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+ from repoze.bfg.security import ViewPermissionFactory
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 2)
+
+ permission = actions[0]
+ permission_discriminator = ('permission', IFoo, '', IRequest,
+ IViewPermission)
+ self.assertEqual(permission['discriminator'], permission_discriminator)
+ self.assertEqual(permission['callable'], handler)
+ self.assertEqual(permission['args'][0], 'registerAdapter')
+ self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory))
+ self.assertEqual(permission['args'][1].permission_name, 'repoze.view')
+ self.assertEqual(permission['args'][2], (IFoo, IRequest))
+ self.assertEqual(permission['args'][3], IViewPermission)
+ self.assertEqual(permission['args'][4], '')
+ self.assertEqual(permission['args'][5], None)
+
+ regadapt = actions[1]
+ regadapt_discriminator = ('view', IFoo, '', IRequest, IView)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerAdapter')
+ wrapper = regadapt['args'][1]
+ self.assertEqual(wrapper.__module__, view.__module__)
+ self.assertEqual(wrapper.__name__, view.__name__)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result.request, None)
+ self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+ self.assertEqual(regadapt['args'][3], IView)
+ self.assertEqual(regadapt['args'][4], '')
+ self.assertEqual(regadapt['args'][5], None)
+
+ def test_view_as_newstyle_class(self):
+ context = DummyContext()
+ class IFoo:
+ pass
+ class view(object):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self):
+ return self
+ self._callFUT(context, 'repoze.view', IFoo, view=view)
+ actions = context.actions
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+ from repoze.bfg.security import ViewPermissionFactory
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 2)
+
+ permission = actions[0]
+ permission_discriminator = ('permission', IFoo, '', IRequest,
+ IViewPermission)
+ self.assertEqual(permission['discriminator'], permission_discriminator)
+ self.assertEqual(permission['callable'], handler)
+ self.assertEqual(permission['args'][0], 'registerAdapter')
+ self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory))
+ self.assertEqual(permission['args'][1].permission_name, 'repoze.view')
+ self.assertEqual(permission['args'][2], (IFoo, IRequest))
+ self.assertEqual(permission['args'][3], IViewPermission)
+ self.assertEqual(permission['args'][4], '')
+ self.assertEqual(permission['args'][5], None)
+
+ regadapt = actions[1]
+ regadapt_discriminator = ('view', IFoo, '', IRequest, IView)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerAdapter')
+ wrapper = regadapt['args'][1]
+ self.assertEqual(wrapper.__module__, view.__module__)
+ self.assertEqual(wrapper.__name__, view.__name__)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result.context, None)
+ self.assertEqual(result.request, None)
+ self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+ self.assertEqual(regadapt['args'][3], IView)
+ self.assertEqual(regadapt['args'][4], '')
+ self.assertEqual(regadapt['args'][5], None)
+
+ def test_view_as_newstyle_class_requestonly(self):
+ context = DummyContext()
+ class IFoo:
+ pass
+ class view(object):
+ def __init__(self, request):
+ self.request = request
+
+ def __call__(self):
+ return self
+ self._callFUT(context, 'repoze.view', IFoo, view=view)
+ actions = context.actions
+ from repoze.bfg.interfaces import IRequest
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IViewPermission
+ from repoze.bfg.security import ViewPermissionFactory
+ from repoze.bfg.zcml import handler
+
+ self.assertEqual(len(actions), 2)
+
+ permission = actions[0]
+ permission_discriminator = ('permission', IFoo, '', IRequest,
+ IViewPermission)
+ self.assertEqual(permission['discriminator'], permission_discriminator)
+ self.assertEqual(permission['callable'], handler)
+ self.assertEqual(permission['args'][0], 'registerAdapter')
+ self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory))
+ self.assertEqual(permission['args'][1].permission_name, 'repoze.view')
+ self.assertEqual(permission['args'][2], (IFoo, IRequest))
+ self.assertEqual(permission['args'][3], IViewPermission)
+ self.assertEqual(permission['args'][4], '')
+ self.assertEqual(permission['args'][5], None)
+
+ regadapt = actions[1]
+ regadapt_discriminator = ('view', IFoo, '', IRequest, IView)
+ self.assertEqual(regadapt['discriminator'], regadapt_discriminator)
+ self.assertEqual(regadapt['callable'], handler)
+ self.assertEqual(regadapt['args'][0], 'registerAdapter')
+ wrapper = regadapt['args'][1]
+ self.assertEqual(wrapper.__module__, view.__module__)
+ self.assertEqual(wrapper.__name__, view.__name__)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result.request, None)
+ self.assertEqual(regadapt['args'][2], (IFoo, IRequest))
+ self.assertEqual(regadapt['args'][3], IView)
+ self.assertEqual(regadapt['args'][4], '')
+ self.assertEqual(regadapt['args'][5], None)
+
def test_request_type_asinterface(self):
context = DummyContext()
class IFoo:
@@ -632,7 +919,142 @@ class TestExcludeFunction(unittest.TestCase):
def test_it(self):
self.assertEqual(self._callFUT('.foo'), True)
self.assertEqual(self._callFUT('foo'), False)
+
+class TestRequestOnly(unittest.TestCase):
+ def _callFUT(self, arg):
+ from repoze.bfg.zcml import requestonly
+ return requestonly(arg)
+ def test_newstyle_class_no_init(self):
+ class foo(object):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_newstyle_class_init_toomanyargs(self):
+ class foo(object):
+ def __init__(self, context, request):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_newstyle_class_init_onearg_named_request(self):
+ class foo(object):
+ def __init__(self, request):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_newstyle_class_init_onearg_named_somethingelse(self):
+ class foo(object):
+ def __init__(self, req):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_newstyle_class_init_defaultargs_firstname_not_request(self):
+ class foo(object):
+ def __init__(self, context, request=None):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_newstyle_class_init_defaultargs_firstname_request(self):
+ class foo(object):
+ def __init__(self, request, foo=1, bar=2):
+ """ """
+ self.assertTrue(self._callFUT(foo), True)
+
+ def test_oldstyle_class_no_init(self):
+ class foo:
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_oldstyle_class_init_toomanyargs(self):
+ class foo:
+ def __init__(self, context, request):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_oldstyle_class_init_onearg_named_request(self):
+ class foo:
+ def __init__(self, request):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_oldstyle_class_init_onearg_named_somethingelse(self):
+ class foo:
+ def __init__(self, req):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_oldstyle_class_init_defaultargs_firstname_not_request(self):
+ class foo:
+ def __init__(self, context, request=None):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_oldstyle_class_init_defaultargs_firstname_request(self):
+ class foo:
+ def __init__(self, request, foo=1, bar=2):
+ """ """
+ self.assertTrue(self._callFUT(foo), True)
+
+ def test_function_toomanyargs(self):
+ def foo(context, request):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_function_onearg_named_request(self):
+ def foo(request):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_function_onearg_named_somethingelse(self):
+ def foo(req):
+ """ """
+ self.assertTrue(self._callFUT(foo))
+
+ def test_function_defaultargs_firstname_not_request(self):
+ def foo(context, request=None):
+ """ """
+ self.assertFalse(self._callFUT(foo))
+
+ def test_function_defaultargs_firstname_request(self):
+ def foo(request, foo=1, bar=2):
+ """ """
+ self.assertTrue(self._callFUT(foo), True)
+
+ def test_instance_toomanyargs(self):
+ class Foo:
+ def __call__(self, context, request):
+ """ """
+ foo = Foo()
+ self.assertFalse(self._callFUT(foo))
+
+ def test_instance_defaultargs_onearg_named_request(self):
+ class Foo:
+ def __call__(self, request):
+ """ """
+ foo = Foo()
+ self.assertTrue(self._callFUT(foo))
+
+ def test_instance_defaultargs_onearg_named_somethingelse(self):
+ class Foo:
+ def __call__(self, req):
+ """ """
+ foo = Foo()
+ self.assertTrue(self._callFUT(foo))
+
+ def test_instance_defaultargs_firstname_not_request(self):
+ class Foo:
+ def __call__(self, context, request=None):
+ """ """
+ foo = Foo()
+ self.assertFalse(self._callFUT(foo))
+
+ def test_instance_defaultargs_firstname_request(self):
+ class Foo:
+ def __call__(self, request, foo=1, bar=2):
+ """ """
+ foo = Foo()
+ self.assertTrue(self._callFUT(foo), True)
+
class DummyModule:
__name__ = 'dummy'
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index eeb8dfc32..80f8f1f38 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -64,6 +64,8 @@ def view(
else:
request_type = _context.resolve(request_type)
+ derived_view = view
+
if inspect.isclass(view):
# If the object we've located is a class, turn it into a
# function that operates like a Zope view (when it's invoked,
@@ -71,14 +73,29 @@ def view(
# position arguments, then immediately invoke the __call__
# method of the instance with no arguments; __call__ should
# return an IResponse).
- _view = view
- def _bfg_class_view(context, request):
- inst = _view(context, request)
- return inst()
- _bfg_class_view.__module__ = view.__module__
- _bfg_class_view.__name__ = view.__name__
- _bfg_class_view.__doc__ = view.__doc__
- view = _bfg_class_view
+ if requestonly(view):
+ def _bfg_class_requestonly_view(context, request):
+ inst = view(request)
+ return inst()
+ derived_view = _bfg_class_requestonly_view
+ else:
+ def _bfg_class_view(context, request):
+ inst = view(context, request)
+ return inst()
+ derived_view = _bfg_class_view
+
+ elif requestonly(view):
+ def _bfg_requestonly_view(context, request):
+ return view(request)
+ derived_view = _bfg_requestonly_view
+
+ if derived_view is not view:
+ derived_view.__module__ = view.__module__
+ derived_view.__doc__ = view.__doc__
+ try:
+ derived_view.__name__ = view.__name__
+ except AttributeError:
+ derived_view.__name__ = repr(view)
if permission:
pfactory = ViewPermissionFactory(permission)
@@ -95,7 +112,7 @@ def view(
discriminator = ('view', for_, name, request_type, IView),
callable = handler,
args = ('registerAdapter',
- view, (for_, request_type), IView, name, _context.info),
+ derived_view, (for_, request_type), IView, name, _context.info),
)
class IViewDirective(Interface):
@@ -338,3 +355,43 @@ class Uncacheable(object):
""" Include in discriminators of actions which are not cacheable;
this class only exists for backwards compatibility (<0.8.1)"""
+def requestonly(class_or_callable):
+ """ Return true of the class or callable accepts only a request argument,
+ as opposed to something that accepts context, request """
+ if inspect.isfunction(class_or_callable):
+ fn = class_or_callable
+ elif inspect.isclass(class_or_callable):
+ try:
+ fn = class_or_callable.__init__
+ except AttributeError:
+ return False
+ else:
+ try:
+ fn = class_or_callable.__call__
+ except AttributeError:
+ return False
+
+ try:
+ argspec = inspect.getargspec(fn)
+ except TypeError:
+ return False
+
+ args = argspec[0]
+ defaults = argspec[3]
+
+ if hasattr(fn, 'im_func'):
+ # it's an instance method
+ if not args:
+ return False
+ args = args[1:]
+ if not args:
+ return False
+
+ if len(args) == 1:
+ return True
+
+ elif args[0] == 'request':
+ if len(args) - len(defaults) == 1:
+ return True
+
+ return False