Repository: encode/uvicorn

Issue:
Add support for --reload to monitor additional file types.
The "reload" process currently only monitors ".py" files in various directories. I have a changes that will pass in a list of additional "reload_suffixes" that the process will monitor. This allows the service to monitor data files in addition to code files.

Any feedback on whether this is useful to others?

<!-- POLAR PLEDGE BADGE START -->
> [!IMPORTANT]
> - We're using [Polar.sh](https://polar.sh/encode) so you can upvote and help fund this issue.
> - We receive the funding once the issue is completed & confirmed by you.
> - Thank you in advance for helping prioritize & fund our backlog.

<a href="https://polar.sh/encode/uvicorn/issues/528">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/encode/uvicorn/issues/528/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/encode/uvicorn/issues/528/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion:

Resolving patch:
diff --git a/uvicorn/supervisors/statreload.py b/uvicorn/supervisors/statreload.py
--- a/uvicorn/supervisors/statreload.py
+++ b/uvicorn/supervisors/statreload.py
@@ -14,7 +14,7 @@ def __init__(self, config, target, sockets):
         self.mtimes = {}
 
     def should_restart(self):
-        for filename in self.iter_py_files():
+        for filename in self.iter_files():
             try:
                 mtime = os.path.getmtime(filename)
             except OSError:  # pragma: nocover
@@ -33,9 +33,9 @@ def should_restart(self):
                 return True
         return False
 
-    def iter_py_files(self):
+    def iter_files(self):
         for reload_dir in self.config.reload_dirs:
             for subdir, dirs, files in os.walk(reload_dir):
                 for file in files:
-                    if file.endswith(".py"):
+                    if not file.startswith("."):
                         yield subdir + os.sep + file


Test patch:
diff --git a/tests/supervisors/__init__.py b/tests/supervisors/__init__.py
new file mode 100644
--- /dev/null
+++ b/tests/supervisors/__init__.py
@@ -0,0 +1,5 @@
+WATCHED_FILES = (
+    "example.py",
+    "example.html",
+    "example.graphql",
+)
diff --git a/tests/supervisors/test_statreload.py b/tests/supervisors/test_statreload.py
--- a/tests/supervisors/test_statreload.py
+++ b/tests/supervisors/test_statreload.py
@@ -3,9 +3,13 @@
 import time
 from pathlib import Path
 
+import pytest
+
 from uvicorn.config import Config
 from uvicorn.supervisors.statreload import StatReload
 
+from . import WATCHED_FILES
+
 
 def run(sockets):
     pass
@@ -24,8 +28,9 @@ def test_statreload():
     reloader.run()
 
 
-def test_should_reload(tmpdir):
-    update_file = Path(os.path.join(str(tmpdir), "example.py"))
+@pytest.mark.parametrize("filename", WATCHED_FILES)
+def test_should_reload_when_watched_file_is_changed(tmpdir, filename):
+    update_file = Path(tmpdir) / filename
     update_file.touch()
 
     working_dir = os.getcwd()
@@ -45,3 +50,26 @@ def test_should_reload(tmpdir):
         reloader.shutdown()
     finally:
         os.chdir(working_dir)
+
+
+def test_should_not_reload_when_dot_file_is_changed(tmpdir):
+    update_file = Path(tmpdir) / ".dotted"
+    update_file.touch()
+
+    working_dir = os.getcwd()
+    os.chdir(str(tmpdir))
+    try:
+        config = Config(app=None, reload=True)
+        reloader = StatReload(config, target=run, sockets=[])
+        reloader.signal_handler(sig=signal.SIGINT, frame=None)
+        reloader.startup()
+
+        assert not reloader.should_restart()
+        time.sleep(0.1)
+        update_file.touch()
+        assert not reloader.should_restart()
+
+        reloader.restart()
+        reloader.shutdown()
+    finally:
+        os.chdir(working_dir)
diff --git a/tests/supervisors/test_watchgodreload.py b/tests/supervisors/test_watchgodreload.py
--- a/tests/supervisors/test_watchgodreload.py
+++ b/tests/supervisors/test_watchgodreload.py
@@ -3,9 +3,13 @@
 import time
 from pathlib import Path
 
+import pytest
+
 from uvicorn.config import Config
 from uvicorn.supervisors.watchgodreload import WatchGodReload
 
+from . import WATCHED_FILES
+
 
 def run(sockets):
     pass
@@ -18,9 +22,9 @@ def test_watchgodreload(certfile_and_keyfile):
     reloader.run()
 
 
-def test_should_reload_when_python_file_is_changed(tmpdir):
-    file = "example.py"
-    update_file = Path(os.path.join(str(tmpdir), file))
+@pytest.mark.parametrize("filename", WATCHED_FILES)
+def test_should_reload_when_file_is_changed(tmpdir, filename):
+    update_file = Path(tmpdir) / filename
     update_file.touch()
 
     working_dir = os.getcwd()
@@ -43,8 +47,7 @@ def test_should_reload_when_python_file_is_changed(tmpdir):
 
 
 def test_should_not_reload_when_dot_file_is_changed(tmpdir):
-    file = ".dotted"
-    update_file = Path(os.path.join(str(tmpdir), file))
+    update_file = Path(tmpdir) / ".dotted"
     update_file.touch()
 
     working_dir = os.getcwd()