Skip to content

Commit 3c0f46c

Browse files
committed
Add typing for ASGI Scopes, messages, and frameworks
These are useful for users of asgiref to type check their ASGI usage. It also serves as the reference typing for the ASGI Specification.
1 parent 5b86d96 commit 3c0f46c

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

asgiref/typing.py

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
from typing import Awaitable, Callable, Dict, Iterable, Optional, Tuple, Type, Union
2+
3+
try:
4+
from typing import Literal, Protocol, TypedDict
5+
except ImportError:
6+
from typing_extensions import Literal, Protocol, TypedDict # type: ignore
7+
8+
9+
class ASGIVersions(TypedDict, total=False):
10+
spec_version: str
11+
version: Union[Literal["2.0"], Literal["3.0"]]
12+
13+
14+
class HTTPScope(TypedDict):
15+
type: Literal["http"]
16+
asgi: ASGIVersions
17+
http_version: str
18+
method: str
19+
scheme: str
20+
path: str
21+
raw_path: bytes
22+
query_string: bytes
23+
root_path: str
24+
headers: Iterable[Tuple[bytes, bytes]]
25+
client: Optional[Tuple[str, int]]
26+
server: Optional[Tuple[str, Optional[int]]]
27+
extensions: Dict[str, dict]
28+
29+
30+
class WebsocketScope(TypedDict):
31+
type: Literal["websocket"]
32+
asgi: ASGIVersions
33+
http_version: str
34+
scheme: str
35+
path: str
36+
raw_path: bytes
37+
query_string: bytes
38+
root_path: str
39+
headers: Iterable[Tuple[bytes, bytes]]
40+
client: Optional[Tuple[str, int]]
41+
server: Optional[Tuple[str, Optional[int]]]
42+
subprotocols: Iterable[str]
43+
extensions: Dict[str, dict]
44+
45+
46+
class LifespanScope(TypedDict):
47+
type: Literal["lifespan"]
48+
asgi: ASGIVersions
49+
50+
51+
WWWScope = Union[HTTPScope, WebsocketScope]
52+
Scope = Union[HTTPScope, WebsocketScope, LifespanScope]
53+
54+
55+
class HTTPRequestEvent(TypedDict):
56+
type: Literal["http.request"]
57+
body: bytes
58+
more_body: bool
59+
60+
61+
class HTTPResponseStartEvent(TypedDict):
62+
type: Literal["http.response.start"]
63+
status: int
64+
headers: Iterable[Tuple[bytes, bytes]]
65+
66+
67+
class HTTPResponseBodyEvent(TypedDict):
68+
type: Literal["http.response.body"]
69+
body: bytes
70+
more_body: bool
71+
72+
73+
class HTTPServerPushEvent(TypedDict):
74+
type: Literal["http.response.push"]
75+
path: str
76+
headers: Iterable[Tuple[bytes, bytes]]
77+
78+
79+
class HTTPDisconnectEvent(TypedDict):
80+
type: Literal["http.disconnect"]
81+
82+
83+
class WebsocketConnectEvent(TypedDict):
84+
type: Literal["websocket.connect"]
85+
86+
87+
class WebsocketAcceptEvent(TypedDict):
88+
type: Literal["websocket.accept"]
89+
subprotocol: Optional[str]
90+
headers: Iterable[Tuple[bytes, bytes]]
91+
92+
93+
class WebsocketReceiveEvent(TypedDict):
94+
type: Literal["websocket.receive"]
95+
bytes: Optional[bytes]
96+
text: Optional[str]
97+
98+
99+
class WebsocketSendEvent(TypedDict):
100+
type: Literal["websocket.send"]
101+
bytes: Optional[bytes]
102+
text: Optional[str]
103+
104+
105+
class WebsocketResponseStartEvent(TypedDict):
106+
type: Literal["websocket.http.response.start"]
107+
status: int
108+
headers: Iterable[Tuple[bytes, bytes]]
109+
110+
111+
class WebsocketResponseBodyEvent(TypedDict):
112+
type: Literal["websocket.http.response.body"]
113+
body: bytes
114+
more_body: bool
115+
116+
117+
class WebsocketDisconnectEvent(TypedDict):
118+
type: Literal["websocket.disconnect"]
119+
code: int
120+
121+
122+
class WebsocketCloseEvent(TypedDict):
123+
type: Literal["websocket.close"]
124+
code: Optional[int]
125+
126+
127+
class LifespanStartupEvent(TypedDict):
128+
type: Literal["lifespan.startup"]
129+
130+
131+
class LifespanShutdownEvent(TypedDict):
132+
type: Literal["lifespan.shutdown"]
133+
134+
135+
class LifespanStartupCompleteEvent(TypedDict):
136+
type: Literal["lifespan.startup.complete"]
137+
138+
139+
class LifespanStartupFailedEvent(TypedDict):
140+
type: Literal["lifespan.startup.failed"]
141+
message: str
142+
143+
144+
class LifespanShutdownCompleteEvent(TypedDict):
145+
type: Literal["lifespan.shutdown.complete"]
146+
147+
148+
class LifespanShutdownFailedEvent(TypedDict):
149+
type: Literal["lifespan.shutdown.failed"]
150+
message: str
151+
152+
153+
ASGIReceiveEvent = Union[
154+
HTTPRequestEvent,
155+
HTTPDisconnectEvent,
156+
WebsocketConnectEvent,
157+
WebsocketReceiveEvent,
158+
WebsocketDisconnectEvent,
159+
LifespanStartupEvent,
160+
LifespanShutdownEvent,
161+
]
162+
163+
164+
ASGISendEvent = Union[
165+
HTTPResponseStartEvent,
166+
HTTPResponseBodyEvent,
167+
HTTPServerPushEvent,
168+
HTTPDisconnectEvent,
169+
WebsocketAcceptEvent,
170+
WebsocketSendEvent,
171+
WebsocketResponseStartEvent,
172+
WebsocketResponseBodyEvent,
173+
WebsocketCloseEvent,
174+
LifespanStartupCompleteEvent,
175+
LifespanStartupFailedEvent,
176+
LifespanShutdownCompleteEvent,
177+
LifespanShutdownFailedEvent,
178+
]
179+
180+
181+
ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]]
182+
ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]]
183+
184+
185+
class ASGI2Protocol(Protocol):
186+
def __init__(self, scope: Scope) -> None:
187+
...
188+
189+
async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
190+
...
191+
192+
193+
ASGI2Framework = Type[ASGI2Protocol]
194+
ASGI3Framework = Callable[
195+
[
196+
Scope,
197+
ASGIReceiveCallable,
198+
ASGISendCallable,
199+
],
200+
Awaitable[None],
201+
]
202+
ASGIFramework = Union[ASGI2Framework, ASGI3Framework]

setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ project_urls =
3131
python_requires = >=3.5
3232
packages = find:
3333
include_package_data = true
34+
install_requires =
35+
typing_extensions; python_version < "3.8"
3436
zip_safe = false
3537

3638
[options.extras_require]

0 commit comments

Comments
 (0)