punkasgi 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. punkasgi-0.1.0/.gitignore +11 -0
  2. punkasgi-0.1.0/LICENSE +28 -0
  3. punkasgi-0.1.0/PKG-INFO +117 -0
  4. punkasgi-0.1.0/README.md +84 -0
  5. punkasgi-0.1.0/punkasgi/__init__.py +6 -0
  6. punkasgi-0.1.0/punkasgi/__main__.py +5 -0
  7. punkasgi-0.1.0/punkasgi/_types.py +253 -0
  8. punkasgi-0.1.0/punkasgi/_version.py +1 -0
  9. punkasgi-0.1.0/punkasgi/config.py +329 -0
  10. punkasgi-0.1.0/punkasgi/importer.py +66 -0
  11. punkasgi-0.1.0/punkasgi/lifespan/__init__.py +0 -0
  12. punkasgi-0.1.0/punkasgi/lifespan/off.py +10 -0
  13. punkasgi-0.1.0/punkasgi/lifespan/on.py +142 -0
  14. punkasgi-0.1.0/punkasgi/logging.py +1 -0
  15. punkasgi-0.1.0/punkasgi/main.py +400 -0
  16. punkasgi-0.1.0/punkasgi/middleware/__init__.py +0 -0
  17. punkasgi-0.1.0/punkasgi/middleware/message_logger.py +101 -0
  18. punkasgi-0.1.0/punkasgi/middleware/proxy_headers.py +178 -0
  19. punkasgi-0.1.0/punkasgi/protocols/__init__.py +0 -0
  20. punkasgi-0.1.0/punkasgi/protocols/http.py +430 -0
  21. punkasgi-0.1.0/punkasgi/protocols/utils.py +52 -0
  22. punkasgi-0.1.0/punkasgi/protocols/websockets.py +643 -0
  23. punkasgi-0.1.0/punkasgi/server.py +322 -0
  24. punkasgi-0.1.0/pyproject.toml +132 -0
  25. punkasgi-0.1.0/tests/__init__.py +0 -0
  26. punkasgi-0.1.0/tests/conftest.py +119 -0
  27. punkasgi-0.1.0/tests/fakes.py +154 -0
  28. punkasgi-0.1.0/tests/protocols/__init__.py +0 -0
  29. punkasgi-0.1.0/tests/protocols/test_http.py +650 -0
  30. punkasgi-0.1.0/tests/protocols/test_websocket.py +1022 -0
  31. punkasgi-0.1.0/tests/response.py +70 -0
  32. punkasgi-0.1.0/tests/test_config.py +65 -0
  33. punkasgi-0.1.0/tests/test_lifespan.py +224 -0
  34. punkasgi-0.1.0/tests/test_server.py +160 -0
  35. punkasgi-0.1.0/tests/utils.py +27 -0
  36. punkasgi-0.1.0/tests/ws.py +252 -0
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ uv.lock
10
+
11
+ bench/.envs/
punkasgi-0.1.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ Copyright 2026 Giovanni Barillari
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.5
2
+ Name: punkasgi
3
+ Version: 0.1.0
4
+ Summary: An ASGI server for the tonio runtime, built on httpunk
5
+ Project-URL: Homepage, https://github.com/gi0baro/punkasgi
6
+ Project-URL: Funding, https://github.com/sponsors/gi0baro
7
+ Project-URL: Source, https://github.com/gi0baro/punkasgi
8
+ Project-URL: Issues, https://github.com/gi0baro/punkasgi/issues
9
+ Author-email: Giovanni Barillari <g@baro.dev>
10
+ License-Expression: BSD-3-Clause
11
+ License-File: LICENSE
12
+ Keywords: asgi,async,http,httpunk,server,tonio
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: BSD License
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: 3.15
22
+ Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
23
+ Classifier: Topic :: Internet :: WWW/HTTP
24
+ Requires-Python: >=3.14
25
+ Requires-Dist: click>=7.0
26
+ Requires-Dist: httpunk~=0.4.1
27
+ Requires-Dist: tonio~=0.9.17
28
+ Requires-Dist: websockets>=17
29
+ Provides-Extra: standard
30
+ Requires-Dist: python-dotenv>=0.13; extra == 'standard'
31
+ Requires-Dist: pyyaml>=5.1; extra == 'standard'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # punkasgi
35
+
36
+ punkasgi is an ASGI server for the [TonIO](https://github.com/gi0baro/tonio) runtime,
37
+ built on top of [HTTPunk](https://github.com/gi0baro/httpunk).
38
+
39
+ > **Warning:** punkasgi is in an early stage and still work in progress.
40
+
41
+ > **Note:** punkasgi was built with substantial help from LLMs, under human supervision.
42
+
43
+ ## In a nutshell
44
+
45
+ - HTTP/1.0, HTTP/1.1 and HTTP/2 (prior knowledge on plain TCP, ALPN over TLS), with
46
+ every protocol behaviour coming from httpunk: keep-alive, head timeouts, framing,
47
+ `Date` headers, graceful shutdown
48
+ - WebSockets over HTTP/1.1 through the sans-io `websockets` implementation, with
49
+ keepalive pings, per-message deflate and the `websocket.http.response` extension
50
+ - The ASGI 3 protocol handling (scope contents, `receive()` / `send()` semantics, lifespan,
51
+ websocket messages) follows [uvicorn](https://github.com/Kludex/uvicorn)
52
+ - Single process architecture: the TonIO runtime's threads are the workers
53
+
54
+ ## Installation
55
+
56
+ ```shell
57
+ pip install punkasgi
58
+ ```
59
+
60
+ punkasgi needs a free-threaded CPython 3.14 or later, as tonio does. The `standard` extra
61
+ adds `python-dotenv` (for `--env-file`) and `PyYAML` (for YAML logging configurations).
62
+
63
+ ## Quickstart
64
+
65
+ ```python
66
+ # main.py
67
+ async def app(scope, receive, send):
68
+ assert scope["type"] == "http"
69
+ await send({"type": "http.response.start", "status": 200, "headers": [(b"content-type", b"text/plain")]})
70
+ await send({"type": "http.response.body", "body": b"Hello, world!"})
71
+ ```
72
+
73
+ ```shell
74
+ punkasgi main:app
75
+ ```
76
+
77
+ Or *programmatically*:
78
+
79
+ ```python
80
+ import punkasgi
81
+
82
+ punkasgi.run("main:app", host="0.0.0.0", port=8000, threads=4)
83
+ ```
84
+
85
+ `punkasgi --help` lists every option. The ones worth knowing:
86
+
87
+ | option | note |
88
+ |---|---|
89
+ | `--threads` | size of the tonio runtime, defaults to `$WEB_CONCURRENCY` or the CPU count |
90
+ | `--http auto\|h1\|h2` | protocol selection: `auto` sniffs the HTTP/2 preface on plain TCP and follows ALPN over TLS |
91
+ | `--ws/--no-ws` | WebSocket upgrades on or off; with them off an upgrade request is answered 400 |
92
+ | `--timeout-keep-alive` | hyper's head read timeout: it bounds the idle wait between requests together with the read of the next request head |
93
+ | `--timeout-graceful-shutdown` | how long a shutdown waits for in-flight requests; unset waits without a deadline |
94
+ | `--proxy-headers` | trust `X-Forwarded-For` / `X-Forwarded-Proto` from `--forwarded-allow-ips`; off by default |
95
+ | `--access-log` | one `punkasgi.access` line per response; off by default |
96
+
97
+ A first `SIGINT` or `SIGTERM` starts a graceful shutdown; a second `SIGINT` leaves at once
98
+ with whatever is still in flight. Environment variables use the `PUNKASGI_` prefix (e.g.
99
+ `PUNKASGI_PORT`), loggers are `punkasgi.error`, `punkasgi.access` and `punkasgi.asgi`,
100
+ and the default `server` header is `punkasgi`.
101
+
102
+ ## Behaviours worth knowing
103
+
104
+ These follow httpunk, and through it hyper, rather than any other server:
105
+
106
+ - **Unread request bodies.** After a response completes, a request body the application
107
+ left unread is drained only if it is already buffered; otherwise the connection closes
108
+ instead of being reused for the next request.
109
+ - **Response `content-length`.** punkasgi does not check the body against the declared
110
+ length. On HTTP/1 a body shorter than declared fails the response and closes the
111
+ connection, and a longer one is cut at the declared length; on HTTP/2 the frames go out
112
+ as sent.
113
+ - **`Date` header.** Always written, on both protocols.
114
+
115
+ ## License
116
+
117
+ punkasgi is released under the BSD-3-Clause license.
@@ -0,0 +1,84 @@
1
+ # punkasgi
2
+
3
+ punkasgi is an ASGI server for the [TonIO](https://github.com/gi0baro/tonio) runtime,
4
+ built on top of [HTTPunk](https://github.com/gi0baro/httpunk).
5
+
6
+ > **Warning:** punkasgi is in an early stage and still work in progress.
7
+
8
+ > **Note:** punkasgi was built with substantial help from LLMs, under human supervision.
9
+
10
+ ## In a nutshell
11
+
12
+ - HTTP/1.0, HTTP/1.1 and HTTP/2 (prior knowledge on plain TCP, ALPN over TLS), with
13
+ every protocol behaviour coming from httpunk: keep-alive, head timeouts, framing,
14
+ `Date` headers, graceful shutdown
15
+ - WebSockets over HTTP/1.1 through the sans-io `websockets` implementation, with
16
+ keepalive pings, per-message deflate and the `websocket.http.response` extension
17
+ - The ASGI 3 protocol handling (scope contents, `receive()` / `send()` semantics, lifespan,
18
+ websocket messages) follows [uvicorn](https://github.com/Kludex/uvicorn)
19
+ - Single process architecture: the TonIO runtime's threads are the workers
20
+
21
+ ## Installation
22
+
23
+ ```shell
24
+ pip install punkasgi
25
+ ```
26
+
27
+ punkasgi needs a free-threaded CPython 3.14 or later, as tonio does. The `standard` extra
28
+ adds `python-dotenv` (for `--env-file`) and `PyYAML` (for YAML logging configurations).
29
+
30
+ ## Quickstart
31
+
32
+ ```python
33
+ # main.py
34
+ async def app(scope, receive, send):
35
+ assert scope["type"] == "http"
36
+ await send({"type": "http.response.start", "status": 200, "headers": [(b"content-type", b"text/plain")]})
37
+ await send({"type": "http.response.body", "body": b"Hello, world!"})
38
+ ```
39
+
40
+ ```shell
41
+ punkasgi main:app
42
+ ```
43
+
44
+ Or *programmatically*:
45
+
46
+ ```python
47
+ import punkasgi
48
+
49
+ punkasgi.run("main:app", host="0.0.0.0", port=8000, threads=4)
50
+ ```
51
+
52
+ `punkasgi --help` lists every option. The ones worth knowing:
53
+
54
+ | option | note |
55
+ |---|---|
56
+ | `--threads` | size of the tonio runtime, defaults to `$WEB_CONCURRENCY` or the CPU count |
57
+ | `--http auto\|h1\|h2` | protocol selection: `auto` sniffs the HTTP/2 preface on plain TCP and follows ALPN over TLS |
58
+ | `--ws/--no-ws` | WebSocket upgrades on or off; with them off an upgrade request is answered 400 |
59
+ | `--timeout-keep-alive` | hyper's head read timeout: it bounds the idle wait between requests together with the read of the next request head |
60
+ | `--timeout-graceful-shutdown` | how long a shutdown waits for in-flight requests; unset waits without a deadline |
61
+ | `--proxy-headers` | trust `X-Forwarded-For` / `X-Forwarded-Proto` from `--forwarded-allow-ips`; off by default |
62
+ | `--access-log` | one `punkasgi.access` line per response; off by default |
63
+
64
+ A first `SIGINT` or `SIGTERM` starts a graceful shutdown; a second `SIGINT` leaves at once
65
+ with whatever is still in flight. Environment variables use the `PUNKASGI_` prefix (e.g.
66
+ `PUNKASGI_PORT`), loggers are `punkasgi.error`, `punkasgi.access` and `punkasgi.asgi`,
67
+ and the default `server` header is `punkasgi`.
68
+
69
+ ## Behaviours worth knowing
70
+
71
+ These follow httpunk, and through it hyper, rather than any other server:
72
+
73
+ - **Unread request bodies.** After a response completes, a request body the application
74
+ left unread is drained only if it is already buffered; otherwise the connection closes
75
+ instead of being reused for the next request.
76
+ - **Response `content-length`.** punkasgi does not check the body against the declared
77
+ length. On HTTP/1 a body shorter than declared fails the response and closes the
78
+ connection, and a longer one is cut at the declared length; on HTTP/2 the frames go out
79
+ as sent.
80
+ - **`Date` header.** Always written, on both protocols.
81
+
82
+ ## License
83
+
84
+ punkasgi is released under the BSD-3-Clause license.
@@ -0,0 +1,6 @@
1
+ from punkasgi._version import __version__
2
+ from punkasgi.config import Config
3
+ from punkasgi.main import Server, main, run
4
+
5
+
6
+ __all__ = ["Config", "Server", "__version__", "main", "run"]
@@ -0,0 +1,5 @@
1
+ from punkasgi.main import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,253 @@
1
+ """
2
+ Copyright (c) Django Software Foundation and individual contributors.
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification,
6
+ are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of Django nor the names of its contributors may be used
16
+ to endorse or promote products derived from this software without
17
+ specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from collections.abc import Awaitable, Callable, Iterable
34
+ from typing import Any, Literal, NotRequired, TypedDict
35
+
36
+
37
+ class ASGIVersions(TypedDict):
38
+ spec_version: str
39
+ version: Literal["2.0"] | Literal["3.0"]
40
+
41
+
42
+ class HTTPScope(TypedDict):
43
+ type: Literal["http"]
44
+ asgi: ASGIVersions
45
+ http_version: str
46
+ method: str
47
+ scheme: str
48
+ path: str
49
+ raw_path: bytes
50
+ query_string: bytes
51
+ root_path: str
52
+ headers: Iterable[tuple[bytes, bytes]]
53
+ client: tuple[str, int] | None
54
+ server: tuple[str, int | None] | None
55
+ state: NotRequired[dict[str, Any]]
56
+ extensions: NotRequired[dict[str, dict[object, object]]]
57
+
58
+
59
+ class WebSocketScope(TypedDict):
60
+ type: Literal["websocket"]
61
+ asgi: ASGIVersions
62
+ http_version: str
63
+ scheme: str
64
+ path: str
65
+ raw_path: bytes
66
+ query_string: bytes
67
+ root_path: str
68
+ headers: Iterable[tuple[bytes, bytes]]
69
+ client: tuple[str, int] | None
70
+ server: tuple[str, int | None] | None
71
+ subprotocols: Iterable[str]
72
+ state: NotRequired[dict[str, Any]]
73
+ extensions: NotRequired[dict[str, dict[object, object]]]
74
+
75
+
76
+ class LifespanScope(TypedDict):
77
+ type: Literal["lifespan"]
78
+ asgi: ASGIVersions
79
+ state: NotRequired[dict[str, Any]]
80
+
81
+
82
+ WWWScope = HTTPScope | WebSocketScope
83
+ Scope = HTTPScope | WebSocketScope | LifespanScope
84
+
85
+
86
+ class HTTPRequestEvent(TypedDict):
87
+ type: Literal["http.request"]
88
+ body: bytes
89
+ more_body: bool
90
+
91
+
92
+ class HTTPResponseDebugEvent(TypedDict):
93
+ type: Literal["http.response.debug"]
94
+ info: dict[str, object]
95
+
96
+
97
+ class HTTPResponseStartEvent(TypedDict):
98
+ type: Literal["http.response.start"]
99
+ status: int
100
+ headers: NotRequired[Iterable[tuple[bytes, bytes]]]
101
+ trailers: NotRequired[bool]
102
+
103
+
104
+ class HTTPResponseBodyEvent(TypedDict):
105
+ type: Literal["http.response.body"]
106
+ body: bytes
107
+ more_body: NotRequired[bool]
108
+
109
+
110
+ class HTTPResponseTrailersEvent(TypedDict):
111
+ type: Literal["http.response.trailers"]
112
+ headers: Iterable[tuple[bytes, bytes]]
113
+ more_trailers: bool
114
+
115
+
116
+ class HTTPServerPushEvent(TypedDict):
117
+ type: Literal["http.response.push"]
118
+ path: str
119
+ headers: Iterable[tuple[bytes, bytes]]
120
+
121
+
122
+ class HTTPDisconnectEvent(TypedDict):
123
+ type: Literal["http.disconnect"]
124
+
125
+
126
+ class WebSocketConnectEvent(TypedDict):
127
+ type: Literal["websocket.connect"]
128
+
129
+
130
+ class WebSocketAcceptEvent(TypedDict):
131
+ type: Literal["websocket.accept"]
132
+ subprotocol: NotRequired[str | None]
133
+ headers: NotRequired[Iterable[tuple[bytes, bytes]]]
134
+
135
+
136
+ class _WebSocketReceiveEventBytes(TypedDict):
137
+ type: Literal["websocket.receive"]
138
+ bytes: bytes
139
+ text: NotRequired[None]
140
+
141
+
142
+ class _WebSocketReceiveEventText(TypedDict):
143
+ type: Literal["websocket.receive"]
144
+ bytes: NotRequired[None]
145
+ text: str
146
+
147
+
148
+ WebSocketReceiveEvent = _WebSocketReceiveEventBytes | _WebSocketReceiveEventText
149
+
150
+
151
+ class _WebSocketSendEventBytes(TypedDict):
152
+ type: Literal["websocket.send"]
153
+ bytes: bytes
154
+ text: NotRequired[None]
155
+
156
+
157
+ class _WebSocketSendEventText(TypedDict):
158
+ type: Literal["websocket.send"]
159
+ bytes: NotRequired[None]
160
+ text: str
161
+
162
+
163
+ WebSocketSendEvent = _WebSocketSendEventBytes | _WebSocketSendEventText
164
+
165
+
166
+ class WebSocketResponseStartEvent(TypedDict):
167
+ type: Literal["websocket.http.response.start"]
168
+ status: int
169
+ headers: Iterable[tuple[bytes, bytes]]
170
+
171
+
172
+ class WebSocketResponseBodyEvent(TypedDict):
173
+ type: Literal["websocket.http.response.body"]
174
+ body: bytes
175
+ more_body: NotRequired[bool]
176
+
177
+
178
+ class WebSocketDisconnectEvent(TypedDict):
179
+ type: Literal["websocket.disconnect"]
180
+ code: int
181
+ reason: NotRequired[str | None]
182
+
183
+
184
+ class WebSocketCloseEvent(TypedDict):
185
+ type: Literal["websocket.close"]
186
+ code: NotRequired[int]
187
+ reason: NotRequired[str | None]
188
+
189
+
190
+ class LifespanStartupEvent(TypedDict):
191
+ type: Literal["lifespan.startup"]
192
+
193
+
194
+ class LifespanShutdownEvent(TypedDict):
195
+ type: Literal["lifespan.shutdown"]
196
+
197
+
198
+ class LifespanStartupCompleteEvent(TypedDict):
199
+ type: Literal["lifespan.startup.complete"]
200
+
201
+
202
+ class LifespanStartupFailedEvent(TypedDict):
203
+ type: Literal["lifespan.startup.failed"]
204
+ message: str
205
+
206
+
207
+ class LifespanShutdownCompleteEvent(TypedDict):
208
+ type: Literal["lifespan.shutdown.complete"]
209
+
210
+
211
+ class LifespanShutdownFailedEvent(TypedDict):
212
+ type: Literal["lifespan.shutdown.failed"]
213
+ message: str
214
+
215
+
216
+ WebSocketEvent = WebSocketReceiveEvent | WebSocketDisconnectEvent | WebSocketConnectEvent
217
+
218
+
219
+ ASGIReceiveEvent = (
220
+ HTTPRequestEvent
221
+ | HTTPDisconnectEvent
222
+ | WebSocketConnectEvent
223
+ | WebSocketReceiveEvent
224
+ | WebSocketDisconnectEvent
225
+ | LifespanStartupEvent
226
+ | LifespanShutdownEvent
227
+ )
228
+
229
+
230
+ ASGISendEvent = (
231
+ HTTPResponseStartEvent
232
+ | HTTPResponseBodyEvent
233
+ | HTTPResponseTrailersEvent
234
+ | HTTPServerPushEvent
235
+ | HTTPDisconnectEvent
236
+ | WebSocketAcceptEvent
237
+ | WebSocketSendEvent
238
+ | WebSocketResponseStartEvent
239
+ | WebSocketResponseBodyEvent
240
+ | WebSocketCloseEvent
241
+ | LifespanStartupCompleteEvent
242
+ | LifespanStartupFailedEvent
243
+ | LifespanShutdownCompleteEvent
244
+ | LifespanShutdownFailedEvent
245
+ )
246
+
247
+
248
+ ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]]
249
+ ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]]
250
+
251
+
252
+ ASGI3Application = Callable[[Scope, ASGIReceiveCallable, ASGISendCallable], Awaitable[None]]
253
+ ASGIApplication = ASGI3Application
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"