def autoroutes_add_routes(routes): router = Routes() for url, controller, methods, method_dict in routes: router.add(url, methods=method_dict) return router
from timeit import timeit from autoroutes import Routes PATHS = [ 'user/', 'user/{id}', 'user/{id}/subpath', 'user/{id}/subpath2', 'boat/', 'boat/{id}', 'boat/{id}/subpath', 'boat/{id}/subpath2', 'horse/', 'horse/{id}', 'horse/{id}/subpath', 'horse/{id}/subpath2', 'bicycle/', 'bicycle/{id}', 'bicycle/{id}/subpath2', 'bicycle/{id}/subpath' ] routes = Routes() for i, path in enumerate(PATHS): routes.add(path, GET=i) node = routes.match('user/') print('user/', node) node = routes.match('horse/22/subpath') print('horse/22/subpath', node) node = routes.match('plane/') print(node) total = timeit("routes.match('user/')", globals=globals(), number=100000) print(f'First flat path:\n> {total}') total = timeit("routes.match('horse/22/subpath')", globals=globals(), number=100000) print(f'Middle path with placeholder:\n> {total}') total = timeit("routes.match('plane/')", globals=globals(), number=100000) print(f'Not found path:\n> {total}')
# Named profile_.py not to clash with cProfile doing bad absolute imports. # Add "# cython: profile=True, linetrace=True, binding=True" directive in # autoroutes.pyx to profile, remove autoroutes.c and autoroutes.xxx.so from cProfile import runctx import pstats import pyximport pyximport.install() from autoroutes import Routes # noqa (we need pyximport.install) PATHS = [ '/user/', '/user/{id}', '/user/{id}/subpath', '/user/{id}/subpath2', '/boat/', '/boat/{id}', '/boat/{id}/subpath', '/boat/{id}/subpath2', '/horse/', '/horse/{id}', '/horse/{id}/subpath', '/horse/{id}/subpath2', '/bicycle/', '/bicycle/{id}', '/bicycle/{id}/subpath2', '/bicycle/{id}/subpath' ] routes = Routes() for path in PATHS: routes.add(path, data='data') runctx('for i in range(100000):\n routes.match("/horse/22/subpath")', globals(), locals(), 'Profile.prof') s = pstats.Stats('Profile.prof') s.strip_dirs().sort_stats('time').print_stats()
class Trinket(Application, dict): __slots__ = ('hooks', 'routes', 'websockets', 'server') handle_request = request_handler def __init__(self): self.routes = Routes() self.websockets = set() self.hooks = defaultdict(list) async def lookup(self, request: Request): payload, params = self.routes.match(request.path) if not payload: raise HTTPError(HTTPStatus.NOT_FOUND, request.path) # Uppercased in order to only consider HTTP verbs. handler = payload.get(request.method.upper(), None) if handler is None: raise HTTPError(HTTPStatus.METHOD_NOT_ALLOWED) # We check if the route is for a websocket handler. # If it is, we make sure we were asked for an upgrade. if payload.get('websocket', False) and not request.upgrade: raise HTTPError(HTTPStatus.UPGRADE_REQUIRED, 'This is a websocket endpoint, please upgrade.') return handler, params @handler_events async def __call__(self, request: Request): handler, params = await self.lookup(request) return await handler(request, **params) def route(self, path: str, methods: list = None, **extras: dict): if methods is None: methods = ['GET'] def wrapper(func): payload = {method: func for method in methods} payload.update(extras) self.routes.add(path, **payload) return func return wrapper def websocket(self, path: str, **extras: dict): def wrapper(func): @wraps(func) async def websocket_handler(request, **params): websocket = Websocket(request.socket) try: await websocket.upgrade(request) self.websockets.add(websocket) task = await spawn(func, request, websocket, **params) await websocket.flow(task) finally: self.websockets.discard(websocket) payload = {'GET': websocket_handler, 'websocket': True} payload.update(extras) self.routes.add(path, **payload) return func return wrapper def listen(self, name: str): def wrapper(func): self.hooks[name].append(func) return wrapper async def notify(self, name: str, *args, **kwargs): if name in self.hooks: for hook in self.hooks[name]: result = await hook(*args, **kwargs) if result is not None: # Allows to shortcut the chain. return result return None def start(self, host='127.0.0.1', port=5000, debug=True): Server.start(self, host, port, debug)