Skip to content

Commit

Permalink
Introduce lifespan state
Browse files Browse the repository at this point in the history
  • Loading branch information
adriangb committed Dec 27, 2022
1 parent 79a1d01 commit cfdcecd
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 2 deletions.
10 changes: 9 additions & 1 deletion asgiref/typing.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import sys
from typing import Awaitable, Callable, Dict, Iterable, Optional, Tuple, Type, Union
from typing import Any, Awaitable, Callable, Dict, Iterable, Optional, Tuple, Type, Union

if sys.version_info >= (3, 8):
from typing import Literal, Protocol, TypedDict
else:
from typing_extensions import Literal, Protocol, TypedDict

if sys.version_info >= (3, 11):
from typing import NotRequired
else:
from typing_extensions import NotRequired

__all__ = (
"ASGIVersions",
"HTTPScope",
Expand Down Expand Up @@ -62,6 +67,7 @@ class HTTPScope(TypedDict):
headers: Iterable[Tuple[bytes, bytes]]
client: Optional[Tuple[str, int]]
server: Optional[Tuple[str, Optional[int]]]
state: NotRequired[Dict[str, Any]]
extensions: Optional[Dict[str, Dict[object, object]]]


Expand All @@ -78,12 +84,14 @@ class WebSocketScope(TypedDict):
client: Optional[Tuple[str, int]]
server: Optional[Tuple[str, Optional[int]]]
subprotocols: Iterable[str]
state: NotRequired[Dict[str, Any]]
extensions: Optional[Dict[str, Dict[object, object]]]


class LifespanScope(TypedDict):
type: Literal["lifespan"]
asgi: ASGIVersions
state: Optional[Dict[str, Any]]


WWWScope = Union[HTTPScope, WebSocketScope]
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ python_requires = >=3.7
packages = find:
include_package_data = true
install_requires =
typing_extensions; python_version < "3.8"
typing_extensions; python_version < "3.11"
zip_safe = false

[options.extras_require]
Expand Down
35 changes: 35 additions & 0 deletions specs/lifespan.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ The scope information passed in ``scope`` contains basic metadata:
* ``asgi["version"]`` (*Unicode string*) -- The version of the ASGI spec.
* ``asgi["spec_version"]`` (*Unicode string*) -- The version of this spec being
used. Optional; if missing defaults to ``"1.0"``.
* ``state`` Optional(*dict[Unicode string, Any]*) -- An empty namespace where
the application can persist state to be used when handling subsequent requests.
Optional; if missing the server does not support this feature.

If an exception is raised when calling the application callable with a
``lifespan.startup`` message or a ``scope`` with type ``lifespan``,
Expand All @@ -56,6 +59,38 @@ lifespan protocol. If you want to log an error that occurs during lifespan
startup and prevent the server from starting, then send back
``lifespan.startup.failed`` instead.

The ``extensions["lifespan.state"]`` dict is an empty namespace.
Applications can store arbitrary data in this namespace.
A *shallow copy* of this dictionary will get passed into each request handler.
This key will only be set if the server supports this extension.


Lifespan State
--------------

Applications often want to persist data from the lifespan cycle to request/response handling.
For example, a database connection can be established in the lifespan cycle and persisted to
the request/response cycle.
The ```lifespan["state"]`` namespace provides a place to store these sorts of things.
The server will ensure that a *shallow copy* of the namespace is passed into each subsequent
request/response call into the application.
Since the server manages the application lifespan and often the event loop as well this
ensures that the application is always accessing the database connection (or other stored object)
that corresponds to the right event loop and lifecycle, without using context variables,
global mutable state or having to worry about references to stale/closed connections.

ASGI servers that implement this feature will provide
``state`` as part of the ``lifespan`` scope::

"scope": {
...
"state": {},
}

The namespace is controlled completely by the ASGI application, the server will not
interact with it other than to copy it.
Nonetheless applications should be cooperative by properly naming their keys such that they
will not collide with other frameworks or middleware.

Startup - ``receive`` event
'''''''''''''''''''''''''''
Expand Down
4 changes: 4 additions & 0 deletions specs/www.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ The *connection scope* information passed in ``scope`` contains:
listening port, or ``[path, None]`` where ``path`` is that of the
unix socket. Optional; if missing defaults to ``None``.

* ``state`` Optional(*dict[Unicode string, Any]*) -- A copy of the
namespace passed into the lifespan corresponding to this request. (See :doc:`lifespan`).
Optional; if missing the server does not support this feature.

Servers are responsible for handling inbound and outbound chunked transfer
encodings. A request with a ``chunked`` encoded body should be automatically
de-chunked by the server and presented to the application as plain body bytes;
Expand Down

0 comments on commit cfdcecd

Please sign in to comment.