pasteob.Autodispatch

4 views
Skip to first unread message

Sergey Schetinin

unread,
Nov 14, 2009, 5:35:26 AM11/14/09
to Paste Users
Ian's webob.dec is in trunk now, so as webob getting some helper
functions I thought something else might be useful there. Basically,
once one starts to use webapps implemented as decorated functions /
method the next issue that comes up is dispatching. I know people use
various routing libraries, whatever works for them, but I'll just
ignore that approach. I'm more interested in precise dispatching and
in limiting the complexity.

When mixing and matching independent WSGI apps paste.urlmap.URLMap
worked well for me for some time, but later I started preferring more
fine-grained control. I came up with a couple helpers quite some time
ago and finally open-sourced them earlier this year as PasteOb: see
PathMap, PathMatch, HostMap and override_https (
http://code.google.com/p/pasteob/source/browse/trunk/tests/PasteOb.txt
).

There's another use case that worked a little less elegantly: when
developing an app that consists of a number of related subapps mounted
at specific subpaths. URLMap / PasteOb still can handle the
dispatching of those just fine and I used to implement subapps as
methods of a class putting the dispatching logic in __call__. That
works great as an instance of such a class is a self-contained app.
The dispatching didn't take much space or effort, but obviously that
code always looks somewhat alike and it would be nice to get rid of it
or at least get it closer to the subapplications' implementations.
It's really easy to do if all you want is CherryPy-style dispatching,
but I didn't feel that was good enough.

So, having a lot of real cases of these custom dipatchers I tried to
generalize all of them without sacrificing any power or precision
(like distinction between an exact and a prefix match). After some
experimentation I came up with a set of dispatching rules (implemented
in a baseclass) that works great for me, handles corner-cases well and
is surprisingly powerful for what it is. I was using it for a couple
months but didn't bother to document it for others to use until today:

http://code.google.com/p/pasteob/source/browse/trunk/pasteob/__init__.py#98

Anyway, I believe there are more people than just me with similar
requirements / preferences / coding style / whatever, so I wanted to
share what I ended up with so it possibly would save them some time.
Something like that might end up in webob too, so my Autodispatch
class might serve as an example of how some people do this kind of
dispatching based on attribute names.

class Autodispatch(object):
"""
WSGI app that automatically dispatches requests to apps stored
as attributes. (Usually webob_wrapped methods).

For the following examples we assume that an instance of Autodispatch
subclass is mounted at '/app' path, that is, by the time request gets to
it, script_name == '/app' and the rest of the path in path_info.

We give examples for GET requests, but the same dispatch mechanisms are
applied for every other methods, so if a dispatch_GET is
mentioned, it would
be dispatch_POST if the req.method is 'POST' etc.

For the purposes of this docstring, the meaning of "available"
is "an attribute
that is present and is not None".

GET /app
If self.GET is available, a temporary redirect will be
issued to /app/

GET /app/
If self.GET is available it will be used to generate a response.

GET /app/foo
If self.GET_foo is available it will be used for response with
script_name == '/app/foo' and path_info == ''.

Otherwise, if self.ANY_foo is available, it will used with the
same script_name / path_info adjustments. This is useful if you
want some app to be mounted for a certain path and that app can
handle all HTTP methods.

If that is not available either, the dispatch_foo rule below
will be used.

There's an exception to this: if the request uses some method
other than GET and neither `METHOD`_foo nor ANY_foo is available,
but GET_foo is, the dispatch_foo will not be used. Instead, the
405 Method Not Allowed response will be generated with "Allow: GET"
header.

GET /app/foo/
GET /app/foo/bar
If self.dispatch_foo is available it will be used to generate the
response after adjusting script_name and path_info so that
script_name == '/app/foo'

Whenever no rule above matches, self.__default__ will be used
as a fallback
for the response. Unless it is overridden in a subclass, that results in
404 Not Found error.
"""
__default__ = None

def _get_app(self, *args):
return getattr(self, '_'.join(args), None)


@webob_wrap
def __call__(self, req):
seg = req.path_info_peek()
if seg:
app = None
if req.path_info == '/' + seg:
method_app = self._get_app(req.method, seg)
if method_app is not None:
app = method_app
else:
match_app = self._get_app('ANY', seg)
if match_app is not None:
app = match_app
elif self._get_app('GET', seg) is not None:
app = HTTPMethodNotAllowed(allow=['GET']) #@@
query for all methods
if app is None:
app = self._get_app('dispatch', seg)
if app is not None:
req.path_info_pop()
return app
else:
app = self._get_app(req.method)
if app is not None:
if req.path_info == '':
uri_as_dir = Uri(req.url).as_dir(True)
app = HTTPTemporaryRedirect(location=str(uri_as_dir))
return app
return self.__default__

--
Best Regards,
Sergey Schetinin

http://s3bk.com/ -- S3 Backup
http://word-to-html.com/ -- Word to HTML Converter

Reply all
Reply to author
Forward
0 new messages