Pattern Matching based WSGI-enabled URL routing tool.
1 2 easy_install decoroute
Example:
1 2 import decoroute 3 4 app = decoroute.App() 5 6 def render_response(status = '200 OK', **kw): 7 # try your favorite templating engine here 8 return status, [('Content-Type', 'text/plain')], [str(kw)] 9 10 def redirect_to(env, endpoint, **kw): 11 return '302 FOUND', [('Content-Type', 'text/plain'), \ 12 ('Location', '%s://%s%s' % (env['wsgi.url_scheme'], env['HTTP_HOST'], \ 13 env['decoroute.app'].url_for(endpoint, **kw)))], [''] 14 15 @app.expose('/node', id = '1') 16 @app.expose('/node/<id:\d+>') 17 def node(env, id): 18 return render_response(id = id) 19 20 @app.expose('/url_for') 21 def url_for(env): 22 return render_response( \ 23 url = env['decoroute.app'].url_for(node, id = 666)) 24 25 @app.expose('/302') 26 def found(env): 27 return redirect_to(env, not_found) 28 29 @app.expose('/404') 30 def not_found(env): 31 raise decoroute.NotFound() 32 33 @app.not_found() 34 def not_found_handler(env): 35 return render_response('404 NF', url = env['PATH_INFO']) 36 37 from wsgiref.simple_server import make_server 38 39 make_server('', 5555, app).serve_forever()
Complete source < 100 lines of code:
1 2 #!/usr/bin/env python 3 # vim:ts=4:sw=4:et 4 # -*- coding: utf-8 -*- 5 # $Id: decoroute.py,v 6eb37aeee5cd 2008/06/09 07:38:26 vsevolod $ 6 # 7 # Pattern Matching based WSGI-enabled URL routing tool. 8 # Actual version on http://pypi.python.org/pypi/decoroute 9 # (C) 2008 by Vsevolod S. Balashov <vsevolod@balashov.name> 10 # under terms of GNU LGPL v2.1 http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt 11 12 __author__ = "Vsevolod Balashov" 13 __email__ = "vsevolod at balashov dot name" 14 15 import re 16 from sets import ImmutableSet 17 import wsgistraw 18 19 __all__ = ['NotFound', 'App'] 20 21 class NotFound(Exception): 22 pass 23 24 _pattern_re = re.compile(r''' 25 ([^<]+) # static rule data 26 (?:< 27 ([a-zA-Z][a-zA-Z0-9_]*) # variable name 28 \: 29 ([^>]+) # regexp constraint 30 >)? 31 ''', re.VERBOSE) 32 33 def pattern2regexp(pattern, f, s = lambda x: re.escape(x)): 34 def parser(): 35 for t in _pattern_re.findall(pattern): 36 yield s(t[0]) 37 if t[1] != '': 38 yield f(t[1], t[2]) 39 return parser() 40 41 make_url_for = lambda p: ''.join(pattern2regexp(p, lambda v, r: '%%(%s)s' % v, lambda s: s)) 42 make_variables = lambda p: filter(lambda x: x, pattern2regexp(p, lambda v, r: v, lambda s: None)) 43 make_pattern = lambda p: r''.join(pattern2regexp(p, lambda v, r: r'(?P<%s>%s)' % (v, r))) 44 make_selector_fragment = lambda p: r''.join(pattern2regexp(p, lambda v, r: r'(?:%s)' % r)) 45 make_selector = lambda i: re.compile(r'(^%s$)' % r'$)|(^'.join(map(make_selector_fragment, i))) 46 47 class UrlMap(object): 48 def __init__(self): 49 self._endpoints = {} 50 self._patterns = {} 51 self._pattern_selector = make_selector(self._patterns.iterkeys()) 52 53 def add(self, pattern, endpoint, **kw): 54 if self._patterns.has_key(pattern): 55 raise Exception('duplicate pattern', pattern) 56 self._endpoints[(endpoint, ImmutableSet(make_variables(pattern)))] = make_url_for(pattern) 57 self._patterns[pattern] = (re.compile(make_pattern(pattern)), endpoint, kw) 58 self._pattern_selector = make_selector(self._patterns.iterkeys()) 59 60 def route(self, url): 61 try: 62 p = self._patterns.values()[re.match(self._pattern_selector, url).lastindex - 1] 63 d = re.match(p[0], url).groupdict().copy() 64 d.update(p[2]) 65 return p[1], d 66 except: 67 raise NotFound('route not found', url) 68 69 def url_for(self, endpoint, **kw): 70 return self._endpoints[(endpoint, ImmutableSet(kw.keys()))] % kw 71 72 class App(object): 73 def __init__(self, key = 'decoroute.app'): 74 self._key = key 75 self._map = UrlMap() 76 self._not_found = lambda e: ('404 NOT FOUND', [("Content-Type", "text/plain")], ['']) 77 78 @wsgistraw.app 79 def __call__(self, env): 80 try: 81 env[self._key] = self 82 endpoint, kw = self._map.route(env['PATH_INFO']) 83 return endpoint(env, **kw) 84 except NotFound: 85 return self._not_found(env) 86 87 def expose(self, pattern, **kw): 88 def decorate(f): 89 self._map.add(pattern, f, **kw) 90 return f 91 return decorate 92 93 def not_found(self): 94 def decorate(f): 95 self._not_found = f 96 return f 97 return decorate 98 99 def url_for(self, endpoint, **kw): 100 return self._map.url_for(endpoint, **kw)