Skip to content

Commit

Permalink
Add support for mount routes (#694)
Browse files Browse the repository at this point in the history
* cleanup

* refactor

* simplified design

* fix ci

* finished taversal

* simplified implementation

* added docs

* Update starlite/asgi/routing_trie/validate.py

Co-authored-by: provinzkraut <[email protected]>

* Update starlite/router.py

Co-authored-by: provinzkraut <[email protected]>

* Update starlite/router.py

Co-authored-by: provinzkraut <[email protected]>

* addressed revoew comments

* 1.35.0

Co-authored-by: provinzkraut <[email protected]>
  • Loading branch information
Goldziher and provinzkraut authored Oct 29, 2022
1 parent c308fea commit de3bb1d
Show file tree
Hide file tree
Showing 45 changed files with 1,117 additions and 824 deletions.
30 changes: 22 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# Changelog

[1.35.0]

- add context-manager when using SQLAlchemy sessions.
- add support for mounting ASGI applications.
- fix `SQLAlchemyPlugin.to_dict()` where instance has relationship raising an exception.
- update route registration to ensure unique handlers.
- update routing logic to use a cleaner architecture.
- update sessions to support explicitly setting to `Empty`.
- update test client to run session creation in the client's portal.

[1.34.0]

- Add support for server-side sessions
- Fix an issue where header values would be forced to lower case
- Add a `__test__ = False` attribute to the `TestClient` so it won't get collected by pytest
together with an async test
- add a `__test__ = False` attribute to the `TestClient` so it won't get collected by pytest together with an async
test.
- add support for server-side sessions.
- fix an issue where header values would be forced to lower case.

[1.33.0]

Expand Down Expand Up @@ -102,7 +112,8 @@

- add `**kwargs` support to route handlers.
- breaking: remove `create_test_request`.
- breaking: update Starlette to version `0.21.0`. This version changes the TestClient to use `httpx` instead of `requests`, which is a breaking change.
- breaking: update Starlette to version `0.21.0`. This version changes the TestClient to use `httpx` instead
of `requests`, which is a breaking change.
- fix add default empty session to `RequestFactory`.

[1.21.2]
Expand All @@ -124,7 +135,8 @@

[1.20.0]

- update ASGI typings (`scope`, `receive`, `send`, `message` and `ASGIApp`) to use strong types derived from [asgiref](https://github.com/django/asgiref).
- update ASGI typings (`scope`, `receive`, `send`, `message` and `ASGIApp`) to use strong types derived
from [asgiref](https://github.com/django/asgiref).
- update `SessionMiddleware` to use custom serializer used on request.
- update `openapi-pydantic-schema` to `v1.3.0` adding support for `__schema_name__`.

Expand Down Expand Up @@ -252,7 +264,8 @@
[1.8.0]

- add [Stoplights Elements](https://stoplight.io/open-source/elements) OpenAPI support @aedify-swi
- breaking replace [openapi-pydantic-schema](https://github.com/kuimono/openapi-schema-pydantic) with [pydantic-openapi-schema](https://github.com/starlite-api/pydantic-openapi-schema).
- breaking replace [openapi-pydantic-schema](https://github.com/kuimono/openapi-schema-pydantic)
with [pydantic-openapi-schema](https://github.com/starlite-api/pydantic-openapi-schema).

[1.7.3]

Expand Down Expand Up @@ -434,7 +447,8 @@

- add template support @ashwinvin.
- update `starlite.request` by renaming it to `starlite.connection`.
- update the kwarg parsing and data injection logic to compute required kwargs for each route handler during application bootstrap.
- update the kwarg parsing and data injection logic to compute required kwargs for each route handler during application
bootstrap.
- update the redoc UI path from `/schema/redoc` to `/schema` @yudjinn.

[0.7.2]
Expand Down
23 changes: 23 additions & 0 deletions docs/usage/1-routing/5-mounting-asgi-apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Mounting ASGI Apps

Starlite support "mounting" ASGI applications on sub paths, that is - specifying a handler function that will handle all
requests addressed to a given path.

```py title="Mounting an ASGI App"
--8<-- "examples/routing/mount_custom_app.py"
```

The handler function will receive all requests with a url that begins with `/some/sub-path`, e.g. `/some/sub-path` and
`/some/sub-path/abc` and `/some/sub-path/123/another/sub-path` etc.

!!! info Technical Details
If we were to send a request to the above with the url `/some/sub-path/abc`, the handler will be invoked and
the value of `scope["path"]` will equal `/`. If we send a request to `/some/sub-path/abc`, it will also be invoked,
and `scope["path"]` will equal `/abc`.

Mounting is especially useful when you need to combine components of other ASGI applications - for example, for 3rd part libraries.
The following example is identical in principle to the one above but it uses `Starlette`:

```py title="Mounting a Starlette App"
--8<-- "examples/routing/mounting_starlette_app.py"
```
File renamed without changes.
24 changes: 24 additions & 0 deletions examples/routing/mount_custom_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import TYPE_CHECKING

from starlite import Response, Starlite, asgi

if TYPE_CHECKING:
from starlite.types import Receive, Scope, Send


@asgi("/some/sub-path", is_mount=True)
async def my_asgi_app(scope: "Scope", receive: "Receive", send: "Send") -> None:
"""
Args:
scope: The ASGI connection scope.
receive: The ASGI receive function.
send: The ASGI send function.
Returns:
None
"""
response = Response(content={"forwarded_path": scope["path"]})
await response(scope, receive, send)


app = Starlite(route_handlers=[my_asgi_app])
30 changes: 30 additions & 0 deletions examples/routing/mounting_starlette_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import TYPE_CHECKING

from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

from starlite import Starlite, asgi

if TYPE_CHECKING:
from starlette.requests import Request


async def index(request: "Request") -> JSONResponse:
"""A generic starlette handler."""
return JSONResponse({"forwarded_path": request.url.path})


starlette_app = asgi(path="/some/sub-path", is_mount=True)(
Starlette(
debug=True,
routes=[
Route("/", index),
Route("/abc", index),
Route("/123/another/sub-path", index),
],
)
)


app = Starlite(route_handlers=[starlette_app])
32 changes: 32 additions & 0 deletions examples/tests/routing/test_mounting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import TYPE_CHECKING

import pytest

from examples.routing import mount_custom_app, mounting_starlette_app
from starlite.status_codes import HTTP_200_OK
from starlite.testing import TestClient

if TYPE_CHECKING:
from starlite import Starlite


@pytest.mark.parametrize(
"app",
(
mount_custom_app.app,
mounting_starlette_app.app,
),
)
def test_mounting_asgi_app_example(app: "Starlite") -> None:
with TestClient(app) as client:
response = client.get("/some/sub-path")
assert response.status_code == HTTP_200_OK
assert response.json() == {"forwarded_path": "/"}

response = client.get("/some/sub-path/abc")
assert response.status_code == HTTP_200_OK
assert response.json() == {"forwarded_path": "/abc"}

response = client.get("/some/sub-path/123/another/sub-path")
assert response.status_code == HTTP_200_OK
assert response.json() == {"forwarded_path": "/123/another/sub-path"}
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ nav:
- usage/1-routing/2-routers.md
- usage/1-routing/3-controllers.md
- usage/1-routing/4-registering-components-multiple-times.md
- usage/1-routing/5-mounting-asgi-apps.md
- Route Handlers:
- usage/2-route-handlers/0-route-handlers-concept.md
- usage/2-route-handlers/1-http-route-handlers.md
Expand Down
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ ignore_missing_imports = True

[mypy-mako.*]
ignore_missing_imports = True

[mypy-fakeredis.*]
ignore_missing_imports = True
42 changes: 11 additions & 31 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "starlite"
version = "1.34.0"
version = "1.35.0"
description = "Light-weight and flexible ASGI API Framework"
authors = ["Na'aman Hirschfeld <[email protected]>"]
maintainers = [
Expand Down Expand Up @@ -110,6 +110,7 @@ disable = [
"cyclic-import",
"duplicate-code",
"fixme",
"import-outside-toplevel",
"line-too-long",
"missing-class-docstring",
"missing-module-docstring",
Expand Down
2 changes: 1 addition & 1 deletion starlite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
from starlite.response import Response
from starlite.router import Router
from starlite.routes import ASGIRoute, BaseRoute, HTTPRoute, WebSocketRoute
from starlite.testing import TestClient, create_test_client # type: ignore[no-redef]
from starlite.testing import TestClient, create_test_client
from starlite.types.partial import Partial

__all__ = (
Expand Down
Loading

0 comments on commit de3bb1d

Please sign in to comment.