uvicorn 0.31.1__tar.gz → 0.32.1__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 (77) hide show
  1. {uvicorn-0.31.1 → uvicorn-0.32.1}/PKG-INFO +4 -4
  2. {uvicorn-0.31.1 → uvicorn-0.32.1}/pyproject.toml +2 -1
  3. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/test_http.py +2 -2
  4. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_server.py +27 -1
  5. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/__init__.py +1 -1
  6. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/h11_impl.py +1 -4
  7. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/httptools_impl.py +9 -1
  8. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/server.py +6 -2
  9. {uvicorn-0.31.1 → uvicorn-0.32.1}/.gitignore +0 -0
  10. {uvicorn-0.31.1 → uvicorn-0.32.1}/LICENSE.md +0 -0
  11. {uvicorn-0.31.1 → uvicorn-0.32.1}/README.md +0 -0
  12. {uvicorn-0.31.1 → uvicorn-0.32.1}/requirements.txt +0 -0
  13. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/__init__.py +0 -0
  14. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/conftest.py +0 -0
  15. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/__init__.py +0 -0
  16. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/circular_import_a.py +0 -0
  17. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/circular_import_b.py +0 -0
  18. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/raise_import_error.py +0 -0
  19. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/test_importer.py +0 -0
  20. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/__init__.py +0 -0
  21. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_logging.py +0 -0
  22. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_message_logger.py +0 -0
  23. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_proxy_headers.py +0 -0
  24. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_wsgi.py +0 -0
  25. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/__init__.py +0 -0
  26. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/test_utils.py +0 -0
  27. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/test_websocket.py +0 -0
  28. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/response.py +0 -0
  29. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/__init__.py +0 -0
  30. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/test_multiprocess.py +0 -0
  31. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/test_reload.py +0 -0
  32. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/test_signal.py +0 -0
  33. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_auto_detection.py +0 -0
  34. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_cli.py +0 -0
  35. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_config.py +0 -0
  36. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_default_headers.py +0 -0
  37. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_lifespan.py +0 -0
  38. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_main.py +0 -0
  39. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_ssl.py +0 -0
  40. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_subprocess.py +0 -0
  41. {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/utils.py +0 -0
  42. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/__main__.py +0 -0
  43. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/_subprocess.py +0 -0
  44. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/_types.py +0 -0
  45. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/config.py +0 -0
  46. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/importer.py +0 -0
  47. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/lifespan/__init__.py +0 -0
  48. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/lifespan/off.py +0 -0
  49. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/lifespan/on.py +0 -0
  50. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/logging.py +0 -0
  51. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/__init__.py +0 -0
  52. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/asyncio.py +0 -0
  53. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/auto.py +0 -0
  54. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/uvloop.py +0 -0
  55. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/main.py +0 -0
  56. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/__init__.py +0 -0
  57. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/asgi2.py +0 -0
  58. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/message_logger.py +0 -0
  59. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/proxy_headers.py +0 -0
  60. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/wsgi.py +0 -0
  61. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/__init__.py +0 -0
  62. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/__init__.py +0 -0
  63. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/auto.py +0 -0
  64. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/flow_control.py +0 -0
  65. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/utils.py +0 -0
  66. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/__init__.py +0 -0
  67. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/auto.py +0 -0
  68. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/websockets_impl.py +0 -0
  69. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/wsproto_impl.py +0 -0
  70. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/py.typed +0 -0
  71. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/__init__.py +0 -0
  72. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/basereload.py +0 -0
  73. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/multiprocess.py +0 -0
  74. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/statreload.py +0 -0
  75. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/watchfilesreload.py +0 -0
  76. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/watchgodreload.py +0 -0
  77. {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/workers.py +0 -0
@@ -1,14 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: uvicorn
3
- Version: 0.31.1
3
+ Version: 0.32.1
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
7
7
  Project-URL: Homepage, https://www.uvicorn.org/
8
8
  Project-URL: Source, https://github.com/encode/uvicorn
9
9
  Author-email: Tom Christie <tom@tomchristie.com>, Marcelo Trylesinski <marcelotryle@gmail.com>
10
- License-Expression: BSD-3-Clause
11
- License-File: LICENSE.md
10
+ License: BSD-3-Clause
12
11
  Classifier: Development Status :: 4 - Beta
13
12
  Classifier: Environment :: Web Environment
14
13
  Classifier: Intended Audience :: Developers
@@ -20,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
24
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Classifier: Topic :: Internet :: WWW/HTTP
@@ -29,7 +29,7 @@ Requires-Dist: h11>=0.8
29
29
  Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
30
30
  Provides-Extra: standard
31
31
  Requires-Dist: colorama>=0.4; (sys_platform == 'win32') and extra == 'standard'
32
- Requires-Dist: httptools>=0.5.0; extra == 'standard'
32
+ Requires-Dist: httptools>=0.6.3; extra == 'standard'
33
33
  Requires-Dist: python-dotenv>=0.13; extra == 'standard'
34
34
  Requires-Dist: pyyaml>=5.1; extra == 'standard'
35
35
  Requires-Dist: uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')) and extra == 'standard'
@@ -25,6 +25,7 @@ classifiers = [
25
25
  "Programming Language :: Python :: 3.10",
26
26
  "Programming Language :: Python :: 3.11",
27
27
  "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
28
29
  "Programming Language :: Python :: Implementation :: CPython",
29
30
  "Programming Language :: Python :: Implementation :: PyPy",
30
31
  "Topic :: Internet :: WWW/HTTP",
@@ -38,7 +39,7 @@ dependencies = [
38
39
  [project.optional-dependencies]
39
40
  standard = [
40
41
  "colorama>=0.4;sys_platform == 'win32'",
41
- "httptools>=0.5.0",
42
+ "httptools>=0.6.3",
42
43
  "python-dotenv>=0.13",
43
44
  "PyYAML>=5.1",
44
45
  "uvloop>=0.14.0,!=0.15.0,!=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')",
@@ -860,8 +860,8 @@ def asgi2app(scope: Scope):
860
860
  @pytest.mark.parametrize(
861
861
  "asgi2or3_app, expected_scopes",
862
862
  [
863
- (asgi3app, {"version": "3.0", "spec_version": "2.4"}),
864
- (asgi2app, {"version": "2.0", "spec_version": "2.4"}),
863
+ (asgi3app, {"version": "3.0", "spec_version": "2.3"}),
864
+ (asgi2app, {"version": "2.0", "spec_version": "2.3"}),
865
865
  ],
866
866
  )
867
867
  async def test_scopes(
@@ -2,15 +2,23 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import contextlib
5
+ import logging
5
6
  import signal
6
7
  import sys
7
8
  from typing import Callable, ContextManager, Generator
8
9
 
10
+ import httpx
9
11
  import pytest
10
12
 
13
+ from tests.utils import run_server
14
+ from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
11
15
  from uvicorn.config import Config
16
+ from uvicorn.protocols.http.h11_impl import H11Protocol
17
+ from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
12
18
  from uvicorn.server import Server
13
19
 
20
+ pytestmark = pytest.mark.anyio
21
+
14
22
 
15
23
  # asyncio does NOT allow raising in signal handlers, so to detect
16
24
  # raised signals raised a mutable `witness` receives the signal
@@ -37,6 +45,12 @@ async def dummy_app(scope, receive, send): # pragma: py-win32
37
45
  pass
38
46
 
39
47
 
48
+ async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
49
+ assert scope["type"] == "http"
50
+ await send({"type": "http.response.start", "status": 200, "headers": []})
51
+ await send({"type": "http.response.body", "body": b"", "more_body": False})
52
+
53
+
40
54
  if sys.platform == "win32": # pragma: py-not-win32
41
55
  signals = [signal.SIGBREAK]
42
56
  signal_captures = [capture_signal_sync]
@@ -45,7 +59,6 @@ else: # pragma: py-win32
45
59
  signal_captures = [capture_signal_sync, capture_signal_async]
46
60
 
47
61
 
48
- @pytest.mark.anyio
49
62
  @pytest.mark.parametrize("exception_signal", signals)
50
63
  @pytest.mark.parametrize("capture_signal", signal_captures)
51
64
  async def test_server_interrupt(
@@ -65,3 +78,16 @@ async def test_server_interrupt(
65
78
  assert witness
66
79
  # set by the server's graceful exit handler
67
80
  assert server.should_exit
81
+
82
+
83
+ async def test_request_than_limit_max_requests_warn_log(
84
+ unused_tcp_port: int, http_protocol_cls: type[H11Protocol | HttpToolsProtocol], caplog: pytest.LogCaptureFixture
85
+ ):
86
+ caplog.set_level(logging.WARNING, logger="uvicorn.error")
87
+ config = Config(app=app, limit_max_requests=1, port=unused_tcp_port, http=http_protocol_cls)
88
+ async with run_server(config):
89
+ async with httpx.AsyncClient() as client:
90
+ tasks = [client.get(f"http://127.0.0.1:{unused_tcp_port}") for _ in range(2)]
91
+ responses = await asyncio.gather(*tasks)
92
+ assert len(responses) == 2
93
+ assert "Maximum request limit of 1 exceeded. Terminating process." in caplog.text
@@ -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.31.1"
4
+ __version__ = "0.32.1"
5
5
  __all__ = ["main", "run", "Config", "Server"]
@@ -200,10 +200,7 @@ class H11Protocol(asyncio.Protocol):
200
200
  full_raw_path = self.root_path.encode("ascii") + raw_path
201
201
  self.scope = {
202
202
  "type": "http",
203
- "asgi": {
204
- "version": self.config.asgi_version,
205
- "spec_version": "2.4",
206
- },
203
+ "asgi": {"version": self.config.asgi_version, "spec_version": "2.3"},
207
204
  "http_version": event.http_version.decode("ascii"),
208
205
  "server": self.server,
209
206
  "client": self.client,
@@ -58,6 +58,14 @@ class HttpToolsProtocol(asyncio.Protocol):
58
58
  self.access_logger = logging.getLogger("uvicorn.access")
59
59
  self.access_log = self.access_logger.hasHandlers()
60
60
  self.parser = httptools.HttpRequestParser(self)
61
+
62
+ try:
63
+ # Enable dangerous leniencies to allow server to a response on the first request from a pipelined request.
64
+ self.parser.set_dangerous_leniencies(lenient_data_after_close=True)
65
+ except AttributeError: # pragma: no cover
66
+ # httptools < 0.6.3
67
+ pass
68
+
61
69
  self.ws_protocol_class = config.ws_protocol_class
62
70
  self.root_path = config.root_path
63
71
  self.limit_concurrency = config.limit_concurrency
@@ -214,7 +222,7 @@ class HttpToolsProtocol(asyncio.Protocol):
214
222
  self.headers = []
215
223
  self.scope = { # type: ignore[typeddict-item]
216
224
  "type": "http",
217
- "asgi": {"version": self.config.asgi_version, "spec_version": "2.4"},
225
+ "asgi": {"version": self.config.asgi_version, "spec_version": "2.3"},
218
226
  "http_version": "1.1",
219
227
  "server": self.server,
220
228
  "client": self.client,
@@ -250,8 +250,12 @@ class Server:
250
250
  # Determine if we should exit.
251
251
  if self.should_exit:
252
252
  return True
253
- if self.config.limit_max_requests is not None:
254
- return self.server_state.total_requests >= self.config.limit_max_requests
253
+
254
+ max_requests = self.config.limit_max_requests
255
+ if max_requests is not None and self.server_state.total_requests >= max_requests:
256
+ logger.warning(f"Maximum request limit of {max_requests} exceeded. Terminating process.")
257
+ return True
258
+
255
259
  return False
256
260
 
257
261
  async def shutdown(self, sockets: list[socket.socket] | None = None) -> None:
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes