uvicorn 0.32.1__tar.gz → 0.34.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.
- {uvicorn-0.32.1 → uvicorn-0.34.0}/PKG-INFO +2 -3
- {uvicorn-0.32.1 → uvicorn-0.34.0}/pyproject.toml +5 -13
- {uvicorn-0.32.1 → uvicorn-0.34.0}/requirements.txt +10 -11
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/conftest.py +0 -23
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/middleware/test_wsgi.py +3 -2
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/supervisors/test_reload.py +59 -90
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_cli.py +1 -1
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_server.py +4 -2
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/__init__.py +1 -1
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/_types.py +5 -17
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/config.py +3 -2
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/logging.py +1 -1
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/middleware/wsgi.py +1 -1
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/websockets/websockets_impl.py +2 -1
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/websockets/wsproto_impl.py +1 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/server.py +3 -5
- uvicorn-0.34.0/uvicorn/supervisors/__init__.py +16 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/supervisors/basereload.py +2 -1
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/supervisors/statreload.py +2 -1
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/workers.py +1 -1
- uvicorn-0.32.1/uvicorn/supervisors/__init__.py +0 -23
- uvicorn-0.32.1/uvicorn/supervisors/watchgodreload.py +0 -152
- {uvicorn-0.32.1 → uvicorn-0.34.0}/.gitignore +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/LICENSE.md +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/README.md +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/importer/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/importer/circular_import_a.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/importer/circular_import_b.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/importer/raise_import_error.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/importer/test_importer.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/middleware/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/middleware/test_logging.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/middleware/test_message_logger.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/middleware/test_proxy_headers.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/protocols/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/protocols/test_http.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/protocols/test_utils.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/protocols/test_websocket.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/response.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/supervisors/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/supervisors/test_multiprocess.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/supervisors/test_signal.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_auto_detection.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_config.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_default_headers.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_lifespan.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_main.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_ssl.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/test_subprocess.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/tests/utils.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/__main__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/_subprocess.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/importer.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/lifespan/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/lifespan/off.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/lifespan/on.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/loops/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/loops/asyncio.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/loops/auto.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/loops/uvloop.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/main.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/middleware/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/middleware/asgi2.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/middleware/message_logger.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/middleware/proxy_headers.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/http/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/http/auto.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/http/flow_control.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/http/h11_impl.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/http/httptools_impl.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/utils.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/websockets/__init__.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/protocols/websockets/auto.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/py.typed +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/supervisors/multiprocess.py +0 -0
- {uvicorn-0.32.1 → uvicorn-0.34.0}/uvicorn/supervisors/watchfilesreload.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: uvicorn
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.34.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
|
@@ -14,7 +14,6 @@ Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: BSD License
|
15
15
|
Classifier: Operating System :: OS Independent
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
18
17
|
Classifier: Programming Language :: Python :: 3.9
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
@@ -23,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
23
22
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
24
23
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
25
24
|
Classifier: Topic :: Internet :: WWW/HTTP
|
26
|
-
Requires-Python: >=3.
|
25
|
+
Requires-Python: >=3.9
|
27
26
|
Requires-Dist: click>=7.0
|
28
27
|
Requires-Dist: h11>=0.8
|
29
28
|
Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
|
@@ -8,10 +8,10 @@ dynamic = ["version"]
|
|
8
8
|
description = "The lightning-fast ASGI server."
|
9
9
|
readme = "README.md"
|
10
10
|
license = "BSD-3-Clause"
|
11
|
-
requires-python = ">=3.
|
11
|
+
requires-python = ">=3.9"
|
12
12
|
authors = [
|
13
13
|
{ name = "Tom Christie", email = "tom@tomchristie.com" },
|
14
|
-
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" }
|
14
|
+
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
|
15
15
|
]
|
16
16
|
classifiers = [
|
17
17
|
"Development Status :: 4 - Beta",
|
@@ -20,7 +20,6 @@ classifiers = [
|
|
20
20
|
"License :: OSI Approved :: BSD License",
|
21
21
|
"Operating System :: OS Independent",
|
22
22
|
"Programming Language :: Python :: 3",
|
23
|
-
"Programming Language :: Python :: 3.8",
|
24
23
|
"Programming Language :: Python :: 3.9",
|
25
24
|
"Programming Language :: Python :: 3.10",
|
26
25
|
"Programming Language :: Python :: 3.11",
|
@@ -38,7 +37,7 @@ dependencies = [
|
|
38
37
|
|
39
38
|
[project.optional-dependencies]
|
40
39
|
standard = [
|
41
|
-
"colorama>=0.4;sys_platform == 'win32'",
|
40
|
+
"colorama>=0.4; sys_platform == 'win32'",
|
42
41
|
"httptools>=0.6.3",
|
43
42
|
"python-dotenv>=0.13",
|
44
43
|
"PyYAML>=5.1",
|
@@ -60,11 +59,7 @@ Source = "https://github.com/encode/uvicorn"
|
|
60
59
|
path = "uvicorn/__init__.py"
|
61
60
|
|
62
61
|
[tool.hatch.build.targets.sdist]
|
63
|
-
include = [
|
64
|
-
"/uvicorn",
|
65
|
-
"/tests",
|
66
|
-
"/requirements.txt",
|
67
|
-
]
|
62
|
+
include = ["/uvicorn", "/tests", "/requirements.txt"]
|
68
63
|
|
69
64
|
[tool.ruff]
|
70
65
|
line-length = 120
|
@@ -94,10 +89,9 @@ addopts = "-rxXs --strict-config --strict-markers"
|
|
94
89
|
xfail_strict = true
|
95
90
|
filterwarnings = [
|
96
91
|
"error",
|
97
|
-
'ignore: \"watchgod\" is deprecated\, you should switch to watchfiles \(`pip install watchfiles`\)\.:DeprecationWarning',
|
98
92
|
"ignore:Uvicorn's native WSGI implementation is deprecated.*:DeprecationWarning",
|
99
93
|
"ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning",
|
100
|
-
"ignore: remove second argument of ws_handler:DeprecationWarning:websockets"
|
94
|
+
"ignore: remove second argument of ws_handler:DeprecationWarning:websockets",
|
101
95
|
]
|
102
96
|
|
103
97
|
[tool.coverage.run]
|
@@ -132,8 +126,6 @@ py-win32 = "sys_platform == 'win32'"
|
|
132
126
|
py-not-win32 = "sys_platform != 'win32'"
|
133
127
|
py-linux = "sys_platform == 'linux'"
|
134
128
|
py-darwin = "sys_platform == 'darwin'"
|
135
|
-
py-gte-38 = "sys_version_info >= (3, 8)"
|
136
|
-
py-lt-38 = "sys_version_info < (3, 8)"
|
137
129
|
py-gte-39 = "sys_version_info >= (3, 9)"
|
138
130
|
py-lt-39 = "sys_version_info < (3, 9)"
|
139
131
|
py-gte-310 = "sys_version_info >= (3, 10)"
|
@@ -10,23 +10,22 @@ wsproto==1.2.0
|
|
10
10
|
websockets==13.1
|
11
11
|
|
12
12
|
# Packaging
|
13
|
-
build==1.2.2
|
14
|
-
twine==
|
13
|
+
build==1.2.2.post1
|
14
|
+
twine==6.0.1
|
15
15
|
|
16
16
|
# Testing
|
17
|
-
ruff==0.
|
18
|
-
pytest==8.3.
|
17
|
+
ruff==0.8.3
|
18
|
+
pytest==8.3.4
|
19
19
|
pytest-mock==3.14.0
|
20
|
-
mypy==1.
|
20
|
+
mypy==1.13.0
|
21
21
|
types-click==7.1.8
|
22
22
|
types-pyyaml==6.0.12.20240917
|
23
|
-
trustme==1.
|
24
|
-
cryptography==
|
25
|
-
coverage==7.6.
|
23
|
+
trustme==1.2.0
|
24
|
+
cryptography==44.0.0
|
25
|
+
coverage==7.6.9
|
26
26
|
coverage-conditional-plugin==0.9.0
|
27
|
-
httpx==0.
|
28
|
-
watchgod==0.8.2
|
27
|
+
httpx==0.28.1
|
29
28
|
|
30
29
|
# Documentation
|
31
30
|
mkdocs==1.6.1
|
32
|
-
mkdocs-material==9.5.
|
31
|
+
mkdocs-material==9.5.48
|
@@ -9,8 +9,6 @@ from copy import deepcopy
|
|
9
9
|
from hashlib import md5
|
10
10
|
from pathlib import Path
|
11
11
|
from tempfile import TemporaryDirectory
|
12
|
-
from threading import Thread
|
13
|
-
from time import sleep
|
14
12
|
from typing import Any
|
15
13
|
from uuid import uuid4
|
16
14
|
|
@@ -214,27 +212,6 @@ def short_socket_name(tmp_path, tmp_path_factory): # pragma: py-win32
|
|
214
212
|
return
|
215
213
|
|
216
214
|
|
217
|
-
def sleep_touch(*paths: Path):
|
218
|
-
sleep(0.1)
|
219
|
-
for p in paths:
|
220
|
-
p.touch()
|
221
|
-
|
222
|
-
|
223
|
-
@pytest.fixture
|
224
|
-
def touch_soon():
|
225
|
-
threads = []
|
226
|
-
|
227
|
-
def start(*paths: Path):
|
228
|
-
thread = Thread(target=sleep_touch, args=paths)
|
229
|
-
thread.start()
|
230
|
-
threads.append(thread)
|
231
|
-
|
232
|
-
yield start
|
233
|
-
|
234
|
-
for t in threads:
|
235
|
-
t.join()
|
236
|
-
|
237
|
-
|
238
215
|
def _unused_port(socket_type: int) -> int:
|
239
216
|
"""Find an unused localhost port from 1024-65535 and return it."""
|
240
217
|
with contextlib.closing(socket.socket(type=socket_type)) as sock:
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import io
|
4
4
|
import sys
|
5
|
-
from
|
5
|
+
from collections.abc import AsyncGenerator
|
6
|
+
from typing import Callable
|
6
7
|
|
7
8
|
import a2wsgi
|
8
9
|
import httpx
|
@@ -72,7 +73,7 @@ async def test_wsgi_post(wsgi_middleware: Callable) -> None:
|
|
72
73
|
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
|
73
74
|
response = await client.post("/", json={"example": 123})
|
74
75
|
assert response.status_code == 200
|
75
|
-
assert response.text == '{"example":
|
76
|
+
assert response.text == '{"example":123}'
|
76
77
|
|
77
78
|
|
78
79
|
@pytest.mark.anyio
|
@@ -1,14 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import logging
|
4
3
|
import platform
|
5
4
|
import signal
|
6
5
|
import socket
|
7
6
|
import sys
|
7
|
+
from collections.abc import Generator
|
8
8
|
from pathlib import Path
|
9
|
+
from threading import Thread
|
9
10
|
from time import sleep
|
11
|
+
from typing import Callable
|
10
12
|
|
11
13
|
import pytest
|
14
|
+
from pytest_mock import MockerFixture
|
12
15
|
|
13
16
|
from tests.utils import as_cwd
|
14
17
|
from uvicorn.config import Config
|
@@ -20,11 +23,6 @@ try:
|
|
20
23
|
except ImportError: # pragma: no cover
|
21
24
|
WatchFilesReload = None # type: ignore[misc,assignment]
|
22
25
|
|
23
|
-
try:
|
24
|
-
from uvicorn.supervisors.watchgodreload import WatchGodReload
|
25
|
-
except ImportError: # pragma: no cover
|
26
|
-
WatchGodReload = None # type: ignore[misc,assignment]
|
27
|
-
|
28
26
|
|
29
27
|
# TODO: Investigate why this is flaky on MacOS M1.
|
30
28
|
skip_if_m1 = pytest.mark.skipif(
|
@@ -33,17 +31,34 @@ skip_if_m1 = pytest.mark.skipif(
|
|
33
31
|
)
|
34
32
|
|
35
33
|
|
36
|
-
def run(sockets):
|
34
|
+
def run(sockets: list[socket.socket] | None) -> None:
|
37
35
|
pass # pragma: no cover
|
38
36
|
|
39
37
|
|
38
|
+
def sleep_touch(*paths: Path):
|
39
|
+
sleep(0.1)
|
40
|
+
for p in paths:
|
41
|
+
p.touch()
|
42
|
+
|
43
|
+
|
44
|
+
@pytest.fixture
|
45
|
+
def touch_soon() -> Generator[Callable[[Path], None]]:
|
46
|
+
threads: list[Thread] = []
|
47
|
+
|
48
|
+
def start(*paths: Path) -> None:
|
49
|
+
thread = Thread(target=sleep_touch, args=paths)
|
50
|
+
thread.start()
|
51
|
+
threads.append(thread)
|
52
|
+
|
53
|
+
yield start
|
54
|
+
|
55
|
+
for t in threads:
|
56
|
+
t.join()
|
57
|
+
|
58
|
+
|
40
59
|
class TestBaseReload:
|
41
60
|
@pytest.fixture(autouse=True)
|
42
|
-
def setup(
|
43
|
-
self,
|
44
|
-
reload_directory_structure: Path,
|
45
|
-
reloader_class: type[BaseReload] | None,
|
46
|
-
):
|
61
|
+
def setup(self, reload_directory_structure: Path, reloader_class: type[BaseReload] | None):
|
47
62
|
if reloader_class is None: # pragma: no cover
|
48
63
|
pytest.skip("Needed dependency not installed")
|
49
64
|
self.reload_path = reload_directory_structure
|
@@ -52,17 +67,15 @@ class TestBaseReload:
|
|
52
67
|
def _setup_reloader(self, config: Config) -> BaseReload:
|
53
68
|
config.reload_delay = 0 # save time
|
54
69
|
|
55
|
-
|
56
|
-
with pytest.deprecated_call():
|
57
|
-
reloader = self.reloader_class(config, target=run, sockets=[])
|
58
|
-
else:
|
59
|
-
reloader = self.reloader_class(config, target=run, sockets=[])
|
70
|
+
reloader = self.reloader_class(config, target=run, sockets=[])
|
60
71
|
|
61
72
|
assert config.should_reload
|
62
73
|
reloader.startup()
|
63
74
|
return reloader
|
64
75
|
|
65
|
-
def _reload_tester(
|
76
|
+
def _reload_tester(
|
77
|
+
self, touch_soon: Callable[[Path], None], reloader: BaseReload, *files: Path
|
78
|
+
) -> list[Path] | None:
|
66
79
|
reloader.restart()
|
67
80
|
if WatchFilesReload is not None and isinstance(reloader, WatchFilesReload):
|
68
81
|
touch_soon(*files)
|
@@ -73,7 +86,7 @@ class TestBaseReload:
|
|
73
86
|
file.touch()
|
74
87
|
return next(reloader)
|
75
88
|
|
76
|
-
@pytest.mark.parametrize("reloader_class", [StatReload,
|
89
|
+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
|
77
90
|
def test_reloader_should_initialize(self) -> None:
|
78
91
|
"""
|
79
92
|
A basic sanity check.
|
@@ -86,8 +99,8 @@ class TestBaseReload:
|
|
86
99
|
reloader = self._setup_reloader(config)
|
87
100
|
reloader.shutdown()
|
88
101
|
|
89
|
-
@pytest.mark.parametrize("reloader_class", [StatReload,
|
90
|
-
def test_reload_when_python_file_is_changed(self, touch_soon
|
102
|
+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
|
103
|
+
def test_reload_when_python_file_is_changed(self, touch_soon: Callable[[Path], None]):
|
91
104
|
file = self.reload_path / "main.py"
|
92
105
|
|
93
106
|
with as_cwd(self.reload_path):
|
@@ -99,8 +112,8 @@ class TestBaseReload:
|
|
99
112
|
|
100
113
|
reloader.shutdown()
|
101
114
|
|
102
|
-
@pytest.mark.parametrize("reloader_class", [StatReload,
|
103
|
-
def test_should_reload_when_python_file_in_subdir_is_changed(self, touch_soon
|
115
|
+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
|
116
|
+
def test_should_reload_when_python_file_in_subdir_is_changed(self, touch_soon: Callable[[Path], None]):
|
104
117
|
file = self.reload_path / "app" / "sub" / "sub.py"
|
105
118
|
|
106
119
|
with as_cwd(self.reload_path):
|
@@ -111,8 +124,8 @@ class TestBaseReload:
|
|
111
124
|
|
112
125
|
reloader.shutdown()
|
113
126
|
|
114
|
-
@pytest.mark.parametrize("reloader_class", [WatchFilesReload
|
115
|
-
def test_should_not_reload_when_python_file_in_excluded_subdir_is_changed(self, touch_soon
|
127
|
+
@pytest.mark.parametrize("reloader_class", [WatchFilesReload])
|
128
|
+
def test_should_not_reload_when_python_file_in_excluded_subdir_is_changed(self, touch_soon: Callable[[Path], None]):
|
116
129
|
sub_dir = self.reload_path / "app" / "sub"
|
117
130
|
sub_file = sub_dir / "sub.py"
|
118
131
|
|
@@ -129,7 +142,7 @@ class TestBaseReload:
|
|
129
142
|
reloader.shutdown()
|
130
143
|
|
131
144
|
@pytest.mark.parametrize("reloader_class, result", [(StatReload, False), (WatchFilesReload, True)])
|
132
|
-
def test_reload_when_pattern_matched_file_is_changed(self, result: bool, touch_soon
|
145
|
+
def test_reload_when_pattern_matched_file_is_changed(self, result: bool, touch_soon: Callable[[Path], None]):
|
133
146
|
file = self.reload_path / "app" / "js" / "main.js"
|
134
147
|
|
135
148
|
with as_cwd(self.reload_path):
|
@@ -140,14 +153,10 @@ class TestBaseReload:
|
|
140
153
|
|
141
154
|
reloader.shutdown()
|
142
155
|
|
143
|
-
@pytest.mark.parametrize(
|
144
|
-
|
145
|
-
[
|
146
|
-
|
147
|
-
WatchGodReload,
|
148
|
-
],
|
149
|
-
)
|
150
|
-
def test_should_not_reload_when_exclude_pattern_match_file_is_changed(self, touch_soon) -> None:
|
156
|
+
@pytest.mark.parametrize("reloader_class", [pytest.param(WatchFilesReload, marks=skip_if_m1)])
|
157
|
+
def test_should_not_reload_when_exclude_pattern_match_file_is_changed(
|
158
|
+
self, touch_soon: Callable[[Path], None]
|
159
|
+
): # pragma: py-darwin
|
151
160
|
python_file = self.reload_path / "app" / "src" / "main.py"
|
152
161
|
css_file = self.reload_path / "app" / "css" / "main.css"
|
153
162
|
js_file = self.reload_path / "app" / "js" / "main.js"
|
@@ -167,8 +176,8 @@ class TestBaseReload:
|
|
167
176
|
|
168
177
|
reloader.shutdown()
|
169
178
|
|
170
|
-
@pytest.mark.parametrize("reloader_class", [StatReload,
|
171
|
-
def test_should_not_reload_when_dot_file_is_changed(self, touch_soon
|
179
|
+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
|
180
|
+
def test_should_not_reload_when_dot_file_is_changed(self, touch_soon: Callable[[Path], None]):
|
172
181
|
file = self.reload_path / ".dotted"
|
173
182
|
|
174
183
|
with as_cwd(self.reload_path):
|
@@ -179,8 +188,8 @@ class TestBaseReload:
|
|
179
188
|
|
180
189
|
reloader.shutdown()
|
181
190
|
|
182
|
-
@pytest.mark.parametrize("reloader_class", [StatReload,
|
183
|
-
def test_should_reload_when_directories_have_same_prefix(self, touch_soon
|
191
|
+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
|
192
|
+
def test_should_reload_when_directories_have_same_prefix(self, touch_soon: Callable[[Path], None]):
|
184
193
|
app_dir = self.reload_path / "app"
|
185
194
|
app_file = app_dir / "src" / "main.py"
|
186
195
|
app_first_dir = self.reload_path / "app_first"
|
@@ -201,13 +210,9 @@ class TestBaseReload:
|
|
201
210
|
|
202
211
|
@pytest.mark.parametrize(
|
203
212
|
"reloader_class",
|
204
|
-
[
|
205
|
-
StatReload,
|
206
|
-
WatchGodReload,
|
207
|
-
pytest.param(WatchFilesReload, marks=skip_if_m1),
|
208
|
-
],
|
213
|
+
[StatReload, pytest.param(WatchFilesReload, marks=skip_if_m1)],
|
209
214
|
)
|
210
|
-
def test_should_not_reload_when_only_subdirectory_is_watched(self, touch_soon
|
215
|
+
def test_should_not_reload_when_only_subdirectory_is_watched(self, touch_soon: Callable[[Path], None]):
|
211
216
|
app_dir = self.reload_path / "app"
|
212
217
|
app_dir_file = self.reload_path / "app" / "src" / "main.py"
|
213
218
|
root_file = self.reload_path / "main.py"
|
@@ -224,14 +229,8 @@ class TestBaseReload:
|
|
224
229
|
|
225
230
|
reloader.shutdown()
|
226
231
|
|
227
|
-
@pytest.mark.parametrize(
|
228
|
-
|
229
|
-
[
|
230
|
-
pytest.param(WatchFilesReload, marks=skip_if_m1),
|
231
|
-
WatchGodReload,
|
232
|
-
],
|
233
|
-
)
|
234
|
-
def test_override_defaults(self, touch_soon) -> None:
|
232
|
+
@pytest.mark.parametrize("reloader_class", [pytest.param(WatchFilesReload, marks=skip_if_m1)])
|
233
|
+
def test_override_defaults(self, touch_soon: Callable[[Path], None]) -> None: # pragma: py-darwin
|
235
234
|
dotted_file = self.reload_path / ".dotted"
|
236
235
|
dotted_dir_file = self.reload_path / ".dotted_dir" / "file.txt"
|
237
236
|
python_file = self.reload_path / "main.py"
|
@@ -252,14 +251,8 @@ class TestBaseReload:
|
|
252
251
|
|
253
252
|
reloader.shutdown()
|
254
253
|
|
255
|
-
@pytest.mark.parametrize(
|
256
|
-
|
257
|
-
[
|
258
|
-
pytest.param(WatchFilesReload, marks=skip_if_m1),
|
259
|
-
WatchGodReload,
|
260
|
-
],
|
261
|
-
)
|
262
|
-
def test_explicit_paths(self, touch_soon) -> None:
|
254
|
+
@pytest.mark.parametrize("reloader_class", [pytest.param(WatchFilesReload, marks=skip_if_m1)])
|
255
|
+
def test_explicit_paths(self, touch_soon: Callable[[Path], None]) -> None: # pragma: py-darwin
|
263
256
|
dotted_file = self.reload_path / ".dotted"
|
264
257
|
non_dotted_file = self.reload_path / "ext" / "ext.jpg"
|
265
258
|
python_file = self.reload_path / "main.py"
|
@@ -307,33 +300,9 @@ class TestBaseReload:
|
|
307
300
|
|
308
301
|
reloader.shutdown()
|
309
302
|
|
310
|
-
@pytest.mark.parametrize("reloader_class", [WatchGodReload])
|
311
|
-
def test_should_detect_new_reload_dirs(self, touch_soon, caplog: pytest.LogCaptureFixture, tmp_path: Path) -> None:
|
312
|
-
app_dir = tmp_path / "app"
|
313
|
-
app_file = app_dir / "file.py"
|
314
|
-
app_dir.mkdir()
|
315
|
-
app_file.touch()
|
316
|
-
app_first_dir = tmp_path / "app_first"
|
317
|
-
app_first_file = app_first_dir / "file.py"
|
318
|
-
|
319
|
-
with as_cwd(tmp_path):
|
320
|
-
config = Config(app="tests.test_config:asgi_app", reload=True, reload_includes=["app*"])
|
321
|
-
reloader = self._setup_reloader(config)
|
322
|
-
assert self._reload_tester(touch_soon, reloader, app_file)
|
323
|
-
|
324
|
-
app_first_dir.mkdir()
|
325
|
-
assert self._reload_tester(touch_soon, reloader, app_first_file)
|
326
|
-
assert caplog.records[-2].levelno == logging.INFO
|
327
|
-
assert (
|
328
|
-
caplog.records[-1].message == "WatchGodReload detected a new reload "
|
329
|
-
f"dir '{app_first_dir.name}' in '{tmp_path}'; Adding to watch list."
|
330
|
-
)
|
331
|
-
|
332
|
-
reloader.shutdown()
|
333
|
-
|
334
303
|
|
335
304
|
@pytest.mark.skipif(WatchFilesReload is None, reason="watchfiles not available")
|
336
|
-
def test_should_watch_one_dir_cwd(mocker, reload_directory_structure):
|
305
|
+
def test_should_watch_one_dir_cwd(mocker: MockerFixture, reload_directory_structure: Path):
|
337
306
|
mock_watch = mocker.patch("uvicorn.supervisors.watchfilesreload.watch")
|
338
307
|
app_dir = reload_directory_structure / "app"
|
339
308
|
app_first_dir = reload_directory_structure / "app_first"
|
@@ -350,7 +319,7 @@ def test_should_watch_one_dir_cwd(mocker, reload_directory_structure):
|
|
350
319
|
|
351
320
|
|
352
321
|
@pytest.mark.skipif(WatchFilesReload is None, reason="watchfiles not available")
|
353
|
-
def test_should_watch_separate_dirs_outside_cwd(mocker, reload_directory_structure):
|
322
|
+
def test_should_watch_separate_dirs_outside_cwd(mocker: MockerFixture, reload_directory_structure: Path):
|
354
323
|
mock_watch = mocker.patch("uvicorn.supervisors.watchfilesreload.watch")
|
355
324
|
app_dir = reload_directory_structure / "app"
|
356
325
|
app_first_dir = reload_directory_structure / "app_first"
|
@@ -368,7 +337,7 @@ def test_should_watch_separate_dirs_outside_cwd(mocker, reload_directory_structu
|
|
368
337
|
}
|
369
338
|
|
370
339
|
|
371
|
-
def test_display_path_relative(tmp_path):
|
340
|
+
def test_display_path_relative(tmp_path: Path):
|
372
341
|
with as_cwd(tmp_path):
|
373
342
|
p = tmp_path / "app" / "foobar.py"
|
374
343
|
# accept windows paths as wells as posix
|
@@ -380,8 +349,8 @@ def test_display_path_non_relative():
|
|
380
349
|
assert _display_path(p) in ("'/foo/bar.py'", "'\\foo\\bar.py'")
|
381
350
|
|
382
351
|
|
383
|
-
def test_base_reloader_run(tmp_path):
|
384
|
-
calls = []
|
352
|
+
def test_base_reloader_run(tmp_path: Path):
|
353
|
+
calls: list[str] = []
|
385
354
|
step = 0
|
386
355
|
|
387
356
|
class CustomReload(BaseReload):
|
@@ -411,7 +380,7 @@ def test_base_reloader_run(tmp_path):
|
|
411
380
|
assert calls == ["startup", "restart", "shutdown"]
|
412
381
|
|
413
382
|
|
414
|
-
def test_base_reloader_should_exit(tmp_path):
|
383
|
+
def test_base_reloader_should_exit(tmp_path: Path):
|
415
384
|
config = Config(app="tests.test_config:asgi_app", reload=True)
|
416
385
|
reloader = BaseReload(config, target=run, sockets=[])
|
417
386
|
assert not reloader.should_exit.is_set()
|
@@ -5,7 +5,9 @@ import contextlib
|
|
5
5
|
import logging
|
6
6
|
import signal
|
7
7
|
import sys
|
8
|
-
from
|
8
|
+
from collections.abc import Generator
|
9
|
+
from contextlib import AbstractContextManager
|
10
|
+
from typing import Callable
|
9
11
|
|
10
12
|
import httpx
|
11
13
|
import pytest
|
@@ -62,7 +64,7 @@ else: # pragma: py-win32
|
|
62
64
|
@pytest.mark.parametrize("exception_signal", signals)
|
63
65
|
@pytest.mark.parametrize("capture_signal", signal_captures)
|
64
66
|
async def test_server_interrupt(
|
65
|
-
exception_signal: signal.Signals, capture_signal: Callable[[signal.Signals],
|
67
|
+
exception_signal: signal.Signals, capture_signal: Callable[[signal.Signals], AbstractContextManager[None]]
|
66
68
|
): # pragma: py-win32
|
67
69
|
"""Test interrupting a Server that is run explicitly inside asyncio"""
|
68
70
|
|
@@ -32,20 +32,8 @@ from __future__ import annotations
|
|
32
32
|
|
33
33
|
import sys
|
34
34
|
import types
|
35
|
-
from
|
36
|
-
|
37
|
-
Awaitable,
|
38
|
-
Callable,
|
39
|
-
Iterable,
|
40
|
-
Literal,
|
41
|
-
MutableMapping,
|
42
|
-
Optional,
|
43
|
-
Protocol,
|
44
|
-
Tuple,
|
45
|
-
Type,
|
46
|
-
TypedDict,
|
47
|
-
Union,
|
48
|
-
)
|
35
|
+
from collections.abc import Awaitable, Iterable, MutableMapping
|
36
|
+
from typing import Any, Callable, Literal, Optional, Protocol, TypedDict, Union
|
49
37
|
|
50
38
|
if sys.version_info >= (3, 11): # pragma: py-lt-311
|
51
39
|
from typing import NotRequired
|
@@ -54,8 +42,8 @@ else: # pragma: py-gte-311
|
|
54
42
|
|
55
43
|
# WSGI
|
56
44
|
Environ = MutableMapping[str, Any]
|
57
|
-
ExcInfo =
|
58
|
-
StartResponse = Callable[[str, Iterable[
|
45
|
+
ExcInfo = tuple[type[BaseException], BaseException, Optional[types.TracebackType]]
|
46
|
+
StartResponse = Callable[[str, Iterable[tuple[str, str]], Optional[ExcInfo]], None]
|
59
47
|
WSGIApp = Callable[[Environ, StartResponse], Union[Iterable[bytes], BaseException]]
|
60
48
|
|
61
49
|
|
@@ -281,7 +269,7 @@ class ASGI2Protocol(Protocol):
|
|
281
269
|
async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ... # pragma: no cover
|
282
270
|
|
283
271
|
|
284
|
-
ASGI2Application =
|
272
|
+
ASGI2Application = type[ASGI2Protocol]
|
285
273
|
ASGI3Application = Callable[
|
286
274
|
[
|
287
275
|
Scope,
|
@@ -9,9 +9,10 @@ import os
|
|
9
9
|
import socket
|
10
10
|
import ssl
|
11
11
|
import sys
|
12
|
+
from collections.abc import Awaitable
|
12
13
|
from configparser import RawConfigParser
|
13
14
|
from pathlib import Path
|
14
|
-
from typing import IO, Any,
|
15
|
+
from typing import IO, Any, Callable, Literal
|
15
16
|
|
16
17
|
import click
|
17
18
|
|
@@ -137,7 +138,7 @@ def resolve_reload_patterns(patterns_list: list[str], directories_list: list[str
|
|
137
138
|
# Special case for the .* pattern, otherwise this would only match
|
138
139
|
# hidden directories which is probably undesired
|
139
140
|
if pattern == ".*":
|
140
|
-
continue
|
141
|
+
continue # pragma: py-darwin
|
141
142
|
patterns.append(pattern)
|
142
143
|
if is_dir(Path(pattern)):
|
143
144
|
directories.append(Path(pattern))
|
@@ -16,7 +16,7 @@ class ColourizedFormatter(logging.Formatter):
|
|
16
16
|
A custom log formatter class that:
|
17
17
|
|
18
18
|
* Outputs the LOG_LEVEL with an appropriate color.
|
19
|
-
* If a log call includes an `
|
19
|
+
* If a log call includes an `extra={"color_message": ...}` it will be used
|
20
20
|
for formatting the output, instead of the plain text message.
|
21
21
|
"""
|
22
22
|
|
@@ -3,7 +3,8 @@ from __future__ import annotations
|
|
3
3
|
import asyncio
|
4
4
|
import http
|
5
5
|
import logging
|
6
|
-
from
|
6
|
+
from collections.abc import Sequence
|
7
|
+
from typing import Any, Literal, Optional, cast
|
7
8
|
from urllib.parse import unquote
|
8
9
|
|
9
10
|
import websockets
|
@@ -224,6 +224,7 @@ class WSProtocol(asyncio.Protocol):
|
|
224
224
|
headers: list[tuple[bytes, bytes]] = [
|
225
225
|
(b"content-type", b"text/plain; charset=utf-8"),
|
226
226
|
(b"connection", b"close"),
|
227
|
+
(b"content-length", b"21"),
|
227
228
|
]
|
228
229
|
output = self.conn.send(wsproto.events.RejectConnection(status_code=500, headers=headers, has_body=True))
|
229
230
|
output += self.conn.send(wsproto.events.RejectData(data=b"Internal Server Error"))
|
@@ -10,9 +10,10 @@ import socket
|
|
10
10
|
import sys
|
11
11
|
import threading
|
12
12
|
import time
|
13
|
+
from collections.abc import Generator, Sequence
|
13
14
|
from email.utils import formatdate
|
14
15
|
from types import FrameType
|
15
|
-
from typing import TYPE_CHECKING,
|
16
|
+
from typing import TYPE_CHECKING, Union
|
16
17
|
|
17
18
|
import click
|
18
19
|
|
@@ -284,10 +285,7 @@ class Server:
|
|
284
285
|
len(self.server_state.tasks),
|
285
286
|
)
|
286
287
|
for t in self.server_state.tasks:
|
287
|
-
|
288
|
-
t.cancel()
|
289
|
-
else: # pragma: py-lt-39
|
290
|
-
t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded")
|
288
|
+
t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded")
|
291
289
|
|
292
290
|
# Send the lifespan shutdown event, and wait for application shutdown.
|
293
291
|
if not self.force_exit:
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
|
5
|
+
from uvicorn.supervisors.basereload import BaseReload
|
6
|
+
from uvicorn.supervisors.multiprocess import Multiprocess
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
ChangeReload: type[BaseReload]
|
10
|
+
else:
|
11
|
+
try:
|
12
|
+
from uvicorn.supervisors.watchfilesreload import WatchFilesReload as ChangeReload
|
13
|
+
except ImportError: # pragma: no cover
|
14
|
+
from uvicorn.supervisors.statreload import StatReload as ChangeReload
|
15
|
+
|
16
|
+
__all__ = ["Multiprocess", "ChangeReload"]
|
@@ -5,10 +5,11 @@ import os
|
|
5
5
|
import signal
|
6
6
|
import sys
|
7
7
|
import threading
|
8
|
+
from collections.abc import Iterator
|
8
9
|
from pathlib import Path
|
9
10
|
from socket import socket
|
10
11
|
from types import FrameType
|
11
|
-
from typing import Callable
|
12
|
+
from typing import Callable
|
12
13
|
|
13
14
|
import click
|
14
15
|
|
@@ -1,9 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import logging
|
4
|
+
from collections.abc import Iterator
|
4
5
|
from pathlib import Path
|
5
6
|
from socket import socket
|
6
|
-
from typing import Callable
|
7
|
+
from typing import Callable
|
7
8
|
|
8
9
|
from uvicorn.config import Config
|
9
10
|
from uvicorn.supervisors.basereload import BaseReload
|
@@ -11,7 +11,7 @@ from gunicorn.arbiter import Arbiter
|
|
11
11
|
from gunicorn.workers.base import Worker
|
12
12
|
|
13
13
|
from uvicorn.config import Config
|
14
|
-
from uvicorn.
|
14
|
+
from uvicorn.server import Server
|
15
15
|
|
16
16
|
warnings.warn(
|
17
17
|
"The `uvicorn.workers` module is deprecated. Please use `uvicorn-worker` package instead.\n"
|
@@ -1,23 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import TYPE_CHECKING
|
4
|
-
|
5
|
-
from uvicorn.supervisors.basereload import BaseReload
|
6
|
-
from uvicorn.supervisors.multiprocess import Multiprocess
|
7
|
-
|
8
|
-
if TYPE_CHECKING:
|
9
|
-
ChangeReload: type[BaseReload]
|
10
|
-
else:
|
11
|
-
try:
|
12
|
-
from uvicorn.supervisors.watchfilesreload import (
|
13
|
-
WatchFilesReload as ChangeReload,
|
14
|
-
)
|
15
|
-
except ImportError: # pragma: no cover
|
16
|
-
try:
|
17
|
-
from uvicorn.supervisors.watchgodreload import (
|
18
|
-
WatchGodReload as ChangeReload,
|
19
|
-
)
|
20
|
-
except ImportError:
|
21
|
-
from uvicorn.supervisors.statreload import StatReload as ChangeReload
|
22
|
-
|
23
|
-
__all__ = ["Multiprocess", "ChangeReload"]
|
@@ -1,152 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import logging
|
4
|
-
import warnings
|
5
|
-
from pathlib import Path
|
6
|
-
from socket import socket
|
7
|
-
from typing import TYPE_CHECKING, Callable
|
8
|
-
|
9
|
-
from watchgod import DefaultWatcher
|
10
|
-
|
11
|
-
from uvicorn.config import Config
|
12
|
-
from uvicorn.supervisors.basereload import BaseReload
|
13
|
-
|
14
|
-
if TYPE_CHECKING:
|
15
|
-
import os
|
16
|
-
|
17
|
-
DirEntry = os.DirEntry[str]
|
18
|
-
|
19
|
-
logger = logging.getLogger("uvicorn.error")
|
20
|
-
|
21
|
-
|
22
|
-
class CustomWatcher(DefaultWatcher):
|
23
|
-
def __init__(self, root_path: Path, config: Config):
|
24
|
-
default_includes = ["*.py"]
|
25
|
-
self.includes = [default for default in default_includes if default not in config.reload_excludes]
|
26
|
-
self.includes.extend(config.reload_includes)
|
27
|
-
self.includes = list(set(self.includes))
|
28
|
-
|
29
|
-
default_excludes = [".*", ".py[cod]", ".sw.*", "~*"]
|
30
|
-
self.excludes = [default for default in default_excludes if default not in config.reload_includes]
|
31
|
-
self.excludes.extend(config.reload_excludes)
|
32
|
-
self.excludes = list(set(self.excludes))
|
33
|
-
|
34
|
-
self.watched_dirs: dict[str, bool] = {}
|
35
|
-
self.watched_files: dict[str, bool] = {}
|
36
|
-
self.dirs_includes = set(config.reload_dirs)
|
37
|
-
self.dirs_excludes = set(config.reload_dirs_excludes)
|
38
|
-
self.resolved_root = root_path
|
39
|
-
super().__init__(str(root_path))
|
40
|
-
|
41
|
-
def should_watch_file(self, entry: DirEntry) -> bool:
|
42
|
-
cached_result = self.watched_files.get(entry.path)
|
43
|
-
if cached_result is not None:
|
44
|
-
return cached_result
|
45
|
-
|
46
|
-
entry_path = Path(entry)
|
47
|
-
|
48
|
-
# cwd is not verified through should_watch_dir, so we need to verify here
|
49
|
-
if entry_path.parent == Path.cwd() and Path.cwd() not in self.dirs_includes:
|
50
|
-
self.watched_files[entry.path] = False
|
51
|
-
return False
|
52
|
-
for include_pattern in self.includes:
|
53
|
-
if str(entry_path).endswith(include_pattern):
|
54
|
-
self.watched_files[entry.path] = True
|
55
|
-
return True
|
56
|
-
if entry_path.match(include_pattern):
|
57
|
-
for exclude_pattern in self.excludes:
|
58
|
-
if entry_path.match(exclude_pattern):
|
59
|
-
self.watched_files[entry.path] = False
|
60
|
-
return False
|
61
|
-
self.watched_files[entry.path] = True
|
62
|
-
return True
|
63
|
-
self.watched_files[entry.path] = False
|
64
|
-
return False
|
65
|
-
|
66
|
-
def should_watch_dir(self, entry: DirEntry) -> bool:
|
67
|
-
cached_result = self.watched_dirs.get(entry.path)
|
68
|
-
if cached_result is not None:
|
69
|
-
return cached_result
|
70
|
-
|
71
|
-
entry_path = Path(entry)
|
72
|
-
|
73
|
-
if entry_path in self.dirs_excludes:
|
74
|
-
self.watched_dirs[entry.path] = False
|
75
|
-
return False
|
76
|
-
|
77
|
-
for exclude_pattern in self.excludes:
|
78
|
-
if entry_path.match(exclude_pattern):
|
79
|
-
is_watched = False
|
80
|
-
if entry_path in self.dirs_includes:
|
81
|
-
is_watched = True
|
82
|
-
|
83
|
-
for directory in self.dirs_includes:
|
84
|
-
if directory in entry_path.parents:
|
85
|
-
is_watched = True
|
86
|
-
|
87
|
-
if is_watched:
|
88
|
-
logger.debug(
|
89
|
-
"WatchGodReload detected a new excluded dir '%s' in '%s'; " "Adding to exclude list.",
|
90
|
-
entry_path.relative_to(self.resolved_root),
|
91
|
-
str(self.resolved_root),
|
92
|
-
)
|
93
|
-
self.watched_dirs[entry.path] = False
|
94
|
-
self.dirs_excludes.add(entry_path)
|
95
|
-
return False
|
96
|
-
|
97
|
-
if entry_path in self.dirs_includes:
|
98
|
-
self.watched_dirs[entry.path] = True
|
99
|
-
return True
|
100
|
-
|
101
|
-
for directory in self.dirs_includes:
|
102
|
-
if directory in entry_path.parents:
|
103
|
-
self.watched_dirs[entry.path] = True
|
104
|
-
return True
|
105
|
-
|
106
|
-
for include_pattern in self.includes:
|
107
|
-
if entry_path.match(include_pattern):
|
108
|
-
logger.info(
|
109
|
-
"WatchGodReload detected a new reload dir '%s' in '%s'; " "Adding to watch list.",
|
110
|
-
str(entry_path.relative_to(self.resolved_root)),
|
111
|
-
str(self.resolved_root),
|
112
|
-
)
|
113
|
-
self.dirs_includes.add(entry_path)
|
114
|
-
self.watched_dirs[entry.path] = True
|
115
|
-
return True
|
116
|
-
|
117
|
-
self.watched_dirs[entry.path] = False
|
118
|
-
return False
|
119
|
-
|
120
|
-
|
121
|
-
class WatchGodReload(BaseReload):
|
122
|
-
def __init__(
|
123
|
-
self,
|
124
|
-
config: Config,
|
125
|
-
target: Callable[[list[socket] | None], None],
|
126
|
-
sockets: list[socket],
|
127
|
-
) -> None:
|
128
|
-
warnings.warn(
|
129
|
-
'"watchgod" is deprecated, you should switch ' "to watchfiles (`pip install watchfiles`).",
|
130
|
-
DeprecationWarning,
|
131
|
-
)
|
132
|
-
super().__init__(config, target, sockets)
|
133
|
-
self.reloader_name = "WatchGod"
|
134
|
-
self.watchers = []
|
135
|
-
reload_dirs = []
|
136
|
-
for directory in config.reload_dirs:
|
137
|
-
if Path.cwd() not in directory.parents:
|
138
|
-
reload_dirs.append(directory)
|
139
|
-
if Path.cwd() not in reload_dirs:
|
140
|
-
reload_dirs.append(Path.cwd())
|
141
|
-
for w in reload_dirs:
|
142
|
-
self.watchers.append(CustomWatcher(w.resolve(), self.config))
|
143
|
-
|
144
|
-
def should_restart(self) -> list[Path] | None:
|
145
|
-
self.pause()
|
146
|
-
|
147
|
-
for watcher in self.watchers:
|
148
|
-
change = watcher.check()
|
149
|
-
if change != set():
|
150
|
-
return list({Path(c[1]) for c in change})
|
151
|
-
|
152
|
-
return 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
|