asgi-tools 1.1.0__cp310-cp310-musllinux_1_2_aarch64.whl → 1.3.1__cp310-cp310-musllinux_1_2_aarch64.whl
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.
- asgi_tools/__init__.py +3 -2
- asgi_tools/_compat.py +10 -13
- asgi_tools/app.py +105 -51
- asgi_tools/forms.c +6646 -7185
- asgi_tools/forms.cpython-310-aarch64-linux-gnu.so +0 -0
- asgi_tools/forms.py +9 -9
- asgi_tools/middleware.py +38 -29
- asgi_tools/multipart.c +5818 -5562
- asgi_tools/multipart.cpython-310-aarch64-linux-gnu.so +0 -0
- asgi_tools/multipart.py +6 -9
- asgi_tools/request.py +63 -47
- asgi_tools/response.py +49 -60
- asgi_tools/tests.py +20 -21
- asgi_tools/view.py +2 -2
- {asgi_tools-1.1.0.dist-info → asgi_tools-1.3.1.dist-info}/METADATA +23 -23
- asgi_tools-1.3.1.dist-info/RECORD +29 -0
- {asgi_tools-1.1.0.dist-info → asgi_tools-1.3.1.dist-info}/WHEEL +1 -1
- asgi_tools-1.1.0.dist-info/RECORD +0 -29
- {asgi_tools-1.1.0.dist-info → asgi_tools-1.3.1.dist-info/licenses}/LICENSE +0 -0
- {asgi_tools-1.1.0.dist-info → asgi_tools-1.3.1.dist-info}/top_level.txt +0 -0
Binary file
|
asgi_tools/forms.py
CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from io import BytesIO
|
6
6
|
from tempfile import SpooledTemporaryFile
|
7
|
-
from typing import TYPE_CHECKING, Callable
|
7
|
+
from typing import TYPE_CHECKING, Callable
|
8
8
|
from urllib.parse import unquote_to_bytes
|
9
9
|
|
10
10
|
from multidict import MultiDict
|
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
|
19
19
|
async def read_formdata(
|
20
20
|
request: "Request",
|
21
21
|
max_size: int,
|
22
|
-
upload_to:
|
22
|
+
upload_to: Callable | None,
|
23
23
|
file_memory_limit: int = 1024 * 1024,
|
24
24
|
) -> MultiDict:
|
25
25
|
"""Read formdata from the given request."""
|
@@ -43,7 +43,7 @@ async def read_formdata(
|
|
43
43
|
class FormReader:
|
44
44
|
"""Process querystring form data."""
|
45
45
|
|
46
|
-
__slots__ = "
|
46
|
+
__slots__ = "charset", "curname", "curvalue", "form"
|
47
47
|
|
48
48
|
def __init__(self, charset: str):
|
49
49
|
self.charset = charset
|
@@ -80,18 +80,18 @@ class MultipartReader(FormReader):
|
|
80
80
|
"""Process multipart formdata."""
|
81
81
|
|
82
82
|
__slots__ = (
|
83
|
-
"
|
83
|
+
"charset",
|
84
84
|
"curname",
|
85
85
|
"curvalue",
|
86
|
-
"
|
86
|
+
"file_memory_limit",
|
87
|
+
"form",
|
88
|
+
"headers",
|
87
89
|
"name",
|
88
90
|
"partdata",
|
89
|
-
"headers",
|
90
91
|
"upload_to",
|
91
|
-
"file_memory_limit",
|
92
92
|
)
|
93
93
|
|
94
|
-
def __init__(self, charset: str, upload_to:
|
94
|
+
def __init__(self, charset: str, upload_to: Callable | None, file_memory_limit: int):
|
95
95
|
super().__init__(charset)
|
96
96
|
self.name = ""
|
97
97
|
self.headers: dict[bytes, bytes] = {}
|
@@ -102,7 +102,7 @@ class MultipartReader(FormReader):
|
|
102
102
|
def init_parser(self, request: "Request", max_size: int) -> BaseParser:
|
103
103
|
boundary = request.media.get("boundary", "")
|
104
104
|
if not boundary:
|
105
|
-
raise ValueError("Invalid content type boundary")
|
105
|
+
raise ValueError("Invalid content type boundary")
|
106
106
|
|
107
107
|
return MultipartParser(
|
108
108
|
boundary,
|
asgi_tools/middleware.py
CHANGED
@@ -8,7 +8,7 @@ from contextvars import ContextVar
|
|
8
8
|
from functools import partial
|
9
9
|
from inspect import isawaitable
|
10
10
|
from pathlib import Path
|
11
|
-
from typing import TYPE_CHECKING, Awaitable, Callable, Final, Mapping
|
11
|
+
from typing import TYPE_CHECKING, Awaitable, Callable, Final, Mapping
|
12
12
|
|
13
13
|
from http_router import Router
|
14
14
|
|
@@ -22,12 +22,12 @@ if TYPE_CHECKING:
|
|
22
22
|
from .types import TASGIApp, TASGIMessage, TASGIReceive, TASGIScope, TASGISend
|
23
23
|
|
24
24
|
|
25
|
-
class
|
25
|
+
class BaseMiddleware(metaclass=abc.ABCMeta):
|
26
26
|
"""Base class for ASGI-Tools middlewares."""
|
27
27
|
|
28
28
|
scopes: tuple[str, ...] = ("http", "websocket")
|
29
29
|
|
30
|
-
def __init__(self, app:
|
30
|
+
def __init__(self, app: TASGIApp | None = None) -> None:
|
31
31
|
"""Save ASGI App."""
|
32
32
|
self.bind(app)
|
33
33
|
|
@@ -48,13 +48,17 @@ class BaseMiddeware(metaclass=abc.ABCMeta):
|
|
48
48
|
"""Setup the middleware without an initialization."""
|
49
49
|
return partial(cls, **params) # type: ignore[abstract]
|
50
50
|
|
51
|
-
def bind(self, app:
|
51
|
+
def bind(self, app: TASGIApp | None = None):
|
52
52
|
"""Rebind the middleware to an ASGI application if it has been inited already."""
|
53
53
|
self.app = app or ResponseError.NOT_FOUND()
|
54
54
|
return self
|
55
55
|
|
56
56
|
|
57
|
-
|
57
|
+
# Backward compatibility
|
58
|
+
BaseMiddeware = BaseMiddleware
|
59
|
+
|
60
|
+
|
61
|
+
class ResponseMiddleware(BaseMiddleware):
|
58
62
|
"""Automatically convert ASGI_ apps results into responses :class:`~asgi_tools.Response` and
|
59
63
|
send them to server as ASGI_ messages.
|
60
64
|
|
@@ -121,22 +125,22 @@ class ResponseMiddleware(BaseMiddeware):
|
|
121
125
|
await exc(scope, receive, send)
|
122
126
|
|
123
127
|
def send(self, _: TASGIMessage):
|
124
|
-
raise RuntimeError("You can't use send() method in ResponseMiddleware")
|
128
|
+
raise RuntimeError("You can't use send() method in ResponseMiddleware")
|
125
129
|
|
126
|
-
def bind(self, app:
|
130
|
+
def bind(self, app: TASGIApp | None = None):
|
127
131
|
"""Rebind the middleware to an ASGI application if it has been inited already."""
|
128
132
|
self.app = app or to_awaitable(lambda *_: ResponseError.NOT_FOUND())
|
129
133
|
return self
|
130
134
|
|
131
135
|
|
132
|
-
class RequestMiddleware(
|
136
|
+
class RequestMiddleware(BaseMiddleware):
|
133
137
|
"""Automatically create :class:`asgi_tools.Request` from the scope and pass it to ASGI_ apps.
|
134
138
|
|
135
139
|
.. code-block:: python
|
136
140
|
|
137
141
|
from asgi_tools import RequestMiddleware, Response
|
138
142
|
|
139
|
-
async def app(
|
143
|
+
async def app(scope, receive, send):
|
140
144
|
content = f"{ request.method } { request.url.path }"
|
141
145
|
response = Response(content)
|
142
146
|
await response(scope, receive, send)
|
@@ -150,7 +154,7 @@ class RequestMiddleware(BaseMiddeware):
|
|
150
154
|
return await self.app(Request(scope, receive, send), receive, send)
|
151
155
|
|
152
156
|
|
153
|
-
class LifespanMiddleware(
|
157
|
+
class LifespanMiddleware(BaseMiddleware):
|
154
158
|
"""Manage ASGI_ Lifespan events.
|
155
159
|
|
156
160
|
:param ignore_errors: Ignore errors from startup/shutdown handlers
|
@@ -193,12 +197,12 @@ class LifespanMiddleware(BaseMiddeware):
|
|
193
197
|
|
194
198
|
def __init__(
|
195
199
|
self,
|
196
|
-
app:
|
200
|
+
app: TASGIApp | None = None,
|
197
201
|
*,
|
198
202
|
logger=logger,
|
199
203
|
ignore_errors: bool = False,
|
200
|
-
on_startup:
|
201
|
-
on_shutdown:
|
204
|
+
on_startup: Callable | list[Callable] | None = None,
|
205
|
+
on_shutdown: Callable | list[Callable] | None = None,
|
202
206
|
) -> None:
|
203
207
|
"""Prepare the middleware."""
|
204
208
|
super(LifespanMiddleware, self).__init__(app)
|
@@ -223,7 +227,7 @@ class LifespanMiddleware(BaseMiddeware):
|
|
223
227
|
break
|
224
228
|
|
225
229
|
def __register__(
|
226
|
-
self, handlers:
|
230
|
+
self, handlers: Callable | list[Callable] | None, container: list[Callable]
|
227
231
|
) -> None:
|
228
232
|
"""Register lifespan handlers."""
|
229
233
|
if not handlers:
|
@@ -243,7 +247,7 @@ class LifespanMiddleware(BaseMiddeware):
|
|
243
247
|
"""Use the lifespan middleware as a context manager."""
|
244
248
|
await self.run("shutdown")
|
245
249
|
|
246
|
-
async def run(self, event: str, _:
|
250
|
+
async def run(self, event: str, _: TASGISend | None = None):
|
247
251
|
"""Run startup/shutdown handlers."""
|
248
252
|
assert event in {"startup", "shutdown"}
|
249
253
|
handlers = getattr(self, f"__{event}__")
|
@@ -273,7 +277,7 @@ class LifespanMiddleware(BaseMiddeware):
|
|
273
277
|
self.__register__(fn, self.__shutdown__)
|
274
278
|
|
275
279
|
|
276
|
-
class RouterMiddleware(
|
280
|
+
class RouterMiddleware(BaseMiddleware):
|
277
281
|
r"""Manage routing.
|
278
282
|
|
279
283
|
.. code-block:: python
|
@@ -328,7 +332,7 @@ class RouterMiddleware(BaseMiddeware):
|
|
328
332
|
|
329
333
|
"""
|
330
334
|
|
331
|
-
def __init__(self, app:
|
335
|
+
def __init__(self, app: TASGIApp | None = None, router: Router | None = None) -> None:
|
332
336
|
"""Initialize HTTP router."""
|
333
337
|
super().__init__(app)
|
334
338
|
self.router = router or Router(validator=callable)
|
@@ -338,7 +342,7 @@ class RouterMiddleware(BaseMiddeware):
|
|
338
342
|
app, scope["path_params"] = self.__dispatch__(scope)
|
339
343
|
return await app(scope, *args)
|
340
344
|
|
341
|
-
def __dispatch__(self, scope: TASGIScope) -> tuple[Callable,
|
345
|
+
def __dispatch__(self, scope: TASGIScope) -> tuple[Callable, Mapping | None]:
|
342
346
|
"""Lookup for a callback."""
|
343
347
|
path = f"{scope.get('root_path', '')}{scope['path']}"
|
344
348
|
try:
|
@@ -355,7 +359,7 @@ class RouterMiddleware(BaseMiddeware):
|
|
355
359
|
return self.router.route(*args, **kwargs)
|
356
360
|
|
357
361
|
|
358
|
-
class StaticFilesMiddleware(
|
362
|
+
class StaticFilesMiddleware(BaseMiddleware):
|
359
363
|
"""Serve static files.
|
360
364
|
|
361
365
|
:param url_prefix: an URL prefix for static files
|
@@ -368,7 +372,7 @@ class StaticFilesMiddleware(BaseMiddeware):
|
|
368
372
|
from asgi_tools import StaticFilesMiddleware, ResponseHTML
|
369
373
|
|
370
374
|
async def app(scope, receive, send):
|
371
|
-
response = ResponseHTML('OK)
|
375
|
+
response = ResponseHTML('OK')
|
372
376
|
await response(scope, receive, send)
|
373
377
|
|
374
378
|
# Files from static folder will be served from /static
|
@@ -380,9 +384,9 @@ class StaticFilesMiddleware(BaseMiddeware):
|
|
380
384
|
|
381
385
|
def __init__(
|
382
386
|
self,
|
383
|
-
app:
|
387
|
+
app: TASGIApp | None = None,
|
384
388
|
url_prefix: str = "/static",
|
385
|
-
folders:
|
389
|
+
folders: list[str | Path] | None = None,
|
386
390
|
) -> None:
|
387
391
|
"""Initialize the middleware."""
|
388
392
|
super().__init__(app)
|
@@ -395,10 +399,12 @@ class StaticFilesMiddleware(BaseMiddeware):
|
|
395
399
|
path = scope["path"]
|
396
400
|
url_prefix = self.url_prefix
|
397
401
|
if path.startswith(url_prefix):
|
398
|
-
response:
|
402
|
+
response: Response | None = None
|
399
403
|
filename = path[len(url_prefix) :].strip("/")
|
400
404
|
for folder in self.folders:
|
401
405
|
filepath = folder.joinpath(filename).resolve()
|
406
|
+
if folder != filepath.parent:
|
407
|
+
continue
|
402
408
|
with suppress(ASGIError):
|
403
409
|
response = ResponseFile(filepath, headers_only=scope["method"] == "HEAD")
|
404
410
|
break
|
@@ -410,10 +416,10 @@ class StaticFilesMiddleware(BaseMiddeware):
|
|
410
416
|
await self.app(scope, receive, send)
|
411
417
|
|
412
418
|
|
413
|
-
BACKGROUND_TASK: Final = ContextVar[
|
419
|
+
BACKGROUND_TASK: Final = ContextVar[list[Awaitable] | None]("background_task", default=None)
|
414
420
|
|
415
421
|
|
416
|
-
class BackgroundMiddleware(
|
422
|
+
class BackgroundMiddleware(BaseMiddleware):
|
417
423
|
"""Run background tasks.
|
418
424
|
|
419
425
|
|
@@ -439,11 +445,14 @@ class BackgroundMiddleware(BaseMiddeware):
|
|
439
445
|
async def __process__(self, scope: TASGIScope, receive: TASGIReceive, send: TASGISend):
|
440
446
|
"""Run background tasks."""
|
441
447
|
await self.app(scope, receive, send)
|
442
|
-
|
443
|
-
|
444
|
-
|
448
|
+
for task in BACKGROUND_TASK.get() or []:
|
449
|
+
await task
|
450
|
+
|
451
|
+
BACKGROUND_TASK.set(None) # Clear the context variable
|
445
452
|
|
446
453
|
@staticmethod
|
447
454
|
def set_task(task: Awaitable):
|
448
455
|
"""Set a task for background execution."""
|
449
|
-
BACKGROUND_TASK.
|
456
|
+
tasks = BACKGROUND_TASK.get() or []
|
457
|
+
tasks.append(task)
|
458
|
+
BACKGROUND_TASK.set(tasks)
|