From f5c25ef97393f7b4bf1353b11eeb841c53e2feaf Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Jun 2009 21:49:33 +0000 Subject: - 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. --- repoze/bfg/zcml.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 9 deletions(-) (limited to 'repoze/bfg/zcml.py') 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 -- cgit v1.2.3