URL Routing in Python with the Routes Library
The Routes library provides a Python implementation inspired by the Rails routing system, enabling bidirectinoal mapping between URLs and application logic. It supports clean URL design for RESTful applications by decoupling URL patterns from their corresponding handlers.
In web development, defining how URLs map to code is fundamental. While direct mappings like /module/function → module.function are straightforward, they become unwieldy as applications grow. Routes abstracts this relationship, allowing flexible and maintainable URL configurations.
Install Routes via pip:
pip install Routes
Defining Routes with connect
The core of Routes is the Mapper class. Use its connect() method to register routes:
name: Optional identifier for reverse URL generation.routepath: The URL pattern (e.g.,/volumes).controller,action: Target handler components.conditions,requirements: Additional constraints (HTTP methods, regex validations).
Example:
from routes import Mapper
mapper = Mapper()
mapper.connect('list_volumes', '/volumes', controller='volume', action='list')
mapper.connect('view_image', '/images/{id}', controller='image', action='view')
for route in mapper.matchlist:
print(f"Route: {route.name} → {route.routepath}")
print(f"Handler: {route.defaults['controller']}.{route.defaults['action']}\n")
Dynamic segments like {id} capture values from URLs. Requirements restrict matches:
mapper.connect(
None,
'/download/{os}/{file}',
controller='files',
requirements={'os': r'linux|windows'}
)
Matching Requests
Use match(url) to resolve a path to its handler:
result = mapper.match('/volumes')
# Returns: {'controller': 'volume', 'action': 'list'}
Unmatched paths return None.
RESTful Resource Routing
For REST APIs, resource() auto-generates standard routes:
mapper.resource('volume', 'volumes')
This creates routes for:
GET /volumes→indexPOST /volumes→createGET /volumes/new→newGET /volumes/{id}→showPUT /volumes/{id}→updateDELETE /volumes/{id}→delete
Formatted variants (e.g., /volumes.json) are included automatically.
Integration with WSGI
Routes integrates into WSGI apps via RoutesMiddleware, which annotates the environment with routing data:
from routes.middleware import RoutesMiddleware
app = RoutesMiddleware(your_wsgi_app, mapper)
The middleware sets:
environ['wsgiorg.routing_args']:(url_generator, match_dict)environ['routes.route']: Matched route object
A minimal framework leveraging this:
import webob.dec
import webob.exc
import routes.middleware
class Router:
def __init__(self, mapper):
self.middleware = routes.middleware.RoutesMiddleware(self.dispatch, mapper)
@webob.dec.wsgify
def __call__(self, req):
return self.middleware
@staticmethod
@webob.dec.wsgify
def dispatch(req):
match = req.environ['wsgiorg.routing_args'][1]
if not match:
raise webob.exc.HTTPNotFound()
handler = match.pop('controller')
return handler(req, **match)
class Resource:
def __init__(self, controller):
self.controller = controller
@webob.dec.wsgify
def __call__(self, req):
action = req.environ['wsgiorg.routing_args'][1]['action']
method = getattr(self.controller, action)
return method(req)
Usage:
# Controller
class VolumeAPI:
def list(self, req):
return [{'id': 1, 'name': 'data-disk'}]
# Routing setup
mapper = routes.Mapper()
mapper.resource('volume', 'volumes', controller=Resource(VolumeAPI()))
app = Router(mapper)
Running this with a WSGI server serves GET /volumes as JSON.