diff --git a/docs/release-notes.md b/docs/release-notes.md index cb0cb825..587206d9 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,7 @@ * Schema generation is no longer attached to the application instance. Use `schemas = SchemaGenerator(...)` and `return schemas.OpenAPIResponse(request=request)` * `LifespanMiddleware` is dropped in favor of router-based lifespan handling. * Application instances now accept a `routes` argument, `Starlette(routes=[...])` +* Schema generation now includes mounted routes. ## 0.10.6 diff --git a/docs/requests.md b/docs/requests.md index c71932e8..317699b6 100644 --- a/docs/requests.md +++ b/docs/requests.md @@ -115,3 +115,12 @@ will raise an error. In some cases such as long-polling, or streaming responses you might need to determine if the client has dropped the connection. You can determine this state with `disconnected = await request.is_disconnected()`. + +#### Other state + +If you want to store additional information on the request you can do so +using `request.state`. + +For example: + +`request.state.time_started = time.time()` diff --git a/starlette/requests.py b/starlette/requests.py index 85762520..6953cee0 100644 --- a/starlette/requests.py +++ b/starlette/requests.py @@ -18,6 +18,10 @@ class ClientDisconnect(Exception): pass +class State: + pass + + class HTTPConnection(Mapping): """ A base class for incoming HTTP connections, that is used to provide @@ -88,16 +92,6 @@ class HTTPConnection(Mapping): ), "SessionMiddleware must be installed to access request.session" return self._scope["session"] - @property - def database(self) -> typing.Any: # pragma: no cover - # NOTE: Pending deprecation. You probably want to look at the - # stand-alone `databases` package instead. - # https://github.com/encode/databases - assert ( - "database" in self._scope - ), "DatabaseMiddleware must be installed to access request.database" - return self._scope["database"] - @property def auth(self) -> typing.Any: assert ( @@ -112,6 +106,12 @@ class HTTPConnection(Mapping): ), "AuthenticationMiddleware must be installed to access request.user" return self._scope["user"] + @property + def state(self) -> State: + if "state" not in self._scope: + self._scope["state"] = State() + return self._scope["state"] + def url_for(self, name: str, **path_params: typing.Any) -> str: router = self._scope["router"] url_path = router.url_path_for(name, **path_params) diff --git a/tests/test_requests.py b/tests/test_requests.py index 8e3f3688..79192e47 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -280,6 +280,21 @@ def test_request_is_disconnected(): assert disconnected_after_response +def test_request_state(): + def app(scope): + async def asgi(receive, send): + request = Request(scope, receive) + request.state.example = 123 + response = JSONResponse({"state.example": request["state"].example}) + await response(receive, send) + + return asgi + + client = TestClient(app) + response = client.get("/123?a=abc") + assert response.json() == {"state.example": 123} + + def test_request_cookies(): def app(scope): async def asgi(receive, send):