For some applications, the HTTP method emulation for POST requests is
undesirable, particularly when the request contains a large payload
(e.g. file uploads, etc.). For these applications, this feature can be
disabled by setting the `post_method_emulation` attribute of their
application objects to `False`.
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.
The `HTTPVerbController` base class can be used by controllers to delegate
request handling to different instance methods based on the HTTP request
method.
This commit breaks up the `Application.__call__` method into smaller methods
that can be overridden by subclasses. These methods allow customization of
various steps of the request/response handling process:
* `make_request`: Create the `Request` object from the WSGI environment
dictionary. The default implementation creates a `milla.Request` object,
copies the application configuration to its `config` attribute, and handles
"emulated" HTTP methods from POST data.
* `resolve_path`: Locates a controller callable from the given path info. The
default implementation calls the `resolve` method on the application's
`dispatcher` attribute. If `UnresolvePath` is raised, it returns a callable
that raises `HTTPNotFound`.
* `handle_error`: Called inside the exception handler when a controller
callable raises an exception. The method should return a callable WSGI
application (such as a `Response` or `WSGIHTTPException` object). To access
the exception that was raised, use the `sys.exc_info` function. The default
implementation returns the exception if it is an instance of
`WSGIHTTPException`, or re-raises the exception otherwise. This allows
middleware applications to handle the exception, if desired.
If the `pkg_resources` module is not available, the `FaviconController` will
fall back to finding the default image in the same directory on the filesystem
as the `milla` package.
In addition to setuptools entry point names, the authentication subsystem now
accepts Python classes directly as the value of the `milla.request_validator`
configuration setting. This removes the dependency on setuptools for
authentication, and allows more flexibility in application configuration.
Using the `milla.app.Application._find_attr` method to find the
`allowed_methods` of returned controller callables allows the attribute to be
defined e.g. on the class for instance methods, etc.
Some WSGI servers, e.g. Werkzeug, unconditionally attempt to iterate over the
application response, even for HEAD requests. If `None` is returned, then the
server will crash in this case, because it is not iterable. This commit alters
the behavior of `milla.Application` to return an empty string, which is
iterable and has the same effect of not sending a body.
When the application path info is empty (e.g. the WSGI script is mounted
somewhere other than the root), appending a trailing slash and redirecting
causes the new location to be calculated incorrectly. This is because the part
of the request URL before the application path is not taken into account, so
the new path always ends up being a literal `/`. This commit changes the
behavior of the `redir` function that is returned by
`milla.dispatch.routing.Router.resolve` to calculate the new path info
correctly in the redirect response.
If a controller callable returns a string, it needs to be wrapped in a
Response object. To determine if this is the case, the Application tests to
see if the returned object is an instance of `basestring`. Since `basestring`
doesn't exist in Python 3, only `str` is a valid return type.
Unfortunately, my way of testing whether the `basestring` type existed was
flawed. Instead of raising `NameError` when it doesn't exist,
`UnboundLocalError` (a subclass `NameError`) is *always* raised. Since the
exception handler sets `basestring` equal to `str` assuming this is Python 3,
most of the time this isn't a problem. If, however, the controller returns a
`unicode` object in Python 2, the `isinstance` call returns `False`, so the
response is not wrapped in a Response object.
Rather than try to reassign the `basestring` name, now we just use `_string`,
which will either be `basestring` (in Python 2) or `str` (in Python 3).
Apparently, the unit tests didn't cover this case...