routr – lightweight request routing for WSGI

Routr provides a set of tools to map WSGI (WebOb by default) request to an artbitrary Python object. It was designed with the following points in mind:

  • Declarativeness – configuration process is designed to be declarative. That means routes are readable and easy to understand and follow. Routr also provides a way to automatically generate documentation from routes (via Sphinx extension).
  • Extensibility – routing mechanism designed to be extremely extendable, you can define guards for your routes, provide annotations to them or even replace parts of routing mechanism by your own implementations.
  • Composability – routes defined with routr are composable – you can mix and match them to compose more sofisticated routing structures.
  • Non-intrusiveness – there are no “frameworkish” things, just a mechanism to map request to Python object. How to setup request processing is completely up to you.

Basic usage

I’ll just give you an example WSGI application with no explanations – code is better than words here:

from routr import route, POST, GET
from myapp.views import list_news, get_news, create_news
from myapp.views import get_comments, create_comment

routes = route("news",
    route(GET,  "/",                  list_news),
    route(POST, "/",                  create_news),
    route(GET,  "/{id:int}/",         get_news),
    route(GET,  "/{id:int}/comments", get_comments),
    route(POST, "/{id:int}/comments", create_comment),
    )

You use routr.route() function to define your routes, then you can dispatch request against them:

from routes.exc import NoMatchFound
from webob import Request, exc

def application(environ, start_response):
    request = Request(environ)
    try:
        trace = routes(request)
        view = trace.target
        args, kwargs = trace.args, trace.kwargs
        response = view(*args, **kwargs)
    except NoMatchFound, e:
        response = e.response
    except exc.HTTPException, e:
        response = e
    return response(environ, start_response)

Note that neither of these are not dictating you how to build your application – you’re completely free about how to structure and organize your application’s code.

Defining routes

As we’ve already seen routes are defined via routr.route() function which can be called in two ways, the first one is for defining endpoint routes:

route([method] [, pattern] [, *guards] [, target], name=None, **anotations)

As you can see method and pattern are optional here, by default method will be set to GET and pattern to /. Argument target is an arbitrary object you want to associate endpoint route with, usually it will be a view callable or string which specifies one.

Second form is for grouping routes together:

route([method] [, pattern] [, *guards] [, *routes], name=None, **anotations)

There is *routes argument which can contain one or more route definitions via same routr.route() so you can nest routes one inside others.

URL patterns

URL pattern is a string which can contain placeholders for parameters. Parameters are captured into trace.args tuple in a corresponding order.

Parameters are typed to strings by default, but you can also annotate them to be integers or enumerations or path segments:

  • /news/{year:int}-{month:int}-{day:int}/ will match against /news/2001-12-12/ and trace.args will contain tuple (2001, 12, 12).
  • /show/{type:any(news, comments)} will match against /show/news or /show/comments
  • /wiki/{page:path} will match against /wiki/category/page/comments, note that type path allows / to be captured while str (which is also default type) doesn’t do that

Trace object

The result of matching routes is a routr.Trace object:

class routr.Trace(args, kwargs, routes, payload=None)

Result of route matching

Represents a trace of matched routes towards the last one called endpoint.

Attr args:collected positional arguments
Attr kwargs:collected keyword arguments
Attr routes:a list of matched routes with the endpoint route being the last one
Attr endpoint:matched endpoint route

Reversing routes

To allow route reversal you must provide name keyword argument to routr.route() directive:

routes = route("/page/{id:int}", myview, name="myview")

Then you are allowed to call reverse(name, *args, **kwargs) method on routes:

routes.reverse("myview", 43, q=12) # produces "/page/43?q=12"

Matching query string

You can match against query string parameters with routr.schema module which exposes routr.schema.QueryParams (or its qs alias) guard:

from routr import route
from routr.schema import qs, opt, Int, String

routes = route("/", myview,
    guards=[qs(query=String, page=opt(Int))])

Class routr.schema.QueryParams represents a guard which processes request’s query string and validates it against predefined schema.

Writing arbitrary tests for routes – guards

Route guards are callables which take request as its single argument and return a dict of params which then collected and accumulated by routr.Trace object or raise a webob.exc.HTTPException.

Annotations

You can annotate any route with arbitrary objects, routr.route() accepts **kwargs arguments and all of those (except name and guards which have special meaning) will be passed to routr.Route constructor so you can access it via routr.Trace object after matching:

...
routes = route("/", myview, middleware=[mymiddelware])
...
trace = routes(request)
trace.endpoint.annotations["middleware"]
...

Note that routr.Trace objects also provide access for parent routes of endpoint route via Trace.routes attribute so you can accumulate annotations along the matched path. This, for example, can be useful for implementing middleware system like Django does but this allows only fire some middleware on those routes which was annotated correspondingly.

Serving static assets with routr

Routr provides a shortcut for defining routes for serving static data, it uses webob.static under the hood. While this is not a production-ready solution (I recommend nginx for this) it can be quite useful during development for serving static assets such as HTML, CSS or javascript.

To define route you just need to use the routr.static.static() function:

from routr.static import static

routes = route(
  ...
  static('/static', 'static'),
  ...
  )

Note, that handler for these routes accepts only two arguments – request and path, so you need to distinguish it using static_view annotation from other target objects and handle call to it separately like this:

...
trace = routes(request)
if trace.endpoint.annotations.get('static_view'):
  return trace.target(request, *trace.args)
...

Generating documentation from routes

Let’s suppose we have following routes defined:

# examples.py

from routr import route, POST, GET

def api_list_news():
    """ API list news items"""

def api_create_news():
    """ API create news item"""

def api_get_news(id):
    """ API get news item by ``id``"""

routes = route(
    route("api",
        route(GET, "news", api_list_news),
        route(POST, "news", api_create_news),
        route(GET, "news/{int}", api_get_news)))

We want to generate documentation from these definitions. For that purpose there’s autoroutr directive which is built on top of sphinx-httpdomain:

.. autoroutr:: examples:routes

That gives us as a result:

GET /api/news

API list news items

POST /api/news

API create news item

GET /api/news/{int}

API get news item by id

Directive autoroutr also supports :include: and :exclude: options which allow to include or exclude routes using glob pattern syntax.

Reporting bugs and working on routr

Development takes place at GitHub, you can clone source code repository with the following command:

% git clone git://github.com/andreypopp/routr.git

In case submitting patch or GitHub pull request please ensure you have corresponding tests for your bugfix or new functionality.

API reference

Module routr contains routr.route() function which is used as a primary way to define routes:

routr.route(*args, **kwargs)

Directive for configuring routes in application

Parameters:
  • args – ([method,] [pattern,] target) produces endpoint route ([method,] [pattern,] *routes) produces route group
  • kwargs – name and guards are treated as name and guards for routes, other keyword args a are passed as annotations
routr.include(spec)

Include routes by spec

Parameters:spec – asset specification which points to Route instance
routr.plug(name)

Plug routes by setuptools entry points, identified by name

Parameters:name – entry point name to query routes
class routr.Trace(args, kwargs, routes, payload=None)

Result of route matching

Represents a trace of matched routes towards the last one called endpoint.

Attr args:collected positional arguments
Attr kwargs:collected keyword arguments
Attr routes:a list of matched routes with the endpoint route being the last one
Attr endpoint:matched endpoint route

For the complete reference, these are classes which are constructed by routr.route():

class routr.Route(guards, pattern, url_pattern_cls=None, **annotations)

Base class for routes

Parameters:
  • guards – a list of guards
  • pattern – pattern for URL pattern
  • url_pattern_cls – class which should be used for URL pattern matching (default to urlpattern.URLPattern
  • annotations – various annotations
match(path_info, request)

Match request against route

return:
trace object which accumulate *args and **kwargs
Return type:

Trace

Raises:
  • routr.exc.NoURLPatternMatched – if no route was matched by URL
  • routr.exc.RouteGuarded – if route was guarded by one or more guards
  • routr.exc.MethodNotAllowed – if method isn’t allowed for matched route
reverse(name, *args, **kwargs)

Reverse route with name using *args as pattern parameters and **kwargs as query string parameters

Raises routr.exc.RouteReversalError:
 if no reversal can be computed for given arguments
class routr.RouteGroup(routes, guards, pattern, url_pattern_cls=None, **annotations)

Route which represents a group of other routes

Can have its own guards and a URL pattern.

Additional to Route params are:

Parameters:routes – a list of Route objects
class routr.Endpoint(target, method, name, guards, pattern, **annotations)

Endpoint route

Associated with some object target which will be returned in case of successful match and a method which matches against request’s method.

Additional to Route params are:

Parameters:
  • target – object to associate with route
  • method – HTTP method associate with route
  • name – optional name, should be provided if reversal of this route is needed, otherwise None is allowed

Module routr.schema contains predefined routr.schema.QueryParams guard and helper utilites:

class routr.schema.QueryParams(**kwargs)
class routr.schema.Optional(typ, default=<object object at 0x400b0c0>)

Also routr.schema module re-exports colander package, so you can import any colander class or function right from there.

routr.static.static(prefix, directory)

Define a route which serves static assets

Parameters:
  • prefix – URL prefix on which to serve static assets
  • directory – directory from which to serve static assets