uvicorn 0.28.0__tar.gz → 0.29.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 (45) hide show
  1. {uvicorn-0.28.0 → uvicorn-0.29.0}/PKG-INFO +2 -2
  2. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/__init__.py +1 -1
  3. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/http/h11_impl.py +1 -4
  4. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/http/httptools_impl.py +2 -10
  5. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/server.py +25 -14
  6. {uvicorn-0.28.0 → uvicorn-0.29.0}/.gitignore +0 -0
  7. {uvicorn-0.28.0 → uvicorn-0.29.0}/LICENSE.md +0 -0
  8. {uvicorn-0.28.0 → uvicorn-0.29.0}/README.md +0 -0
  9. {uvicorn-0.28.0 → uvicorn-0.29.0}/pyproject.toml +0 -0
  10. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/__main__.py +0 -0
  11. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/_subprocess.py +0 -0
  12. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/_types.py +0 -0
  13. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/config.py +0 -0
  14. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/importer.py +0 -0
  15. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/lifespan/__init__.py +0 -0
  16. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/lifespan/off.py +0 -0
  17. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/lifespan/on.py +0 -0
  18. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/logging.py +0 -0
  19. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/loops/__init__.py +0 -0
  20. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/loops/asyncio.py +0 -0
  21. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/loops/auto.py +0 -0
  22. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/loops/uvloop.py +0 -0
  23. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/main.py +0 -0
  24. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/middleware/__init__.py +0 -0
  25. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/middleware/asgi2.py +0 -0
  26. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/middleware/message_logger.py +0 -0
  27. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/middleware/proxy_headers.py +0 -0
  28. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/middleware/wsgi.py +0 -0
  29. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/__init__.py +0 -0
  30. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/http/__init__.py +0 -0
  31. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/http/auto.py +0 -0
  32. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/http/flow_control.py +0 -0
  33. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/utils.py +0 -0
  34. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/websockets/__init__.py +0 -0
  35. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/websockets/auto.py +0 -0
  36. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/websockets/websockets_impl.py +0 -0
  37. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/protocols/websockets/wsproto_impl.py +0 -0
  38. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/py.typed +0 -0
  39. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/supervisors/__init__.py +0 -0
  40. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/supervisors/basereload.py +0 -0
  41. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/supervisors/multiprocess.py +0 -0
  42. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/supervisors/statreload.py +0 -0
  43. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/supervisors/watchfilesreload.py +0 -0
  44. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/supervisors/watchgodreload.py +0 -0
  45. {uvicorn-0.28.0 → uvicorn-0.29.0}/uvicorn/workers.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: uvicorn
3
- Version: 0.28.0
3
+ Version: 0.29.0
4
4
  Summary: The lightning-fast ASGI server.
5
5
  Project-URL: Changelog, https://github.com/encode/uvicorn/blob/master/CHANGELOG.md
6
6
  Project-URL: Funding, https://github.com/sponsors/encode
@@ -1,5 +1,5 @@
1
1
  from uvicorn.config import Config
2
2
  from uvicorn.main import Server, main, run
3
3
 
4
- __version__ = "0.28.0"
4
+ __version__ = "0.29.0"
5
5
  __all__ = ["main", "run", "Config", "Server"]
@@ -27,7 +27,6 @@ from uvicorn.protocols.http.flow_control import (
27
27
  service_unavailable,
28
28
  )
29
29
  from uvicorn.protocols.utils import (
30
- ClientDisconnected,
31
30
  get_client_addr,
32
31
  get_local_addr,
33
32
  get_path_with_query_string,
@@ -408,8 +407,6 @@ class RequestResponseCycle:
408
407
  result = await app( # type: ignore[func-returns-value]
409
408
  self.scope, self.receive, self.send
410
409
  )
411
- except ClientDisconnected:
412
- pass
413
410
  except BaseException as exc:
414
411
  msg = "Exception in ASGI application\n"
415
412
  self.logger.error(msg, exc_info=exc)
@@ -458,7 +455,7 @@ class RequestResponseCycle:
458
455
  await self.flow.drain()
459
456
 
460
457
  if self.disconnected:
461
- raise ClientDisconnected
458
+ return
462
459
 
463
460
  if not self.response_started:
464
461
  # Sending response status line and headers
@@ -29,7 +29,6 @@ from uvicorn.protocols.http.flow_control import (
29
29
  service_unavailable,
30
30
  )
31
31
  from uvicorn.protocols.utils import (
32
- ClientDisconnected,
33
32
  get_client_addr,
34
33
  get_local_addr,
35
34
  get_path_with_query_string,
@@ -412,8 +411,6 @@ class RequestResponseCycle:
412
411
  result = await app( # type: ignore[func-returns-value]
413
412
  self.scope, self.receive, self.send
414
413
  )
415
- except ClientDisconnected:
416
- pass
417
414
  except BaseException as exc:
418
415
  msg = "Exception in ASGI application\n"
419
416
  self.logger.error(msg, exc_info=exc)
@@ -462,7 +459,7 @@ class RequestResponseCycle:
462
459
  await self.flow.drain()
463
460
 
464
461
  if self.disconnected:
465
- raise ClientDisconnected
462
+ return
466
463
 
467
464
  if not self.response_started:
468
465
  # Sending response status line and headers
@@ -573,11 +570,6 @@ class RequestResponseCycle:
573
570
 
574
571
  if self.disconnected or self.response_complete:
575
572
  return {"type": "http.disconnect"}
576
-
577
- message: HTTPRequestEvent = {
578
- "type": "http.request",
579
- "body": self.body,
580
- "more_body": self.more_body,
581
- }
573
+ message: HTTPRequestEvent = {"type": "http.request", "body": self.body, "more_body": self.more_body}
582
574
  self.body = b""
583
575
  return message
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import contextlib
4
5
  import logging
5
6
  import os
6
7
  import platform
@@ -11,7 +12,7 @@ import threading
11
12
  import time
12
13
  from email.utils import formatdate
13
14
  from types import FrameType
14
- from typing import TYPE_CHECKING, Sequence, Union
15
+ from typing import TYPE_CHECKING, Generator, Sequence, Union
15
16
 
16
17
  import click
17
18
 
@@ -57,11 +58,17 @@ class Server:
57
58
  self.force_exit = False
58
59
  self.last_notified = 0.0
59
60
 
61
+ self._captured_signals: list[int] = []
62
+
60
63
  def run(self, sockets: list[socket.socket] | None = None) -> None:
61
64
  self.config.setup_event_loop()
62
65
  return asyncio.run(self.serve(sockets=sockets))
63
66
 
64
67
  async def serve(self, sockets: list[socket.socket] | None = None) -> None:
68
+ with self.capture_signals():
69
+ await self._serve(sockets)
70
+
71
+ async def _serve(self, sockets: list[socket.socket] | None = None) -> None:
65
72
  process_id = os.getpid()
66
73
 
67
74
  config = self.config
@@ -70,8 +77,6 @@ class Server:
70
77
 
71
78
  self.lifespan = config.lifespan_class(config)
72
79
 
73
- self.install_signal_handlers()
74
-
75
80
  message = "Started server process [%d]"
76
81
  color_message = "Started server process [" + click.style("%d", fg="cyan") + "]"
77
82
  logger.info(message, process_id, extra={"color_message": color_message})
@@ -302,22 +307,28 @@ class Server:
302
307
  for server in self.servers:
303
308
  await server.wait_closed()
304
309
 
305
- def install_signal_handlers(self) -> None:
310
+ @contextlib.contextmanager
311
+ def capture_signals(self) -> Generator[None, None, None]:
312
+ # Signals can only be listened to from the main thread.
306
313
  if threading.current_thread() is not threading.main_thread():
307
- # Signals can only be listened to from the main thread.
314
+ yield
308
315
  return
309
-
310
- loop = asyncio.get_event_loop()
311
-
316
+ # always use signal.signal, even if loop.add_signal_handler is available
317
+ # this allows to restore previous signal handlers later on
318
+ original_handlers = {sig: signal.signal(sig, self.handle_exit) for sig in HANDLED_SIGNALS}
312
319
  try:
313
- for sig in HANDLED_SIGNALS:
314
- loop.add_signal_handler(sig, self.handle_exit, sig, None)
315
- except NotImplementedError: # pragma: no cover
316
- # Windows
317
- for sig in HANDLED_SIGNALS:
318
- signal.signal(sig, self.handle_exit)
320
+ yield
321
+ finally:
322
+ for sig, handler in original_handlers.items():
323
+ signal.signal(sig, handler)
324
+ # If we did gracefully shut down due to a signal, try to
325
+ # trigger the expected behaviour now; multiple signals would be
326
+ # done LIFO, see https://stackoverflow.com/questions/48434964
327
+ for captured_signal in reversed(self._captured_signals):
328
+ signal.raise_signal(captured_signal)
319
329
 
320
330
  def handle_exit(self, sig: int, frame: FrameType | None) -> None:
331
+ self._captured_signals.append(sig)
321
332
  if self.should_exit and sig == signal.SIGINT:
322
333
  self.force_exit = True
323
334
  else:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes