1import re 2from urllib.parse import unquote 3 4RE_URL = re.compile(r'^(?P<scheme>.*)://(?P<path>[^\?]*)(\?(?P<query>.*))?$', flags=re.IGNORECASE) 5 6 7def get_url_params(url): 8 params = re.search(RE_URL, url) 9 query = params.group('query') 10 if query: 11 pairs = list(map(lambda kv: kv.split('='), query.split('&'))) 12 query = {k: unquote(v) for k, v in pairs} 13 return { 14 'scheme': params.group('scheme'), 15 'path': params.group('path'), 16 'query': query or None 17 } 18 19 20class Router: 21 """ 22 Usage: 23 24 >>> rt = Router() 25 >>> 26 >>> class App: 27 >>> 28 >>> @rt.route('get/user') 29 >>> def get_user(self, url_params): 30 >>> ... 31 >>> return userObject 32 >>> 33 >>> def on_request(self, url): 34 >>> result = rt.dispatch(self, url) # will return userObject for /get/user path 35 """ 36 37 _callbacks = None 38 39 def __init__(self): 40 self._callbacks = {} 41 42 def dispatch(self, context, url): 43 url_params = get_url_params(url) 44 try: 45 callback = self._callbacks[url_params['path'].strip('/')] 46 except KeyError as e: 47 raise RouteNotFound('Route not found for path %s' % url_params['path']) from e 48 49 return callback(context, url_params) 50 51 def route(self, path): 52 if not path: 53 raise RoutePathEmpty() 54 55 def decorator(callback): 56 self._callbacks[path.strip('/')] = callback 57 return callback 58 59 return decorator 60 61 62class RouterError(RuntimeError): 63 pass 64 65 66class RoutePathEmpty(RouterError): 67 pass 68 69 70class RouteNotFound(RouterError): 71 pass 72