From 903e1b2fc5ca09f22912d3fda2fdd212f19d633d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 25 Jun 2018 22:23:40 +0100 Subject: [PATCH] Add Router, Path, PathPrefix --- starlette/__init__.py | 1 + starlette/routing.py | 64 +++++++++++++++++++++++++++++++++++++++++++ tests/test_routing.py | 43 +++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 starlette/routing.py create mode 100644 tests/test_routing.py diff --git a/starlette/__init__.py b/starlette/__init__.py index 9f44a047..f6104abf 100644 --- a/starlette/__init__.py +++ b/starlette/__init__.py @@ -1,6 +1,7 @@ from starlette.decorators import asgi_application from starlette.response import HTMLResponse, JSONResponse, Response, StreamingResponse from starlette.request import Request +from starlette.routing import Path, PathPrefix, Router from starlette.testclient import TestClient diff --git a/starlette/routing.py b/starlette/routing.py new file mode 100644 index 00000000..7a563579 --- /dev/null +++ b/starlette/routing.py @@ -0,0 +1,64 @@ +from starlette import Response +import re + + +class Path: + def __init__(self, path, app): + self.path = path + self.app = app + regex = '^' + path + '$' + regex = re.sub('{([a-zA-Z_][a-zA-Z0-9_]*)}', r'(?P<\1>[^/]*)', regex) + self.path_regex = re.compile(regex) + + def matches(self, scope): + match = self.path_regex.match(scope['path']) + if match: + kwargs = dict(scope.get('kwargs', {})) + kwargs.update(match.groupdict()) + child_scope = scope.copy() + child_scope['kwargs'] = kwargs + return True, child_scope + return False, {} + + def __call__(self, scope): + return self.app(scope) + + +class PathPrefix: + def __init__(self, path, app): + self.path = path + self.app = app + regex = '^' + path + regex = re.sub('{([a-zA-Z_][a-zA-Z0-9_]*)}', r'(?P<\1>[^/]*)', regex) + self.path_regex = re.compile(regex) + + def matches(self, scope): + match = self.path_regex.match(scope['path']) + if match: + kwargs = dict(scope.get('kwargs', {})) + kwargs.update(match.groupdict()) + child_scope = scope.copy() + child_scope['kwargs'] = kwargs + child_scope['root_path'] = scope.get('root_path', '') + match.string + child_scope['path'] = scope['path'][match.span()[1]:] + return True, child_scope + return False, {} + + def __call__(self, scope): + return self.app(scope) + + +class Router: + def __init__(self, routes, default=None): + self.routes = routes + self.default = self.not_found if default is None else default + + def __call__(self, scope): + for route in self.routes: + matched, child_scope = route.matches(scope) + if matched: + return route(child_scope) + return self.not_found(scope) + + def not_found(self, scope): + return Response('Not found', 404, media_type='text/plain') diff --git a/tests/test_routing.py b/tests/test_routing.py new file mode 100644 index 00000000..22029435 --- /dev/null +++ b/tests/test_routing.py @@ -0,0 +1,43 @@ +from starlette import Response, Path, PathPrefix, Router, TestClient + + +def homepage(scope): + return Response('Hello, world', media_type='text/plain') + + +def users(scope): + return Response('All users', media_type='text/plain') + + +def user(scope): + content = 'User ' + scope['kwargs']['username'] + return Response(content, media_type='text/plain') + + +app = Router([ + Path('/', app=homepage), + PathPrefix('/users', app=Router([ + Path('', app=users), + Path('/{username}', app=user), + ])) +]) + + +def test_router(): + client = TestClient(app) + + response = client.get('/') + assert response.status_code == 200 + assert response.text == 'Hello, world' + + response = client.get('/foo') + assert response.status_code == 404 + assert response.text == 'Not found' + + response = client.get('/users') + assert response.status_code == 200 + assert response.text == 'All users' + + response = client.get('/users/tomchristie') + assert response.status_code == 200 + assert response.text == 'User tomchristie'