Skip to content

Commit 3aa1d01

Browse files
authored
Remove WatchGod (#2536)
* Remove WatchGod * Readd missing pragma * Add py-darwin coverage
1 parent a3cc360 commit 3aa1d01

File tree

7 files changed

+64
-284
lines changed

7 files changed

+64
-284
lines changed

pyproject.toml

+3-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ license = "BSD-3-Clause"
1111
requires-python = ">=3.8"
1212
authors = [
1313
{ name = "Tom Christie", email = "[email protected]" },
14-
{ name = "Marcelo Trylesinski", email = "[email protected]" }
14+
{ name = "Marcelo Trylesinski", email = "[email protected]" },
1515
]
1616
classifiers = [
1717
"Development Status :: 4 - Beta",
@@ -60,11 +60,7 @@ Source = "https://github.com/encode/uvicorn"
6060
path = "uvicorn/__init__.py"
6161

6262
[tool.hatch.build.targets.sdist]
63-
include = [
64-
"/uvicorn",
65-
"/tests",
66-
"/requirements.txt",
67-
]
63+
include = ["/uvicorn", "/tests", "/requirements.txt"]
6864

6965
[tool.ruff]
7066
line-length = 120
@@ -94,10 +90,9 @@ addopts = "-rxXs --strict-config --strict-markers"
9490
xfail_strict = true
9591
filterwarnings = [
9692
"error",
97-
'ignore: \"watchgod\" is deprecated\, you should switch to watchfiles \(`pip install watchfiles`\)\.:DeprecationWarning',
9893
"ignore:Uvicorn's native WSGI implementation is deprecated.*:DeprecationWarning",
9994
"ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning",
100-
"ignore: remove second argument of ws_handler:DeprecationWarning:websockets"
95+
"ignore: remove second argument of ws_handler:DeprecationWarning:websockets",
10196
]
10297

10398
[tool.coverage.run]

requirements.txt

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ coverage==7.6.1; python_version < '3.9'
2626
coverage==7.6.4; python_version >= '3.9'
2727
coverage-conditional-plugin==0.9.0
2828
httpx==0.27.2
29-
watchgod==0.8.2
3029

3130
# Documentation
3231
mkdocs==1.6.1

tests/conftest.py

-23
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from hashlib import md5
1010
from pathlib import Path
1111
from tempfile import TemporaryDirectory
12-
from threading import Thread
13-
from time import sleep
1412
from typing import Any
1513
from uuid import uuid4
1614

@@ -214,27 +212,6 @@ def make_tmp_dir(base_dir):
214212
return
215213

216214

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-
238215
def _unused_port(socket_type: int) -> int:
239216
"""Find an unused localhost port from 1024-65535 and return it."""
240217
with contextlib.closing(socket.socket(type=socket_type)) as sock:

tests/supervisors/test_reload.py

+58-90
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
from __future__ import annotations
22

3-
import logging
43
import platform
54
import signal
65
import socket
76
import sys
87
from pathlib import Path
8+
from threading import Thread
99
from time import sleep
10+
from typing import Callable, Generator
1011

1112
import pytest
13+
from pytest_mock import MockerFixture
1214

1315
from tests.utils import as_cwd
1416
from uvicorn.config import Config
@@ -20,11 +22,6 @@
2022
except ImportError: # pragma: no cover
2123
WatchFilesReload = None # type: ignore[misc,assignment]
2224

23-
try:
24-
from uvicorn.supervisors.watchgodreload import WatchGodReload
25-
except ImportError: # pragma: no cover
26-
WatchGodReload = None # type: ignore[misc,assignment]
27-
2825

2926
# TODO: Investigate why this is flaky on MacOS M1.
3027
skip_if_m1 = pytest.mark.skipif(
@@ -33,17 +30,34 @@
3330
)
3431

3532

36-
def run(sockets):
33+
def run(sockets: list[socket.socket] | None) -> None:
3734
pass # pragma: no cover
3835

3936

37+
def sleep_touch(*paths: Path):
38+
sleep(0.1)
39+
for p in paths:
40+
p.touch()
41+
42+
43+
@pytest.fixture
44+
def touch_soon() -> Generator[Callable[[Path], None]]:
45+
threads: list[Thread] = []
46+
47+
def start(*paths: Path) -> None:
48+
thread = Thread(target=sleep_touch, args=paths)
49+
thread.start()
50+
threads.append(thread)
51+
52+
yield start
53+
54+
for t in threads:
55+
t.join()
56+
57+
4058
class TestBaseReload:
4159
@pytest.fixture(autouse=True)
42-
def setup(
43-
self,
44-
reload_directory_structure: Path,
45-
reloader_class: type[BaseReload] | None,
46-
):
60+
def setup(self, reload_directory_structure: Path, reloader_class: type[BaseReload] | None):
4761
if reloader_class is None: # pragma: no cover
4862
pytest.skip("Needed dependency not installed")
4963
self.reload_path = reload_directory_structure
@@ -52,17 +66,15 @@ def setup(
5266
def _setup_reloader(self, config: Config) -> BaseReload:
5367
config.reload_delay = 0 # save time
5468

55-
if self.reloader_class is WatchGodReload:
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=[])
69+
reloader = self.reloader_class(config, target=run, sockets=[])
6070

6171
assert config.should_reload
6272
reloader.startup()
6373
return reloader
6474

65-
def _reload_tester(self, touch_soon, reloader: BaseReload, *files: Path) -> list[Path] | None:
75+
def _reload_tester(
76+
self, touch_soon: Callable[[Path], None], reloader: BaseReload, *files: Path
77+
) -> list[Path] | None:
6678
reloader.restart()
6779
if WatchFilesReload is not None and isinstance(reloader, WatchFilesReload):
6880
touch_soon(*files)
@@ -73,7 +85,7 @@ def _reload_tester(self, touch_soon, reloader: BaseReload, *files: Path) -> list
7385
file.touch()
7486
return next(reloader)
7587

76-
@pytest.mark.parametrize("reloader_class", [StatReload, WatchGodReload, WatchFilesReload])
88+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
7789
def test_reloader_should_initialize(self) -> None:
7890
"""
7991
A basic sanity check.
@@ -86,8 +98,8 @@ def test_reloader_should_initialize(self) -> None:
8698
reloader = self._setup_reloader(config)
8799
reloader.shutdown()
88100

89-
@pytest.mark.parametrize("reloader_class", [StatReload, WatchGodReload, WatchFilesReload])
90-
def test_reload_when_python_file_is_changed(self, touch_soon) -> None:
101+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
102+
def test_reload_when_python_file_is_changed(self, touch_soon: Callable[[Path], None]):
91103
file = self.reload_path / "main.py"
92104

93105
with as_cwd(self.reload_path):
@@ -99,8 +111,8 @@ def test_reload_when_python_file_is_changed(self, touch_soon) -> None:
99111

100112
reloader.shutdown()
101113

102-
@pytest.mark.parametrize("reloader_class", [StatReload, WatchGodReload, WatchFilesReload])
103-
def test_should_reload_when_python_file_in_subdir_is_changed(self, touch_soon) -> None:
114+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
115+
def test_should_reload_when_python_file_in_subdir_is_changed(self, touch_soon: Callable[[Path], None]):
104116
file = self.reload_path / "app" / "sub" / "sub.py"
105117

106118
with as_cwd(self.reload_path):
@@ -111,8 +123,8 @@ def test_should_reload_when_python_file_in_subdir_is_changed(self, touch_soon) -
111123

112124
reloader.shutdown()
113125

114-
@pytest.mark.parametrize("reloader_class", [WatchFilesReload, WatchGodReload])
115-
def test_should_not_reload_when_python_file_in_excluded_subdir_is_changed(self, touch_soon) -> None:
126+
@pytest.mark.parametrize("reloader_class", [WatchFilesReload])
127+
def test_should_not_reload_when_python_file_in_excluded_subdir_is_changed(self, touch_soon: Callable[[Path], None]):
116128
sub_dir = self.reload_path / "app" / "sub"
117129
sub_file = sub_dir / "sub.py"
118130

@@ -129,7 +141,7 @@ def test_should_not_reload_when_python_file_in_excluded_subdir_is_changed(self,
129141
reloader.shutdown()
130142

131143
@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) -> None:
144+
def test_reload_when_pattern_matched_file_is_changed(self, result: bool, touch_soon: Callable[[Path], None]):
133145
file = self.reload_path / "app" / "js" / "main.js"
134146

135147
with as_cwd(self.reload_path):
@@ -140,14 +152,10 @@ def test_reload_when_pattern_matched_file_is_changed(self, result: bool, touch_s
140152

141153
reloader.shutdown()
142154

143-
@pytest.mark.parametrize(
144-
"reloader_class",
145-
[
146-
pytest.param(WatchFilesReload, marks=skip_if_m1),
147-
WatchGodReload,
148-
],
149-
)
150-
def test_should_not_reload_when_exclude_pattern_match_file_is_changed(self, touch_soon) -> None:
155+
@pytest.mark.parametrize("reloader_class", [pytest.param(WatchFilesReload, marks=skip_if_m1)])
156+
def test_should_not_reload_when_exclude_pattern_match_file_is_changed(
157+
self, touch_soon: Callable[[Path], None]
158+
): # pragma: py-darwin
151159
python_file = self.reload_path / "app" / "src" / "main.py"
152160
css_file = self.reload_path / "app" / "css" / "main.css"
153161
js_file = self.reload_path / "app" / "js" / "main.js"
@@ -167,8 +175,8 @@ def test_should_not_reload_when_exclude_pattern_match_file_is_changed(self, touc
167175

168176
reloader.shutdown()
169177

170-
@pytest.mark.parametrize("reloader_class", [StatReload, WatchGodReload, WatchFilesReload])
171-
def test_should_not_reload_when_dot_file_is_changed(self, touch_soon) -> None:
178+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
179+
def test_should_not_reload_when_dot_file_is_changed(self, touch_soon: Callable[[Path], None]):
172180
file = self.reload_path / ".dotted"
173181

174182
with as_cwd(self.reload_path):
@@ -179,8 +187,8 @@ def test_should_not_reload_when_dot_file_is_changed(self, touch_soon) -> None:
179187

180188
reloader.shutdown()
181189

182-
@pytest.mark.parametrize("reloader_class", [StatReload, WatchGodReload, WatchFilesReload])
183-
def test_should_reload_when_directories_have_same_prefix(self, touch_soon) -> None:
190+
@pytest.mark.parametrize("reloader_class", [StatReload, WatchFilesReload])
191+
def test_should_reload_when_directories_have_same_prefix(self, touch_soon: Callable[[Path], None]):
184192
app_dir = self.reload_path / "app"
185193
app_file = app_dir / "src" / "main.py"
186194
app_first_dir = self.reload_path / "app_first"
@@ -201,13 +209,9 @@ def test_should_reload_when_directories_have_same_prefix(self, touch_soon) -> No
201209

202210
@pytest.mark.parametrize(
203211
"reloader_class",
204-
[
205-
StatReload,
206-
WatchGodReload,
207-
pytest.param(WatchFilesReload, marks=skip_if_m1),
208-
],
212+
[StatReload, pytest.param(WatchFilesReload, marks=skip_if_m1)],
209213
)
210-
def test_should_not_reload_when_only_subdirectory_is_watched(self, touch_soon) -> None:
214+
def test_should_not_reload_when_only_subdirectory_is_watched(self, touch_soon: Callable[[Path], None]):
211215
app_dir = self.reload_path / "app"
212216
app_dir_file = self.reload_path / "app" / "src" / "main.py"
213217
root_file = self.reload_path / "main.py"
@@ -224,14 +228,8 @@ def test_should_not_reload_when_only_subdirectory_is_watched(self, touch_soon) -
224228

225229
reloader.shutdown()
226230

227-
@pytest.mark.parametrize(
228-
"reloader_class",
229-
[
230-
pytest.param(WatchFilesReload, marks=skip_if_m1),
231-
WatchGodReload,
232-
],
233-
)
234-
def test_override_defaults(self, touch_soon) -> None:
231+
@pytest.mark.parametrize("reloader_class", [pytest.param(WatchFilesReload, marks=skip_if_m1)])
232+
def test_override_defaults(self, touch_soon: Callable[[Path], None]) -> None: # pragma: py-darwin
235233
dotted_file = self.reload_path / ".dotted"
236234
dotted_dir_file = self.reload_path / ".dotted_dir" / "file.txt"
237235
python_file = self.reload_path / "main.py"
@@ -252,14 +250,8 @@ def test_override_defaults(self, touch_soon) -> None:
252250

253251
reloader.shutdown()
254252

255-
@pytest.mark.parametrize(
256-
"reloader_class",
257-
[
258-
pytest.param(WatchFilesReload, marks=skip_if_m1),
259-
WatchGodReload,
260-
],
261-
)
262-
def test_explicit_paths(self, touch_soon) -> None:
253+
@pytest.mark.parametrize("reloader_class", [pytest.param(WatchFilesReload, marks=skip_if_m1)])
254+
def test_explicit_paths(self, touch_soon: Callable[[Path], None]) -> None: # pragma: py-darwin
263255
dotted_file = self.reload_path / ".dotted"
264256
non_dotted_file = self.reload_path / "ext" / "ext.jpg"
265257
python_file = self.reload_path / "main.py"
@@ -307,33 +299,9 @@ def test_watchfiles_no_changes(self) -> None:
307299

308300
reloader.shutdown()
309301

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-
334302

335303
@pytest.mark.skipif(WatchFilesReload is None, reason="watchfiles not available")
336-
def test_should_watch_one_dir_cwd(mocker, reload_directory_structure):
304+
def test_should_watch_one_dir_cwd(mocker: MockerFixture, reload_directory_structure: Path):
337305
mock_watch = mocker.patch("uvicorn.supervisors.watchfilesreload.watch")
338306
app_dir = reload_directory_structure / "app"
339307
app_first_dir = reload_directory_structure / "app_first"
@@ -350,7 +318,7 @@ def test_should_watch_one_dir_cwd(mocker, reload_directory_structure):
350318

351319

352320
@pytest.mark.skipif(WatchFilesReload is None, reason="watchfiles not available")
353-
def test_should_watch_separate_dirs_outside_cwd(mocker, reload_directory_structure):
321+
def test_should_watch_separate_dirs_outside_cwd(mocker: MockerFixture, reload_directory_structure: Path):
354322
mock_watch = mocker.patch("uvicorn.supervisors.watchfilesreload.watch")
355323
app_dir = reload_directory_structure / "app"
356324
app_first_dir = reload_directory_structure / "app_first"
@@ -368,7 +336,7 @@ def test_should_watch_separate_dirs_outside_cwd(mocker, reload_directory_structu
368336
}
369337

370338

371-
def test_display_path_relative(tmp_path):
339+
def test_display_path_relative(tmp_path: Path):
372340
with as_cwd(tmp_path):
373341
p = tmp_path / "app" / "foobar.py"
374342
# accept windows paths as wells as posix
@@ -380,8 +348,8 @@ def test_display_path_non_relative():
380348
assert _display_path(p) in ("'/foo/bar.py'", "'\\foo\\bar.py'")
381349

382350

383-
def test_base_reloader_run(tmp_path):
384-
calls = []
351+
def test_base_reloader_run(tmp_path: Path):
352+
calls: list[str] = []
385353
step = 0
386354

387355
class CustomReload(BaseReload):
@@ -411,7 +379,7 @@ def should_restart(self):
411379
assert calls == ["startup", "restart", "shutdown"]
412380

413381

414-
def test_base_reloader_should_exit(tmp_path):
382+
def test_base_reloader_should_exit(tmp_path: Path):
415383
config = Config(app="tests.test_config:asgi_app", reload=True)
416384
reloader = BaseReload(config, target=run, sockets=[])
417385
assert not reloader.should_exit.is_set()

uvicorn/config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def resolve_reload_patterns(patterns_list: list[str], directories_list: list[str
137137
# Special case for the .* pattern, otherwise this would only match
138138
# hidden directories which is probably undesired
139139
if pattern == ".*":
140-
continue
140+
continue # pragma: py-darwin
141141
patterns.append(pattern)
142142
if is_dir(Path(pattern)):
143143
directories.append(Path(pattern))

0 commit comments

Comments
 (0)