asgi-tools 1.1.0__cp310-cp310-macosx_10_9_universal2.whl → 1.3.1__cp310-cp310-macosx_10_9_universal2.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-darwin.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-darwin.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 +2 -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/multipart.py
CHANGED
@@ -256,18 +256,18 @@ class MultipartParser(BaseParser):
|
|
256
256
|
"""
|
257
257
|
|
258
258
|
__slots__ = (
|
259
|
+
"boundary",
|
260
|
+
"boundary_chars",
|
259
261
|
"callbacks",
|
260
262
|
"cursize",
|
261
|
-
"max_size",
|
262
|
-
"state",
|
263
|
-
"index",
|
264
263
|
"flags",
|
265
264
|
"header_field_pos",
|
266
265
|
"header_value_pos",
|
267
|
-
"
|
268
|
-
"boundary",
|
269
|
-
"boundary_chars",
|
266
|
+
"index",
|
270
267
|
"lookbehind",
|
268
|
+
"max_size",
|
269
|
+
"part_data_pos",
|
270
|
+
"state",
|
271
271
|
)
|
272
272
|
|
273
273
|
def __init__(self, boundary, callbacks: dict, max_size: int = 0):
|
@@ -587,6 +587,3 @@ def prune_data(data_len: int, cursize: int, max_size: int) -> int:
|
|
587
587
|
return max_size - cursize
|
588
588
|
|
589
589
|
return data_len
|
590
|
-
|
591
|
-
|
592
|
-
# ruff: noqa: TRY003
|
asgi_tools/request.py
CHANGED
@@ -5,7 +5,7 @@ incoming request.
|
|
5
5
|
from __future__ import annotations
|
6
6
|
|
7
7
|
from http import cookies
|
8
|
-
from typing import TYPE_CHECKING, Any, AsyncGenerator,
|
8
|
+
from typing import TYPE_CHECKING, Any, AsyncGenerator, Iterator, Mapping, Protocol, TypeVar
|
9
9
|
|
10
10
|
from yarl import URL
|
11
11
|
|
@@ -17,11 +17,21 @@ from .types import TJSON, TASGIReceive, TASGIScope, TASGISend
|
|
17
17
|
from .utils import CIMultiDict, parse_headers, parse_options_header
|
18
18
|
|
19
19
|
if TYPE_CHECKING:
|
20
|
+
from pathlib import Path
|
21
|
+
|
20
22
|
from multidict import MultiDict, MultiDictProxy
|
21
23
|
|
24
|
+
T = TypeVar("T")
|
25
|
+
|
26
|
+
|
27
|
+
class UploadHandler(Protocol):
|
28
|
+
"""Protocol for file upload handlers."""
|
29
|
+
|
30
|
+
async def __call__(self, filename: str, content_type: str, content: bytes) -> str | Path: ...
|
31
|
+
|
22
32
|
|
23
33
|
class Request(TASGIScope):
|
24
|
-
"""
|
34
|
+
"""Provides a convenient, high-level interface for incoming HTTP requests.
|
25
35
|
|
26
36
|
:param scope: HTTP ASGI Scope
|
27
37
|
:param receive: an asynchronous callable which lets the application
|
@@ -32,16 +42,16 @@ class Request(TASGIScope):
|
|
32
42
|
"""
|
33
43
|
|
34
44
|
__slots__ = (
|
35
|
-
"scope",
|
36
|
-
"receive",
|
37
|
-
"send",
|
38
|
-
"_is_read",
|
39
|
-
"_url",
|
40
45
|
"_body",
|
46
|
+
"_cookies",
|
41
47
|
"_form",
|
42
48
|
"_headers",
|
49
|
+
"_is_read",
|
43
50
|
"_media",
|
44
|
-
"
|
51
|
+
"_url",
|
52
|
+
"receive",
|
53
|
+
"scope",
|
54
|
+
"send",
|
45
55
|
)
|
46
56
|
|
47
57
|
def __init__(self, scope: TASGIScope, receive: TASGIReceive, send: TASGISend):
|
@@ -51,12 +61,12 @@ class Request(TASGIScope):
|
|
51
61
|
self.send = send
|
52
62
|
|
53
63
|
self._is_read: bool = False
|
54
|
-
self._url:
|
55
|
-
self._body:
|
56
|
-
self._form:
|
57
|
-
self._headers:
|
58
|
-
self._media:
|
59
|
-
self._cookies:
|
64
|
+
self._url: URL | None = None
|
65
|
+
self._body: bytes | None = None
|
66
|
+
self._form: MultiDict | None = None
|
67
|
+
self._headers: CIMultiDict | None = None
|
68
|
+
self._media: dict[str, str] | None = None
|
69
|
+
self._cookies: dict[str, str] | None = None
|
60
70
|
|
61
71
|
def __str__(self) -> str:
|
62
72
|
"""Return the request's params."""
|
@@ -174,11 +184,11 @@ class Request(TASGIScope):
|
|
174
184
|
return self._cookies
|
175
185
|
|
176
186
|
@property
|
177
|
-
def media(self) ->
|
187
|
+
def media(self) -> Mapping[str, str]:
|
178
188
|
"""Prepare a media data for the request."""
|
179
189
|
if self._media is None:
|
180
|
-
|
181
|
-
content_type, opts = parse_options_header(
|
190
|
+
content_type_header = self.headers.get("content-type", "")
|
191
|
+
content_type, opts = parse_options_header(content_type_header)
|
182
192
|
self._media = dict(opts, content_type=content_type)
|
183
193
|
|
184
194
|
return self._media
|
@@ -215,16 +225,16 @@ class Request(TASGIScope):
|
|
215
225
|
"""
|
216
226
|
if self._is_read:
|
217
227
|
if self._body is None:
|
218
|
-
raise RuntimeError("Stream has been read")
|
228
|
+
raise RuntimeError("Stream has been read")
|
219
229
|
yield self._body
|
220
230
|
|
221
231
|
else:
|
222
232
|
self._is_read = True
|
223
|
-
|
224
|
-
yield message.get("body", b"")
|
225
|
-
while message.get("more_body"):
|
233
|
+
while True:
|
226
234
|
message = await self.receive()
|
227
235
|
yield message.get("body", b"")
|
236
|
+
if not message.get("more_body"):
|
237
|
+
break
|
228
238
|
|
229
239
|
async def body(self) -> bytes:
|
230
240
|
"""Read and return the request's body as bytes.
|
@@ -232,7 +242,10 @@ class Request(TASGIScope):
|
|
232
242
|
`body = await request.body()`
|
233
243
|
"""
|
234
244
|
if self._body is None:
|
235
|
-
|
245
|
+
data = bytearray()
|
246
|
+
async for chunk in self.stream():
|
247
|
+
data.extend(chunk)
|
248
|
+
self._body = bytes(data)
|
236
249
|
|
237
250
|
return self._body
|
238
251
|
|
@@ -261,38 +274,38 @@ class Request(TASGIScope):
|
|
261
274
|
async def form(
|
262
275
|
self,
|
263
276
|
max_size: int = 0,
|
264
|
-
upload_to:
|
277
|
+
upload_to: UploadHandler | None = None,
|
265
278
|
file_memory_limit: int = 1024 * 1024,
|
266
279
|
) -> MultiDict:
|
267
|
-
"""Read and return the request's
|
268
|
-
|
269
|
-
The method reads the request's stream stright into memory formdata.
|
270
|
-
Any subsequent calls to :py:meth:`body`, :py:meth:`json` will raise an error.
|
280
|
+
"""Read and return the request's form data.
|
271
281
|
|
272
|
-
:param max_size:
|
273
|
-
:
|
274
|
-
:param
|
282
|
+
:param max_size: Maximum size of the form data in bytes
|
283
|
+
:type max_size: int
|
284
|
+
:param upload_to: Callable to handle file uploads
|
285
|
+
:type upload_to: Optional[UploadHandler]
|
286
|
+
:param file_memory_limit: Maximum size of file to keep in memory
|
287
|
+
:type file_memory_limit: int
|
288
|
+
:return: Form data as MultiDict
|
289
|
+
:rtype: MultiDict
|
275
290
|
|
276
291
|
`formdata = await request.form()`
|
277
|
-
|
278
292
|
"""
|
279
293
|
if self._form is None:
|
280
|
-
|
281
|
-
self
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
)
|
287
|
-
except (LookupError, ValueError) as exc:
|
288
|
-
raise ASGIDecodeError from exc
|
289
|
-
|
294
|
+
self._form = await read_formdata(
|
295
|
+
self,
|
296
|
+
max_size=max_size,
|
297
|
+
upload_to=upload_to,
|
298
|
+
file_memory_limit=file_memory_limit,
|
299
|
+
)
|
290
300
|
return self._form
|
291
301
|
|
292
|
-
async def data(self, *, raise_errors: bool = False) ->
|
293
|
-
"""
|
302
|
+
async def data(self, *, raise_errors: bool = False) -> str | bytes | MultiDict | TJSON:
|
303
|
+
"""Read and return the request's data based on content type.
|
294
304
|
|
295
305
|
:param raise_errors: Raise an error if the given data is invalid.
|
306
|
+
:return: Request data in appropriate format
|
307
|
+
:rtype: Union[str, bytes, MultiDict, TJSON]
|
308
|
+
:raises ASGIDecodeError: If data cannot be decoded and raise_errors is True
|
296
309
|
|
297
310
|
`data = await request.data()`
|
298
311
|
|
@@ -302,14 +315,15 @@ class Request(TASGIScope):
|
|
302
315
|
Returns data from :py:meth:`json` for `application/json`, :py:meth:`form` for
|
303
316
|
`application/x-www-form-urlencoded`, `multipart/form-data` and :py:meth:`text` otherwise.
|
304
317
|
"""
|
318
|
+
content_type = self.content_type
|
305
319
|
try:
|
306
|
-
if
|
307
|
-
"application/x-www-form-urlencoded",
|
320
|
+
if content_type in {
|
308
321
|
"multipart/form-data",
|
322
|
+
"application/x-www-form-urlencoded",
|
309
323
|
}:
|
310
324
|
return await self.form()
|
311
325
|
|
312
|
-
if
|
326
|
+
if content_type == "application/json":
|
313
327
|
return await self.json()
|
314
328
|
|
315
329
|
except ASGIDecodeError:
|
@@ -317,5 +331,7 @@ class Request(TASGIScope):
|
|
317
331
|
raise
|
318
332
|
return await self.body()
|
319
333
|
|
320
|
-
|
334
|
+
if content_type.startswith("text/"):
|
321
335
|
return await self.text()
|
336
|
+
|
337
|
+
return await self.body()
|
asgi_tools/response.py
CHANGED
@@ -11,7 +11,7 @@ from http.cookies import SimpleCookie
|
|
11
11
|
from mimetypes import guess_type
|
12
12
|
from pathlib import Path
|
13
13
|
from stat import S_ISDIR
|
14
|
-
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Mapping,
|
14
|
+
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Mapping, TypeVar
|
15
15
|
from urllib.parse import quote, quote_plus
|
16
16
|
|
17
17
|
from multidict import MultiDict
|
@@ -24,12 +24,15 @@ from .request import Request
|
|
24
24
|
if TYPE_CHECKING:
|
25
25
|
from .types import TASGIMessage, TASGIReceive, TASGIScope, TASGISend
|
26
26
|
|
27
|
+
T = TypeVar("T")
|
28
|
+
TContent = TypeVar("TContent", str, bytes, dict, list, None)
|
29
|
+
|
27
30
|
|
28
31
|
class Response:
|
29
|
-
"""
|
32
|
+
"""Base class for creating HTTP responses.
|
30
33
|
|
31
34
|
:param content: A response's body
|
32
|
-
:type content: str | bytes
|
35
|
+
:type content: str | bytes | dict | list | None
|
33
36
|
:param status_code: An HTTP status code
|
34
37
|
:type status_code: int
|
35
38
|
:param headers: A dictionary of HTTP headers
|
@@ -57,17 +60,17 @@ class Response:
|
|
57
60
|
strategy ('lax'|'strict'|'none')
|
58
61
|
|
59
62
|
"""
|
60
|
-
content_type:
|
63
|
+
content_type: str | None = None
|
61
64
|
status_code: int = HTTPStatus.OK.value
|
62
65
|
|
63
66
|
def __init__(
|
64
67
|
self,
|
65
|
-
content,
|
68
|
+
content: TContent,
|
66
69
|
*,
|
67
|
-
status_code:
|
68
|
-
content_type:
|
69
|
-
headers:
|
70
|
-
cookies:
|
70
|
+
status_code: int | None = None,
|
71
|
+
content_type: str | None = None,
|
72
|
+
headers: dict[str, str] | None = None,
|
73
|
+
cookies: dict[str, str] | None = None,
|
71
74
|
):
|
72
75
|
"""Setup the response."""
|
73
76
|
self.content = self.process_content(content)
|
@@ -80,8 +83,7 @@ class Response:
|
|
80
83
|
if content_type:
|
81
84
|
self.headers.setdefault(
|
82
85
|
"content-type",
|
83
|
-
content_type.startswith("text/")
|
84
|
-
and f"{content_type}; charset={DEFAULT_CHARSET}"
|
86
|
+
(content_type.startswith("text/") and f"{content_type}; charset={DEFAULT_CHARSET}")
|
85
87
|
or content_type,
|
86
88
|
)
|
87
89
|
|
@@ -101,7 +103,8 @@ class Response:
|
|
101
103
|
await send({"type": "http.response.body", "body": self.content})
|
102
104
|
|
103
105
|
@staticmethod
|
104
|
-
def process_content(content) -> bytes:
|
106
|
+
def process_content(content: TContent) -> bytes:
|
107
|
+
"""Process content into bytes."""
|
105
108
|
if not isinstance(content, bytes):
|
106
109
|
return str(content).encode(DEFAULT_CHARSET)
|
107
110
|
return content
|
@@ -127,19 +130,19 @@ class Response:
|
|
127
130
|
|
128
131
|
|
129
132
|
class ResponseText(Response):
|
130
|
-
"""
|
133
|
+
"""Returns plain text responses (text/plain)."""
|
131
134
|
|
132
135
|
content_type = "text/plain"
|
133
136
|
|
134
137
|
|
135
138
|
class ResponseHTML(Response):
|
136
|
-
"""
|
139
|
+
"""Returns HTML responses (text/html)."""
|
137
140
|
|
138
141
|
content_type = "text/html"
|
139
142
|
|
140
143
|
|
141
144
|
class ResponseJSON(Response):
|
142
|
-
"""
|
145
|
+
"""Returns JSON responses (application/json).
|
143
146
|
|
144
147
|
The class optionally supports `ujson <https://pypi.org/project/ujson/>`_ and `orjson
|
145
148
|
<https://pypi.org/project/orjson/>`_ JSON libraries. Install one of them to use instead
|
@@ -156,7 +159,7 @@ class ResponseJSON(Response):
|
|
156
159
|
|
157
160
|
|
158
161
|
class ResponseStream(Response):
|
159
|
-
"""
|
162
|
+
"""Streams response body as chunks.
|
160
163
|
|
161
164
|
:param content: An async generator to stream the response's body
|
162
165
|
:type content: AsyncGenerator
|
@@ -197,7 +200,7 @@ class ResponseStream(Response):
|
|
197
200
|
|
198
201
|
|
199
202
|
class ResponseSSE(ResponseStream):
|
200
|
-
"""
|
203
|
+
"""Streams Server-Sent Events (SSE).
|
201
204
|
|
202
205
|
:param content: An async generator to stream the events
|
203
206
|
:type content: AsyncGenerator
|
@@ -211,19 +214,19 @@ class ResponseSSE(ResponseStream):
|
|
211
214
|
return super().msg_start()
|
212
215
|
|
213
216
|
@staticmethod
|
214
|
-
def process_content(
|
217
|
+
def process_content(content) -> bytes:
|
215
218
|
"""Prepare a chunk from stream generator to send."""
|
216
|
-
if isinstance(
|
217
|
-
|
219
|
+
if isinstance(content, dict):
|
220
|
+
content = "\n".join(f"{k}: {v}" for k, v in content.items())
|
218
221
|
|
219
|
-
if not isinstance(
|
220
|
-
|
222
|
+
if not isinstance(content, bytes):
|
223
|
+
content = content.encode(DEFAULT_CHARSET)
|
221
224
|
|
222
|
-
return
|
225
|
+
return content + b"\n\n"
|
223
226
|
|
224
227
|
|
225
228
|
class ResponseFile(ResponseStream):
|
226
|
-
"""
|
229
|
+
"""Serves files as HTTP responses.
|
227
230
|
|
228
231
|
:param filepath: The filepath to the file
|
229
232
|
:type filepath: str | Path
|
@@ -238,10 +241,10 @@ class ResponseFile(ResponseStream):
|
|
238
241
|
|
239
242
|
def __init__(
|
240
243
|
self,
|
241
|
-
filepath:
|
244
|
+
filepath: str | Path,
|
242
245
|
*,
|
243
246
|
chunk_size: int = 64 * 1024,
|
244
|
-
filename:
|
247
|
+
filename: str | None = None,
|
245
248
|
headers_only: bool = False,
|
246
249
|
**kwargs,
|
247
250
|
) -> None:
|
@@ -252,7 +255,7 @@ class ResponseFile(ResponseStream):
|
|
252
255
|
raise ASGIError(*exc.args) from exc
|
253
256
|
|
254
257
|
if S_ISDIR(stat.st_mode):
|
255
|
-
raise ASGIError(f"It's a directory: {filepath}")
|
258
|
+
raise ASGIError(f"It's a directory: {filepath}")
|
256
259
|
|
257
260
|
super().__init__(
|
258
261
|
empty() if headers_only else aio_stream_file(filepath, chunk_size),
|
@@ -261,7 +264,7 @@ class ResponseFile(ResponseStream):
|
|
261
264
|
|
262
265
|
headers = self.headers
|
263
266
|
if filename and "content-disposition" not in headers:
|
264
|
-
headers["content-disposition"] = f
|
267
|
+
headers["content-disposition"] = f"attachment; filename*=UTF-8''{quote(filename)}"
|
265
268
|
|
266
269
|
if "content-type" not in headers:
|
267
270
|
headers["content-type"] = guess_type(filename or str(filepath))[0] or "text/plain"
|
@@ -273,13 +276,7 @@ class ResponseFile(ResponseStream):
|
|
273
276
|
|
274
277
|
|
275
278
|
class ResponseWebSocket(Response):
|
276
|
-
"""
|
277
|
-
|
278
|
-
:param scope: Request info (ASGI Scope | ASGI-Tools Request)
|
279
|
-
:type scope: dict
|
280
|
-
:param receive: ASGI receive function
|
281
|
-
:param send: ASGI send function
|
282
|
-
"""
|
279
|
+
"""Provides a WebSocket handler interface."""
|
283
280
|
|
284
281
|
class STATES(Enum):
|
285
282
|
"""Represent websocket states."""
|
@@ -291,15 +288,15 @@ class ResponseWebSocket(Response):
|
|
291
288
|
def __init__(
|
292
289
|
self,
|
293
290
|
scope: TASGIScope,
|
294
|
-
receive:
|
295
|
-
send:
|
291
|
+
receive: TASGIReceive | None = None,
|
292
|
+
send: TASGISend | None = None,
|
296
293
|
) -> None:
|
297
294
|
"""Initialize the websocket response."""
|
298
295
|
if isinstance(scope, Request):
|
299
296
|
receive, send = scope.receive, scope.send
|
300
297
|
|
301
298
|
if not receive or not send:
|
302
|
-
raise ASGIError("Invalid initialization")
|
299
|
+
raise ASGIError("Invalid initialization")
|
303
300
|
|
304
301
|
super().__init__(b"")
|
305
302
|
self._receive: TASGIReceive = receive
|
@@ -317,7 +314,7 @@ class ResponseWebSocket(Response):
|
|
317
314
|
return self
|
318
315
|
|
319
316
|
async def __aexit__(self, *_):
|
320
|
-
"""
|
317
|
+
"""Exit async context."""
|
321
318
|
await self.close()
|
322
319
|
|
323
320
|
@property
|
@@ -348,13 +345,13 @@ class ResponseWebSocket(Response):
|
|
348
345
|
await self.send({"type": "websocket.close", "code": code})
|
349
346
|
self.state = self.STATES.DISCONNECTED
|
350
347
|
|
351
|
-
async def send(self, msg:
|
348
|
+
async def send(self, msg: dict | str | bytes, msg_type="websocket.send") -> None:
|
352
349
|
"""Send the given message to a client."""
|
353
350
|
if self.state == self.STATES.DISCONNECTED:
|
354
351
|
raise ASGIConnectionClosedError
|
355
352
|
|
356
353
|
if not isinstance(msg, dict):
|
357
|
-
msg = {"type": msg_type, (isinstance(msg, str) and "text" or "bytes"): msg}
|
354
|
+
msg = {"type": msg_type, ((isinstance(msg, str) and "text") or "bytes"): msg}
|
358
355
|
|
359
356
|
return await self._send(msg)
|
360
357
|
|
@@ -362,7 +359,7 @@ class ResponseWebSocket(Response):
|
|
362
359
|
"""Serialize the given data to JSON and send to a client."""
|
363
360
|
return await self._send({"type": "websocket.send", "bytes": json_dumps(data)})
|
364
361
|
|
365
|
-
async def receive(self, *, raw: bool = False) ->
|
362
|
+
async def receive(self, *, raw: bool = False) -> TASGIMessage | str:
|
366
363
|
"""Receive messages from a client.
|
367
364
|
|
368
365
|
:param raw: Receive messages as is.
|
@@ -382,7 +379,7 @@ class ResponseWebSocket(Response):
|
|
382
379
|
|
383
380
|
|
384
381
|
class ResponseRedirect(Response, BaseException):
|
385
|
-
"""
|
382
|
+
"""Creates HTTP redirects. Uses a 307 status code by default.
|
386
383
|
|
387
384
|
:param url: A string with the new location
|
388
385
|
:type url: str
|
@@ -390,7 +387,7 @@ class ResponseRedirect(Response, BaseException):
|
|
390
387
|
|
391
388
|
status_code: int = HTTPStatus.TEMPORARY_REDIRECT.value
|
392
389
|
|
393
|
-
def __init__(self, url: str, status_code:
|
390
|
+
def __init__(self, url: str, status_code: int | None = None, **kwargs) -> None:
|
394
391
|
"""Set status code and prepare location."""
|
395
392
|
super().__init__(b"", status_code=status_code, **kwargs)
|
396
393
|
assert (
|
@@ -402,8 +399,7 @@ class ResponseRedirect(Response, BaseException):
|
|
402
399
|
class ResponseErrorMeta(type):
|
403
400
|
"""Generate Response Errors by HTTP names."""
|
404
401
|
|
405
|
-
|
406
|
-
def __getattr__(cls, name: str) -> Callable[..., ResponseError]:
|
402
|
+
def __getattr__(cls, name: str) -> partial[ResponseError]:
|
407
403
|
"""Generate Response Errors by HTTP names."""
|
408
404
|
status = HTTPStatus[name]
|
409
405
|
return partial(
|
@@ -413,7 +409,7 @@ class ResponseErrorMeta(type):
|
|
413
409
|
|
414
410
|
|
415
411
|
class ResponseError(Response, BaseException, metaclass=ResponseErrorMeta):
|
416
|
-
"""
|
412
|
+
"""Helper for returning HTTP errors. Uses a 500 status code by default.
|
417
413
|
|
418
414
|
:param message: A string with the error's message (HTTPStatus messages will be used by default)
|
419
415
|
:type message: str
|
@@ -451,9 +447,8 @@ class ResponseError(Response, BaseException, metaclass=ResponseErrorMeta):
|
|
451
447
|
UNSUPPORTED_MEDIA_TYPE: Callable[..., ResponseError] # 415
|
452
448
|
REQUESTED_RANGE_NOT_SATISFIABLE: Callable[..., ResponseError] # 416
|
453
449
|
EXPECTATION_FAILED: Callable[..., ResponseError] # 417
|
454
|
-
|
455
|
-
|
456
|
-
# MISDIRECTED_REQUEST: Callable[..., ResponseError] # 421
|
450
|
+
IM_A_TEAPOT: Callable[..., ResponseError] # 418
|
451
|
+
MISDIRECTED_REQUEST: Callable[..., ResponseError] # 421
|
457
452
|
UNPROCESSABLE_ENTITY: Callable[..., ResponseError] # 422
|
458
453
|
LOCKED: Callable[..., ResponseError] # 423
|
459
454
|
FAILED_DEPENDENCY: Callable[..., ResponseError] # 424
|
@@ -462,8 +457,7 @@ class ResponseError(Response, BaseException, metaclass=ResponseErrorMeta):
|
|
462
457
|
PRECONDITION_REQUIRED: Callable[..., ResponseError] # 428
|
463
458
|
TOO_MANY_REQUESTS: Callable[..., ResponseError] # 429
|
464
459
|
REQUEST_HEADER_FIELDS_TOO_LARGE: Callable[..., ResponseError] # 431
|
465
|
-
|
466
|
-
# UNAVAILABLE_FOR_LEGAL_REASONS: Callable[..., ResponseError] # 451
|
460
|
+
UNAVAILABLE_FOR_LEGAL_REASONS: Callable[..., ResponseError] # 451
|
467
461
|
|
468
462
|
INTERNAL_SERVER_ERROR: Callable[..., ResponseError] # 500
|
469
463
|
NOT_IMPLEMENTED: Callable[..., ResponseError] # 501
|
@@ -477,7 +471,7 @@ class ResponseError(Response, BaseException, metaclass=ResponseErrorMeta):
|
|
477
471
|
NOT_EXTENDED: Callable[..., ResponseError] # 510
|
478
472
|
NETWORK_AUTHENTICATION_REQUIRED: Callable[..., ResponseError] # 511
|
479
473
|
|
480
|
-
def __init__(self, message=None, status_code:
|
474
|
+
def __init__(self, message=None, status_code: int | None = None, **kwargs):
|
481
475
|
"""Check error status."""
|
482
476
|
content = message or HTTPStatus(status_code or self.status_code).description
|
483
477
|
super().__init__(content=content, status_code=status_code, **kwargs)
|
@@ -495,7 +489,7 @@ CAST_RESPONSE: Mapping[type, type[Response]] = {
|
|
495
489
|
}
|
496
490
|
|
497
491
|
|
498
|
-
def parse_response(response, headers:
|
492
|
+
def parse_response(response, headers: dict | None = None) -> Response:
|
499
493
|
"""Parse the given object and convert it into a asgi_tools.Response."""
|
500
494
|
if isinstance(response, Response):
|
501
495
|
return response
|
@@ -520,9 +514,7 @@ def parse_response(response, headers: Optional[dict] = None) -> Response:
|
|
520
514
|
return ResponseText(str(response), headers=headers)
|
521
515
|
|
522
516
|
|
523
|
-
def parse_websocket_msg(
|
524
|
-
msg: TASGIMessage, charset: Optional[str] = None
|
525
|
-
) -> Union[TASGIMessage, str]:
|
517
|
+
def parse_websocket_msg(msg: TASGIMessage, charset: str | None = None) -> TASGIMessage | str:
|
526
518
|
"""Prepare websocket message."""
|
527
519
|
data = msg.get("text")
|
528
520
|
if data:
|
@@ -537,6 +529,3 @@ def parse_websocket_msg(
|
|
537
529
|
|
538
530
|
async def empty():
|
539
531
|
yield b""
|
540
|
-
|
541
|
-
|
542
|
-
# ruff: noqa: ERA001
|