asgi-tools 1.1.0__cp311-cp311-musllinux_1_2_aarch64.whl → 1.3.1__cp311-cp311-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/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
- "part_data_pos",
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, Callable, Iterator, Optional, Union
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
- """Represent a HTTP Request.
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
- "_cookies",
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: Optional[URL] = None
55
- self._body: Optional[bytes] = None
56
- self._form: Optional[MultiDict] = None
57
- self._headers: Optional[CIMultiDict] = None
58
- self._media: Optional[dict[str, str]] = None
59
- self._cookies: Optional[dict[str, str]] = None
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) -> dict[str, str]:
187
+ def media(self) -> Mapping[str, str]:
178
188
  """Prepare a media data for the request."""
179
189
  if self._media is None:
180
- conten_type_header = self.headers.get("content-type", "")
181
- content_type, opts = parse_options_header(conten_type_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") # noqa: TRY003
228
+ raise RuntimeError("Stream has been read")
219
229
  yield self._body
220
230
 
221
231
  else:
222
232
  self._is_read = True
223
- message = await self.receive()
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
- self._body = b"".join([chunk async for chunk in self.stream()])
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: Optional[Callable] = None,
277
+ upload_to: UploadHandler | None = None,
265
278
  file_memory_limit: int = 1024 * 1024,
266
279
  ) -> MultiDict:
267
- """Read and return the request's multipart formdata as a multidict.
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: The maximum size of the request body in bytes.
273
- :param upload_to: A callable to be used to determine the upload path for files.
274
- :param file_memory_limit: The maximum size of the file to be stored in memory in bytes.
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
- try:
281
- self._form = await read_formdata(
282
- self,
283
- max_size,
284
- upload_to,
285
- file_memory_limit,
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) -> Union[str, bytes, MultiDict, TJSON]:
293
- """The method checks Content-Type Header and parse the request's data automatically.
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 self.content_type in {
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 self.content_type == "application/json":
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
- else:
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, Optional, Union
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
- """A base class to make ASGI_ responses.
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: Optional[str] = None
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: Optional[int] = None,
68
- content_type: Optional[str] = None,
69
- headers: Optional[dict[str, str]] = None,
70
- cookies: Optional[dict[str, str]] = None,
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
- """A helper to return plain text responses (text/plain)."""
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
- """A helper to return HTML responses (text/html)."""
139
+ """Returns HTML responses (text/html)."""
137
140
 
138
141
  content_type = "text/html"
139
142
 
140
143
 
141
144
  class ResponseJSON(Response):
142
- """A helper to return JSON responses (application/json).
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
- """A helper to stream a response's body.
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
- """A helper to stream SSE (server side events).
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(chunk) -> bytes:
217
+ def process_content(content) -> bytes:
215
218
  """Prepare a chunk from stream generator to send."""
216
- if isinstance(chunk, dict):
217
- chunk = "\n".join(f"{k}: {v}" for k, v in chunk.items())
219
+ if isinstance(content, dict):
220
+ content = "\n".join(f"{k}: {v}" for k, v in content.items())
218
221
 
219
- if not isinstance(chunk, bytes):
220
- chunk = chunk.encode(DEFAULT_CHARSET)
222
+ if not isinstance(content, bytes):
223
+ content = content.encode(DEFAULT_CHARSET)
221
224
 
222
- return chunk + b"\n\n"
225
+ return content + b"\n\n"
223
226
 
224
227
 
225
228
  class ResponseFile(ResponseStream):
226
- """A helper to stream files as a response body.
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: Union[str, Path],
244
+ filepath: str | Path,
242
245
  *,
243
246
  chunk_size: int = 64 * 1024,
244
- filename: Optional[str] = None,
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}") # noqa: TRY003
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'attachment; filename="{quote(filename)}"'
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
- """A helper to work with websockets.
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: Optional[TASGIReceive] = None,
295
- send: Optional[TASGISend] = None,
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") # noqa: TRY003
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
- """Use it as async context manager."""
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: Union[dict, str, bytes], msg_type="websocket.send") -> None:
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) -> Union[TASGIMessage, str]:
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
- """A helper to return HTTP redirects. Uses a 307 status code by default.
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: Optional[int] = None, **kwargs) -> None:
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
- # TODO: From python 3.9 -> partial['ResponseError]
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
- """A helper to return HTTP errors. Uses a 500 status code by default.
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
- # TODO: From python 3.9
455
- # IM_A_TEAPOT: Callable[..., ResponseError] # 418
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
- # TODO: From python 3.9
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: Optional[int] = None, **kwargs):
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: Optional[dict] = None) -> Response:
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