about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/uvicorn/supervisors
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/uvicorn/supervisors
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/uvicorn/supervisors')
-rw-r--r--.venv/lib/python3.12/site-packages/uvicorn/supervisors/__init__.py21
-rw-r--r--.venv/lib/python3.12/site-packages/uvicorn/supervisors/basereload.py127
-rw-r--r--.venv/lib/python3.12/site-packages/uvicorn/supervisors/multiprocess.py76
-rw-r--r--.venv/lib/python3.12/site-packages/uvicorn/supervisors/statreload.py55
-rw-r--r--.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchfilesreload.py96
-rw-r--r--.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchgodreload.py163
6 files changed, 538 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/uvicorn/supervisors/__init__.py b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/__init__.py
new file mode 100644
index 00000000..deaf12ed
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/__init__.py
@@ -0,0 +1,21 @@
+from typing import TYPE_CHECKING, Type
+
+from uvicorn.supervisors.basereload import BaseReload
+from uvicorn.supervisors.multiprocess import Multiprocess
+
+if TYPE_CHECKING:
+    ChangeReload: Type[BaseReload]
+else:
+    try:
+        from uvicorn.supervisors.watchfilesreload import (
+            WatchFilesReload as ChangeReload,
+        )
+    except ImportError:  # pragma: no cover
+        try:
+            from uvicorn.supervisors.watchgodreload import (
+                WatchGodReload as ChangeReload,
+            )
+        except ImportError:
+            from uvicorn.supervisors.statreload import StatReload as ChangeReload
+
+__all__ = ["Multiprocess", "ChangeReload"]
diff --git a/.venv/lib/python3.12/site-packages/uvicorn/supervisors/basereload.py b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/basereload.py
new file mode 100644
index 00000000..6e2e0c35
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/basereload.py
@@ -0,0 +1,127 @@
+from __future__ import annotations
+
+import logging
+import os
+import signal
+import sys
+import threading
+from pathlib import Path
+from socket import socket
+from types import FrameType
+from typing import Callable, Iterator
+
+import click
+
+from uvicorn._subprocess import get_subprocess
+from uvicorn.config import Config
+
+HANDLED_SIGNALS = (
+    signal.SIGINT,  # Unix signal 2. Sent by Ctrl+C.
+    signal.SIGTERM,  # Unix signal 15. Sent by `kill <pid>`.
+)
+
+logger = logging.getLogger("uvicorn.error")
+
+
+class BaseReload:
+    def __init__(
+        self,
+        config: Config,
+        target: Callable[[list[socket] | None], None],
+        sockets: list[socket],
+    ) -> None:
+        self.config = config
+        self.target = target
+        self.sockets = sockets
+        self.should_exit = threading.Event()
+        self.pid = os.getpid()
+        self.is_restarting = False
+        self.reloader_name: str | None = None
+
+    def signal_handler(self, sig: int, frame: FrameType | None) -> None:
+        """
+        A signal handler that is registered with the parent process.
+        """
+        if sys.platform == "win32" and self.is_restarting:
+            self.is_restarting = False  # pragma: py-not-win32
+        else:
+            self.should_exit.set()  # pragma: py-win32
+
+    def run(self) -> None:
+        self.startup()
+        for changes in self:
+            if changes:
+                logger.warning(
+                    "%s detected changes in %s. Reloading...",
+                    self.reloader_name,
+                    ", ".join(map(_display_path, changes)),
+                )
+                self.restart()
+
+        self.shutdown()
+
+    def pause(self) -> None:
+        if self.should_exit.wait(self.config.reload_delay):
+            raise StopIteration()
+
+    def __iter__(self) -> Iterator[list[Path] | None]:
+        return self
+
+    def __next__(self) -> list[Path] | None:
+        return self.should_restart()
+
+    def startup(self) -> None:
+        message = f"Started reloader process [{self.pid}] using {self.reloader_name}"
+        color_message = "Started reloader process [{}] using {}".format(
+            click.style(str(self.pid), fg="cyan", bold=True),
+            click.style(str(self.reloader_name), fg="cyan", bold=True),
+        )
+        logger.info(message, extra={"color_message": color_message})
+
+        for sig in HANDLED_SIGNALS:
+            signal.signal(sig, self.signal_handler)
+
+        self.process = get_subprocess(
+            config=self.config, target=self.target, sockets=self.sockets
+        )
+        self.process.start()
+
+    def restart(self) -> None:
+        if sys.platform == "win32":  # pragma: py-not-win32
+            self.is_restarting = True
+            assert self.process.pid is not None
+            os.kill(self.process.pid, signal.CTRL_C_EVENT)
+        else:  # pragma: py-win32
+            self.process.terminate()
+        self.process.join()
+
+        self.process = get_subprocess(
+            config=self.config, target=self.target, sockets=self.sockets
+        )
+        self.process.start()
+
+    def shutdown(self) -> None:
+        if sys.platform == "win32":
+            self.should_exit.set()  # pragma: py-not-win32
+        else:
+            self.process.terminate()  # pragma: py-win32
+        self.process.join()
+
+        for sock in self.sockets:
+            sock.close()
+
+        message = "Stopping reloader process [{}]".format(str(self.pid))
+        color_message = "Stopping reloader process [{}]".format(
+            click.style(str(self.pid), fg="cyan", bold=True)
+        )
+        logger.info(message, extra={"color_message": color_message})
+
+    def should_restart(self) -> list[Path] | None:
+        raise NotImplementedError("Reload strategies should override should_restart()")
+
+
+def _display_path(path: Path) -> str:
+    try:
+        return f"'{path.relative_to(Path.cwd())}'"
+    except ValueError:
+        return f"'{path}'"
diff --git a/.venv/lib/python3.12/site-packages/uvicorn/supervisors/multiprocess.py b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/multiprocess.py
new file mode 100644
index 00000000..153b3d65
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/multiprocess.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+import logging
+import os
+import signal
+import threading
+from multiprocessing.context import SpawnProcess
+from socket import socket
+from types import FrameType
+from typing import Callable
+
+import click
+
+from uvicorn._subprocess import get_subprocess
+from uvicorn.config import Config
+
+HANDLED_SIGNALS = (
+    signal.SIGINT,  # Unix signal 2. Sent by Ctrl+C.
+    signal.SIGTERM,  # Unix signal 15. Sent by `kill <pid>`.
+)
+
+logger = logging.getLogger("uvicorn.error")
+
+
+class Multiprocess:
+    def __init__(
+        self,
+        config: Config,
+        target: Callable[[list[socket] | None], None],
+        sockets: list[socket],
+    ) -> None:
+        self.config = config
+        self.target = target
+        self.sockets = sockets
+        self.processes: list[SpawnProcess] = []
+        self.should_exit = threading.Event()
+        self.pid = os.getpid()
+
+    def signal_handler(self, sig: int, frame: FrameType | None) -> None:
+        """
+        A signal handler that is registered with the parent process.
+        """
+        self.should_exit.set()
+
+    def run(self) -> None:
+        self.startup()
+        self.should_exit.wait()
+        self.shutdown()
+
+    def startup(self) -> None:
+        message = "Started parent process [{}]".format(str(self.pid))
+        color_message = "Started parent process [{}]".format(
+            click.style(str(self.pid), fg="cyan", bold=True)
+        )
+        logger.info(message, extra={"color_message": color_message})
+
+        for sig in HANDLED_SIGNALS:
+            signal.signal(sig, self.signal_handler)
+
+        for _idx in range(self.config.workers):
+            process = get_subprocess(
+                config=self.config, target=self.target, sockets=self.sockets
+            )
+            process.start()
+            self.processes.append(process)
+
+    def shutdown(self) -> None:
+        for process in self.processes:
+            process.terminate()
+            process.join()
+
+        message = "Stopping parent process [{}]".format(str(self.pid))
+        color_message = "Stopping parent process [{}]".format(
+            click.style(str(self.pid), fg="cyan", bold=True)
+        )
+        logger.info(message, extra={"color_message": color_message})
diff --git a/.venv/lib/python3.12/site-packages/uvicorn/supervisors/statreload.py b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/statreload.py
new file mode 100644
index 00000000..2e25dd4a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/statreload.py
@@ -0,0 +1,55 @@
+from __future__ import annotations
+
+import logging
+from pathlib import Path
+from socket import socket
+from typing import Callable, Iterator
+
+from uvicorn.config import Config
+from uvicorn.supervisors.basereload import BaseReload
+
+logger = logging.getLogger("uvicorn.error")
+
+
+class StatReload(BaseReload):
+    def __init__(
+        self,
+        config: Config,
+        target: Callable[[list[socket] | None], None],
+        sockets: list[socket],
+    ) -> None:
+        super().__init__(config, target, sockets)
+        self.reloader_name = "StatReload"
+        self.mtimes: dict[Path, float] = {}
+
+        if config.reload_excludes or config.reload_includes:
+            logger.warning(
+                "--reload-include and --reload-exclude have no effect unless "
+                "watchfiles is installed."
+            )
+
+    def should_restart(self) -> list[Path] | None:
+        self.pause()
+
+        for file in self.iter_py_files():
+            try:
+                mtime = file.stat().st_mtime
+            except OSError:  # pragma: nocover
+                continue
+
+            old_time = self.mtimes.get(file)
+            if old_time is None:
+                self.mtimes[file] = mtime
+                continue
+            elif mtime > old_time:
+                return [file]
+        return None
+
+    def restart(self) -> None:
+        self.mtimes = {}
+        return super().restart()
+
+    def iter_py_files(self) -> Iterator[Path]:
+        for reload_dir in self.config.reload_dirs:
+            for path in list(reload_dir.rglob("*.py")):
+                yield path.resolve()
diff --git a/.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchfilesreload.py b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchfilesreload.py
new file mode 100644
index 00000000..e1cb311f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchfilesreload.py
@@ -0,0 +1,96 @@
+from __future__ import annotations
+
+from pathlib import Path
+from socket import socket
+from typing import Callable
+
+from watchfiles import watch
+
+from uvicorn.config import Config
+from uvicorn.supervisors.basereload import BaseReload
+
+
+class FileFilter:
+    def __init__(self, config: Config):
+        default_includes = ["*.py"]
+        self.includes = [
+            default
+            for default in default_includes
+            if default not in config.reload_excludes
+        ]
+        self.includes.extend(config.reload_includes)
+        self.includes = list(set(self.includes))
+
+        default_excludes = [".*", ".py[cod]", ".sw.*", "~*"]
+        self.excludes = [
+            default
+            for default in default_excludes
+            if default not in config.reload_includes
+        ]
+        self.exclude_dirs = []
+        for e in config.reload_excludes:
+            p = Path(e)
+            try:
+                is_dir = p.is_dir()
+            except OSError:  # pragma: no cover
+                # gets raised on Windows for values like "*.py"
+                is_dir = False
+
+            if is_dir:
+                self.exclude_dirs.append(p)
+            else:
+                self.excludes.append(e)
+        self.excludes = list(set(self.excludes))
+
+    def __call__(self, path: Path) -> bool:
+        for include_pattern in self.includes:
+            if path.match(include_pattern):
+                if str(path).endswith(include_pattern):
+                    return True
+
+                for exclude_dir in self.exclude_dirs:
+                    if exclude_dir in path.parents:
+                        return False
+
+                for exclude_pattern in self.excludes:
+                    if path.match(exclude_pattern):
+                        return False
+
+                return True
+        return False
+
+
+class WatchFilesReload(BaseReload):
+    def __init__(
+        self,
+        config: Config,
+        target: Callable[[list[socket] | None], None],
+        sockets: list[socket],
+    ) -> None:
+        super().__init__(config, target, sockets)
+        self.reloader_name = "WatchFiles"
+        self.reload_dirs = []
+        for directory in config.reload_dirs:
+            if Path.cwd() not in directory.parents:
+                self.reload_dirs.append(directory)
+        if Path.cwd() not in self.reload_dirs:
+            self.reload_dirs.append(Path.cwd())
+
+        self.watch_filter = FileFilter(config)
+        self.watcher = watch(
+            *self.reload_dirs,
+            watch_filter=None,
+            stop_event=self.should_exit,
+            # using yield_on_timeout here mostly to make sure tests don't
+            # hang forever, won't affect the class's behavior
+            yield_on_timeout=True,
+        )
+
+    def should_restart(self) -> list[Path] | None:
+        self.pause()
+
+        changes = next(self.watcher)
+        if changes:
+            unique_paths = {Path(c[1]) for c in changes}
+            return [p for p in unique_paths if self.watch_filter(p)]
+        return None
diff --git a/.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchgodreload.py b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchgodreload.py
new file mode 100644
index 00000000..987909fd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/uvicorn/supervisors/watchgodreload.py
@@ -0,0 +1,163 @@
+from __future__ import annotations
+
+import logging
+import warnings
+from pathlib import Path
+from socket import socket
+from typing import TYPE_CHECKING, Callable
+
+from watchgod import DefaultWatcher
+
+from uvicorn.config import Config
+from uvicorn.supervisors.basereload import BaseReload
+
+if TYPE_CHECKING:
+    import os
+
+    DirEntry = os.DirEntry[str]
+
+logger = logging.getLogger("uvicorn.error")
+
+
+class CustomWatcher(DefaultWatcher):
+    def __init__(self, root_path: Path, config: Config):
+        default_includes = ["*.py"]
+        self.includes = [
+            default
+            for default in default_includes
+            if default not in config.reload_excludes
+        ]
+        self.includes.extend(config.reload_includes)
+        self.includes = list(set(self.includes))
+
+        default_excludes = [".*", ".py[cod]", ".sw.*", "~*"]
+        self.excludes = [
+            default
+            for default in default_excludes
+            if default not in config.reload_includes
+        ]
+        self.excludes.extend(config.reload_excludes)
+        self.excludes = list(set(self.excludes))
+
+        self.watched_dirs: dict[str, bool] = {}
+        self.watched_files: dict[str, bool] = {}
+        self.dirs_includes = set(config.reload_dirs)
+        self.dirs_excludes = set(config.reload_dirs_excludes)
+        self.resolved_root = root_path
+        super().__init__(str(root_path))
+
+    def should_watch_file(self, entry: "DirEntry") -> bool:
+        cached_result = self.watched_files.get(entry.path)
+        if cached_result is not None:
+            return cached_result
+
+        entry_path = Path(entry)
+
+        # cwd is not verified through should_watch_dir, so we need to verify here
+        if entry_path.parent == Path.cwd() and Path.cwd() not in self.dirs_includes:
+            self.watched_files[entry.path] = False
+            return False
+        for include_pattern in self.includes:
+            if str(entry_path).endswith(include_pattern):
+                self.watched_files[entry.path] = True
+                return True
+            if entry_path.match(include_pattern):
+                for exclude_pattern in self.excludes:
+                    if entry_path.match(exclude_pattern):
+                        self.watched_files[entry.path] = False
+                        return False
+                self.watched_files[entry.path] = True
+                return True
+        self.watched_files[entry.path] = False
+        return False
+
+    def should_watch_dir(self, entry: "DirEntry") -> bool:
+        cached_result = self.watched_dirs.get(entry.path)
+        if cached_result is not None:
+            return cached_result
+
+        entry_path = Path(entry)
+
+        if entry_path in self.dirs_excludes:
+            self.watched_dirs[entry.path] = False
+            return False
+
+        for exclude_pattern in self.excludes:
+            if entry_path.match(exclude_pattern):
+                is_watched = False
+                if entry_path in self.dirs_includes:
+                    is_watched = True
+
+                for directory in self.dirs_includes:
+                    if directory in entry_path.parents:
+                        is_watched = True
+
+                if is_watched:
+                    logger.debug(
+                        "WatchGodReload detected a new excluded dir '%s' in '%s'; "
+                        "Adding to exclude list.",
+                        entry_path.relative_to(self.resolved_root),
+                        str(self.resolved_root),
+                    )
+                self.watched_dirs[entry.path] = False
+                self.dirs_excludes.add(entry_path)
+                return False
+
+        if entry_path in self.dirs_includes:
+            self.watched_dirs[entry.path] = True
+            return True
+
+        for directory in self.dirs_includes:
+            if directory in entry_path.parents:
+                self.watched_dirs[entry.path] = True
+                return True
+
+        for include_pattern in self.includes:
+            if entry_path.match(include_pattern):
+                logger.info(
+                    "WatchGodReload detected a new reload dir '%s' in '%s'; "
+                    "Adding to watch list.",
+                    str(entry_path.relative_to(self.resolved_root)),
+                    str(self.resolved_root),
+                )
+                self.dirs_includes.add(entry_path)
+                self.watched_dirs[entry.path] = True
+                return True
+
+        self.watched_dirs[entry.path] = False
+        return False
+
+
+class WatchGodReload(BaseReload):
+    def __init__(
+        self,
+        config: Config,
+        target: Callable[[list[socket] | None], None],
+        sockets: list[socket],
+    ) -> None:
+        warnings.warn(
+            '"watchgod" is deprecated, you should switch '
+            "to watchfiles (`pip install watchfiles`).",
+            DeprecationWarning,
+        )
+        super().__init__(config, target, sockets)
+        self.reloader_name = "WatchGod"
+        self.watchers = []
+        reload_dirs = []
+        for directory in config.reload_dirs:
+            if Path.cwd() not in directory.parents:
+                reload_dirs.append(directory)
+        if Path.cwd() not in reload_dirs:
+            reload_dirs.append(Path.cwd())
+        for w in reload_dirs:
+            self.watchers.append(CustomWatcher(w.resolve(), self.config))
+
+    def should_restart(self) -> list[Path] | None:
+        self.pause()
+
+        for watcher in self.watchers:
+            change = watcher.check()
+            if change != set():
+                return list({Path(c[1]) for c in change})
+
+        return None