From 9b30000a36d075068de50ea5e5787812dbd160a5 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sun, 27 Mar 2011 14:54:42 -0500 Subject: [PATCH] Support for `__before__` and `__after__` method calls for controllers For convenience, a `Controller` class now exists from which all classes wanting to implement these methods should descend. This will allow sane cooperative multiple inheritance. --- src/milla/app.py | 78 ++++++++++++++++++++++++++++++++-------- src/milla/controllers.py | 23 ++++++++++++ 2 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 src/milla/controllers.py diff --git a/src/milla/app.py b/src/milla/app.py index 409dd46..30a575a 100644 --- a/src/milla/app.py +++ b/src/milla/app.py @@ -7,10 +7,12 @@ Please give me a docstring! :Updated: $Date$ :Updater: $Author$ ''' -import milla.dispatch.traversal from webob.exc import HTTPNotFound, WSGIHTTPException +import milla.dispatch import webob +__all__ = ['Application'] + class Application(object): '''Represents a Milla web application @@ -22,33 +24,81 @@ class Application(object): automatically created if a root is given :param dispatcher: An object implementing the dispatcher protocol - ``Application`` instances are WSGI applications + ``Application`` instances are WSGI applications. + + .. py:attribute:: config + + A mapping of configuration settings. For each request, the + configuration is copied and assigned to ``request.config``. ''' - def __init__(self, root=None, dispatcher=None): - if not dispatcher: - if root: - self.dispatcher = milla.dispatch.traversal.Traverser(root) - else: - raise ValueError('Must specify either a root object or a ' - 'dispatcher') - else: - self.dispatcher = dispatcher + def __init__(self, dispatcher): + self.dispatcher = dispatcher + self.config = {} def __call__(self, environ, start_response): + path_info = environ['PATH_INFO'] try: - func = self.dispatcher.resolve(environ['PATH_INFO']) + func = self.dispatcher.resolve(path_info) except milla.dispatch.UnresolvedPath: return HTTPNotFound()(environ, start_response) request = webob.Request(environ) + request.config = self.config.copy() + + start_response_wrapper = StartResponseWrapper(start_response) + request.start_response = start_response_wrapper try: + # If the callable has an __before__ attribute, call it + if hasattr(func, '__before__'): + func.__before__(request) + # If the callable is an instance method and its class has + # a __before__ method, call that + elif hasattr(func, 'im_self') and \ + hasattr(func.im_self, '__before__'): + func.im_self.__before__(request) + # The callable might be a partial, so check the inner func + elif hasattr(func, 'func'): + if hasattr(func.func, '__before__'): + func.func.__before__(request) + elif hasattr(func.func, 'im_self') and \ + hasattr(func.func.im_self, '__before__'): + func.func.im_self.__before__(request) response = func(request) except WSGIHTTPException as e: return e(environ, start_response) + finally: + # If the callable has an __after__ method, call it + if hasattr(func, '__after__'): + func.__after__(request) + # If the callable is an instance method and its class has + # an __after__ method, call that + elif hasattr(func, 'im_self') and \ + hasattr(func.im_self, '__after__'): + func.im_self.__after__(request) + # The callable might be a partial, so check the inner func + elif hasattr(func, 'func'): + if hasattr(func.func, '__after__'): + func.func.__after__(request) + elif hasattr(func.func, 'im_self') and \ + hasattr(func.func.im_self, '__after__'): + func.func.im_self.__after__(request) - if isinstance(response, basestring): + # The callable might have returned just a string, which is OK, + # but we need to wrap it in a WebOb response + if isinstance(response, basestring) or not response: response = webob.Response(response) - start_response(response.status, response.headerlist) + if not start_response_wrapper.called: + start_response(response.status, response.headerlist) return response.app_iter + +class StartResponseWrapper(): + + def __init__(self, start_response): + self.start_response = start_response + self.called = False + + def __call__(self, *args, **kwargs): + self.called = True + return self.start_response(*args, **kwargs) \ No newline at end of file diff --git a/src/milla/controllers.py b/src/milla/controllers.py new file mode 100644 index 0000000..796233d --- /dev/null +++ b/src/milla/controllers.py @@ -0,0 +1,23 @@ +'''Stub controller classes + +These classes can be used as base classes for controllers. While any +callable can technically be a controller, using a class that inherits +from one or more of these classes can make things significantly easier. + +:Created: Mar 27, 2011 +:Author: dustin +:Updated: $Date$ +:Updater: $Author$ +''' +class Controller(object): + '''The base controller class + + This class simply provides empty ``__before__`` and ``__after__`` + methods to facilitate cooperative multiple inheritance. + ''' + + def __before__(self, request): + pass + + def __after__(self, request): + pass