perf(proxy): warn when watchfiles is missing on --reload

Without watchfiles, uvicorn falls back to StatReload, which ignores reload_includes and stat-polls every *.py under cwd. Surface that at startup so config YAML and .env edits silently not reloading is diagnosable, and give the new WatchFilesReload canary test a clear failure message.
This commit is contained in:
mateo-berri 2026-06-11 04:44:28 +00:00
parent 04fb2a4cb6
commit e611e34bc0
No known key found for this signature in database
2 changed files with 35 additions and 1 deletions

View file

@ -1,5 +1,6 @@
# ruff: noqa: T201
import importlib
import importlib.util
import json
import os
import random
@ -239,6 +240,15 @@ class ProxyInitializationHelpers:
ignore `reload_includes`."""
from litellm._logging import verbose_proxy_logger
if importlib.util.find_spec("watchfiles") is None:
verbose_proxy_logger.warning(
"LiteLLM --reload: watchfiles is not installed, so uvicorn falls back "
"to its StatReload poller. StatReload stat-scans every *.py under the "
"current directory several times a second (high CPU) and ignores "
"reload_includes, so --config YAML and .env edits will not trigger "
"reloads. Install it with `pip install 'litellm[proxy]'`."
)
uvicorn_args.update(ProxyInitializationHelpers._get_reload_options(config_path))
os.environ["LITELLM_DEV_ENV_HOT_RELOAD"] = "True"
verbose_proxy_logger.warning(

View file

@ -167,7 +167,10 @@ class TestProxyInitializationHelpers:
def test_reload_uses_watchfiles_reloader(self):
from uvicorn.supervisors import ChangeReload
assert ChangeReload.__name__ == "WatchFilesReload"
assert ChangeReload.__name__ == "WatchFilesReload", (
"uvicorn fell back to StatReload; watchfiles must ship with the proxy "
"extra so --reload selects the event-driven WatchFilesReload"
)
def test_reload_includes_match_config_and_env_under_watchfiles(
self, tmp_path, monkeypatch
@ -217,6 +220,27 @@ class TestProxyInitializationHelpers:
assert "config.yaml" in uvicorn_args["reload_includes"]
def test_configure_dev_reload_warns_when_watchfiles_missing(
self, tmp_path, monkeypatch
):
config_file = tmp_path / "config.yaml"
config_file.write_text("model_list: []\n")
monkeypatch.chdir(tmp_path)
uvicorn_args: dict = {}
with (
patch(
"litellm.proxy.proxy_cli.importlib.util.find_spec", return_value=None
),
patch("litellm._logging.verbose_proxy_logger.warning") as mock_warning,
):
ProxyInitializationHelpers._configure_dev_reload(
uvicorn_args, str(config_file)
)
warnings = [call.args[0].lower() for call in mock_warning.call_args_list]
assert any("watchfiles" in text for text in warnings)
def test_dev_env_hot_reload_enabled_reads_flag(self, monkeypatch):
import litellm