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"