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.
- {uvicorn-0.31.1 → uvicorn-0.32.1}/PKG-INFO +4 -4
- {uvicorn-0.31.1 → uvicorn-0.32.1}/pyproject.toml +2 -1
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/test_http.py +2 -2
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_server.py +27 -1
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/__init__.py +1 -1
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/h11_impl.py +1 -4
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/httptools_impl.py +9 -1
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/server.py +6 -2
- {uvicorn-0.31.1 → uvicorn-0.32.1}/.gitignore +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/LICENSE.md +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/README.md +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/requirements.txt +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/conftest.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/circular_import_a.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/circular_import_b.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/raise_import_error.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/importer/test_importer.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_logging.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_message_logger.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_proxy_headers.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/middleware/test_wsgi.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/test_utils.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/protocols/test_websocket.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/response.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/test_multiprocess.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/test_reload.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/supervisors/test_signal.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_auto_detection.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_cli.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_config.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_default_headers.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_lifespan.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_main.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_ssl.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/test_subprocess.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/tests/utils.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/__main__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/_subprocess.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/_types.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/config.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/importer.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/lifespan/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/lifespan/off.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/lifespan/on.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/logging.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/asyncio.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/auto.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/loops/uvloop.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/main.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/asgi2.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/message_logger.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/proxy_headers.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/middleware/wsgi.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/auto.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/http/flow_control.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/utils.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/auto.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/websockets_impl.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/protocols/websockets/wsproto_impl.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/py.typed +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/__init__.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/basereload.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/multiprocess.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/statreload.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/watchfilesreload.py +0 -0
- {uvicorn-0.31.1 → uvicorn-0.32.1}/uvicorn/supervisors/watchgodreload.py +0 -0
- {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.
|
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
|
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.
|
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.
|
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.
|
864
|
-
(asgi2app, {"version": "2.0", "spec_version": "2.
|
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
|
@@ -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.
|
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
|
-
|
254
|
-
|
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
|
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
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|