app: Add BaseApplication class

The `Application` class is now a sub-class of `BaseApplication`.
Applications that require more control of the framework's behavior can
extend `BaseApplication` instead of `Application`. The `BaseApplication`
class also provides some new features:

* `BaseApplication.setup_routes` is always called when an instance is
  created, and can be used to configure the request dispatcher
* `BaseApplication.update_config` updates the application configuration
  from a configuration file

The major difference between `BaseApplication` and `Application` is that
the latter requires a request dispatcher, while the former does not.
This pattern allows simple applications to construct a `Router` or root
object and then initialize the `Application` without needing to define a
sub-class.
master
Dustin 2016-07-10 19:42:39 -05:00
parent 4090df1286
commit 44a28fda68
1 changed files with 73 additions and 24 deletions

View File

@ -21,40 +21,33 @@ Please give me a docstring!
:Updater: $Author$ :Updater: $Author$
''' '''
from milla import util
from milla.controllers import FaviconController from milla.controllers import FaviconController
from milla.util import asbool
from webob.exc import HTTPNotFound, WSGIHTTPException, HTTPMethodNotAllowed from webob.exc import HTTPNotFound, WSGIHTTPException, HTTPMethodNotAllowed
import milla.dispatch.traversal import milla.dispatch.traversal
import os
import sys import sys
__all__ = ['Application'] __all__ = [
'Application',
'BaseApplication',
]
class Application(object): class BaseApplication(object):
'''Represents a Milla web application '''Base class for Milla applications
Constructing an ``Application`` instance needs a dispatcher, or This class can be used by applications that need to customize the
alternatively, a root object that will be passed to a new behavior of the framework. In most cases, :py:class:`Application`
:py:class:`milla.dispatch.traversal.Traverser`. instances can be created directly and a sublcass is not necessary.
:param obj: An object implementing the dispatcher protocol, or an
object to be used as the root for a Traverser
``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, obj): DEFAULT_CONFIG = {}
if not hasattr(obj, 'resolve'):
# Object is not a dispatcher, but the root object for traversal def __init__(self):
obj = milla.dispatch.traversal.Traverser(obj) self.config = self.DEFAULT_CONFIG.copy()
self.dispatcher = obj self.setup_routes()
self.config = {'milla.favicon': True}
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
start_response = StartResponseWrapper(start_response) start_response = StartResponseWrapper(start_response)
@ -127,6 +120,32 @@ class Application(object):
return self._find_attr(obj.func, attr) return self._find_attr(obj.func, attr)
raise raise
def update_config(self, filename):
'''Update application configuration from a file
:param filename: Path to configuration file
This method will update the application configuration using
the values found in the specified configuration file. If the
specified file does not exist or is not accessible, no
changes will be made.
The configuration file will be read using
:py:func:`milla.util.read_config`.
'''
if filename and os.access(filename, os.R_OK):
self.config.update(util.read_config(filename))
def setup_routes(self):
'''Initialize the request dispatcher
The default implementation of this method does nothing, but it
is called when an instance of :py:class:`BaseApplication` is
created, and can be used by subclasses to set up the request
dispatcher.
'''
def make_request(self, environ): def make_request(self, environ):
'''Create a :py:class:`~milla.Request` from a WSGI environment '''Create a :py:class:`~milla.Request` from a WSGI environment
@ -162,7 +181,7 @@ class Application(object):
return self.dispatcher.resolve(path_info) return self.dispatcher.resolve(path_info)
except milla.dispatch.UnresolvedPath: except milla.dispatch.UnresolvedPath:
if (path_info == '/favicon.ico' and if (path_info == '/favicon.ico' and
asbool(self.config.get('milla.favicon'))): util.asbool(self.config.get('milla.favicon'))):
return FaviconController() return FaviconController()
else: else:
return path_not_found return path_not_found
@ -182,6 +201,36 @@ class Application(object):
raise raise
class Application(BaseApplication):
'''Represents a Milla web application
Constructing an ``Application`` instance needs a dispatcher, or
alternatively, a root object that will be passed to a new
:py:class:`milla.dispatch.traversal.Traverser`.
:param obj: An object implementing the dispatcher protocol, or an
object to be used as the root for a Traverser
``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``.
'''
DEFAULT_CONFIG = {
'milla.favicon': True,
}
def __init__(self, obj):
super(Application, self).__init__()
if not hasattr(obj, 'resolve'):
# Object is not a dispatcher, but the root object for traversal
obj = milla.dispatch.traversal.Traverser(obj)
self.dispatcher = obj
class StartResponseWrapper(): class StartResponseWrapper():
def __init__(self, start_response): def __init__(self, start_response):