diff --git a/README.md b/README.md index 91cdbafc..f80bdb0e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -

-

Starlette

-

+

+ Starlette +

✨ The little ASGI library that shines. ✨

@@ -20,7 +20,7 @@ Starlette is a small library for working with [ASGI](https://asgi.readthedocs.io/en/latest/). -It gives you `Request` and `Response` classes, a test client, and a +It gives you `Request` and `Response` classes, routing, a test client, and a decorator for writing super-minimal applications. **Requirements:** @@ -221,6 +221,48 @@ raise an error. --- +## Routing + +Starlette includes a `Router` class which is an ASGI application that +dispatches to other ASGI applications. + +```python +from starlette import Router, Path, PathPrefix +from myproject import Homepage, StaticFiles + + +app = Router([ + Path('/', app=Homepage, methods=['GET']), + PathPrefix('/static', app=StaticFiles, methods=['GET']) +]) +``` + +Paths can use URI templating style to capture path components. + +```python +Path('/users/{username}', app=User, methods=['GET']) +``` + +Path components are made available in the scope, as `scope["kwargs"]`. + +Because each target of the router is an ASGI instance itself, routers +allow for easy composition. For example: + +```python +app = Router([ + Path('/', app=Homepage, methods=['GET']), + PathPrefix('/users', app=Router([ + Path('/', app=Users, methods=['GET', 'POST']), + Path('/{username}', app=User, methods=['GET']), + ])) +]) +``` + +The router will respond with "404 Not found" or "406 Method not allowed" +responses for requests which do not match. + +--- + ## Test Client The test client allows you to make requests against your ASGI application, @@ -264,4 +306,4 @@ async def app(request): --- -

API Star is BSD licensed code.
Designed & built in Brighton, England.

— ⭐️ —

+

Starlette is BSD licensed code.
Designed & built in Brighton, England.

— ⭐️ —

diff --git a/starlette/__init__.py b/starlette/__init__.py index f6104abf..4978c00d 100644 --- a/starlette/__init__.py +++ b/starlette/__init__.py @@ -9,9 +9,12 @@ __all__ = ( "asgi_application", "HTMLResponse", "JSONResponse", + "Path", + "PathPrefix", "Response", + "Router", "StreamingResponse", "Request", "TestClient", ) -__version__ = "0.1.2" +__version__ = "0.1.3" diff --git a/starlette/routing.py b/starlette/routing.py index 31694b60..4c47f353 100644 --- a/starlette/routing.py +++ b/starlette/routing.py @@ -20,7 +20,7 @@ class Path(Route): self.app = app self.methods = methods regex = "^" + path + "$" - regex = re.sub("{([a-zA-Z_][a-zA-Z0-9_]*)}", r"(?P<\1>[^/]*)", regex) + regex = re.sub("{([a-zA-Z_][a-zA-Z0-9_]*)}", r"(?P<\1>[^/]+)", regex) self.path_regex = re.compile(regex) def matches(self, scope: Scope) -> typing.Tuple[bool, Scope]: diff --git a/tests/test_routing.py b/tests/test_routing.py index c44b25b8..2a30c814 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -14,12 +14,19 @@ def user(scope): return Response(content, media_type="text/plain") +def staticfiles(scope): + return Response("xxxxx", media_type="image/png") + + app = Router( [ - Path("/", app=homepage), + Path("/", app=homepage, methods=['GET']), PathPrefix( "/users", app=Router([Path("", app=users), Path("/{username}", app=user)]) ), + PathPrefix( + "/static", app=staticfiles, methods=['GET'] + ), ] ) @@ -31,6 +38,10 @@ def test_router(): assert response.status_code == 200 assert response.text == "Hello, world" + response = client.post("/") + assert response.status_code == 406 + assert response.text == "Method not allowed" + response = client.get("/foo") assert response.status_code == 404 assert response.text == "Not found" @@ -42,3 +53,11 @@ def test_router(): response = client.get("/users/tomchristie") assert response.status_code == 200 assert response.text == "User tomchristie" + + response = client.get("/static/123") + assert response.status_code == 200 + assert response.text == "xxxxx" + + response = client.post("/static/123") + assert response.status_code == 406 + assert response.text == "Method not allowed"