uvicorn 0.28.1__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.1 → uvicorn-0.29.0}/PKG-INFO +1 -1
  2. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/__init__.py +1 -1
  3. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/server.py +25 -14
  4. {uvicorn-0.28.1 → uvicorn-0.29.0}/.gitignore +0 -0
  5. {uvicorn-0.28.1 → uvicorn-0.29.0}/LICENSE.md +0 -0
  6. {uvicorn-0.28.1 → uvicorn-0.29.0}/README.md +0 -0
  7. {uvicorn-0.28.1 → uvicorn-0.29.0}/pyproject.toml +0 -0
  8. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/__main__.py +0 -0
  9. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/_subprocess.py +0 -0
  10. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/_types.py +0 -0
  11. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/config.py +0 -0
  12. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/importer.py +0 -0
  13. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/lifespan/__init__.py +0 -0
  14. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/lifespan/off.py +0 -0
  15. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/lifespan/on.py +0 -0
  16. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/logging.py +0 -0
  17. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/loops/__init__.py +0 -0
  18. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/loops/asyncio.py +0 -0
  19. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/loops/auto.py +0 -0
  20. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/loops/uvloop.py +0 -0
  21. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/main.py +0 -0
  22. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/middleware/__init__.py +0 -0
  23. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/middleware/asgi2.py +0 -0
  24. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/middleware/message_logger.py +0 -0
  25. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/middleware/proxy_headers.py +0 -0
  26. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/middleware/wsgi.py +0 -0
  27. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/__init__.py +0 -0
  28. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/http/__init__.py +0 -0
  29. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/http/auto.py +0 -0
  30. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/http/flow_control.py +0 -0
  31. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/http/h11_impl.py +0 -0
  32. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/http/httptools_impl.py +0 -0
  33. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/utils.py +0 -0
  34. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/websockets/__init__.py +0 -0
  35. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/websockets/auto.py +0 -0
  36. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/websockets/websockets_impl.py +0 -0
  37. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/protocols/websockets/wsproto_impl.py +0 -0
  38. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/py.typed +0 -0
  39. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/supervisors/__init__.py +0 -0
  40. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/supervisors/basereload.py +0 -0
  41. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/supervisors/multiprocess.py +0 -0
  42. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/supervisors/statreload.py +0 -0
  43. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/supervisors/watchfilesreload.py +0 -0
  44. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/supervisors/watchgodreload.py +0 -0
  45. {uvicorn-0.28.1 → uvicorn-0.29.0}/uvicorn/workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: uvicorn
3
- Version: 0.28.1
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.1"
4
+ __version__ = "0.29.0"
5
5
  __all__ = ["main", "run", "Config", "Server"]
@@ -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