Skip to content

Commit 0a57fc7

Browse files
Evgeny Arshinovearshinov
authored andcommitted
watch_safely
1 parent 9298703 commit 0a57fc7

File tree

1 file changed

+47
-10
lines changed

1 file changed

+47
-10
lines changed

src/django_watchfiles/__init__.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,48 @@
11
from __future__ import annotations
22

33
import fnmatch
4+
import logging
45
import threading
6+
import time
57
from pathlib import Path
6-
from typing import Any
78
from typing import Callable
89
from typing import Generator
10+
from typing import Iterable
11+
from typing import Tuple
12+
from typing import TypeVar
913

1014
import watchfiles
1115
from django.utils import autoreload
1216

17+
logger = logging.getLogger("django_watchfiles")
18+
19+
20+
# Duplicate `FileChange` type from `watchfiles`, which is not exported
21+
FileChange = Tuple[watchfiles.Change, str]
22+
23+
24+
T = TypeVar("T")
25+
26+
27+
def watch_safely(f: Callable[[], Iterable[T]], default: T) -> Iterable[T]:
28+
"""
29+
Yield from `f()`, but when it fails, yield `default` once, log the exception and
30+
retry, unless there are 2 exceptions within 1 second, in which case the exception
31+
is raised.
32+
"""
33+
ts: float | None = None
34+
while True:
35+
try:
36+
yield from f()
37+
except Exception as e:
38+
current_ts = time.monotonic()
39+
if ts is not None and current_ts - ts < 1.0:
40+
# Exit after 2 exceptions within 1 second to avoid endlessly looping
41+
raise
42+
logger.warning(e, exc_info=True)
43+
ts = current_ts
44+
yield default
45+
1346

1447
class MutableWatcher:
1548
"""
@@ -34,17 +67,21 @@ def set_roots(self, roots: set[Path]) -> None:
3467
def stop(self) -> None:
3568
self.stop_event.set()
3669

37-
def __iter__(self) -> Generator[Any, None, None]: # TODO: better type
70+
def __iter__(self) -> Generator[set[FileChange], None, None]:
71+
no_changes: set[FileChange] = set()
3872
while True:
3973
self.change_event.clear()
40-
for changes in watchfiles.watch(
41-
*self.roots,
42-
watch_filter=self.filter,
43-
stop_event=self.stop_event,
44-
debounce=False,
45-
rust_timeout=100,
46-
yield_on_timeout=True,
47-
ignore_permission_denied=True,
74+
for changes in watch_safely(
75+
lambda: watchfiles.watch(
76+
*self.roots,
77+
watch_filter=self.filter,
78+
stop_event=self.stop_event,
79+
debounce=False,
80+
rust_timeout=100,
81+
yield_on_timeout=True,
82+
ignore_permission_denied=True,
83+
),
84+
default=no_changes,
4885
):
4986
if self.change_event.is_set():
5087
break

0 commit comments

Comments
 (0)