Summary
When using the FastAPI backend, app.run(host=..., port=..., debug=True, **kwargs) (main-thread, non-Jupyter path) triggers dash/backends/_fastapi.py::FastAPIBackend.run(), which spawns python -m uvicorn <module>:<app>.server --reload as a subprocess. Extra kwargs such as reload_dirs=[...] or reload_excludes=[...] are accepted by Dash.run(..., **flask_run_options) without error and flow straight into FastAPIBackend.run()'s **kwargs — but only kwargs.get("reload") is ever read there; every other reload-related kwarg is silently dropped when building the subprocess's CLI args. The comment directly above the subprocess.Popen(...) call even says:
# Add any other kwargs as CLI args if needed
proc = subprocess.Popen(uvicorn_args, env=env)
...but nothing implements it.
Practical effect: without an explicit --reload-dir, uvicorn/watchfiles fall back to watching the entire current working directory. In any project run with cwd above the app's own package (e.g. a monorepo, or simply python path/to/app/index.py invoked from a parent directory), the reload subprocess ends up recursively watching unrelated directories — including a shared virtualenv (.venv/site-packages/...) — so any unrelated package file being touched triggers a full, spurious app reload.
Reproduction
# repro.py -- run as `python repro.py` from a directory ABOVE this file,
# e.g.:
# mkdir -p repo_root/app_dir && cd repo_root
# (place this file at repo_root/app_dir/repro.py)
# python app_dir/repro.py
from dash import Dash, html
app = Dash(__name__, backend="fastapi")
app.layout = html.Div("hello")
if __name__ == "__main__":
app.run(
host="127.0.0.1",
port=8050,
debug=True,
reload_dirs=["app_dir"],
reload_excludes=["app_dir/tests/*"],
)
- Expected: the spawned
uvicorn --reload subprocess only watches app_dir/, excluding app_dir/tests/*.
- Actual:
reload_dirs/reload_excludes are silently ignored. The subprocess inherits the parent's cwd (repo_root/) and uvicorn defaults reload_dirs to [Path.cwd()], so it watches repo_root/ recursively -- including repo_root/.venv if one exists there.
Root cause
dash/backends/_fastapi.py, FastAPIBackend.run() (dash 4.4.1):
def run(self, dash_app: Dash, host, port, debug, **kwargs):
...
uvicorn_args = [
sys.executable, "-m", "uvicorn", app_path,
"--host", str(host), "--port", str(port),
]
if kwargs.get("reload"):
uvicorn_args.append("--reload")
dev_tools = dash_app._dev_tools
config = dict(
{"debug": debug} if debug else {"debug": False},
**{f"dev_tools_{k}": v for k, v in dev_tools.items()},
)
env = os.environ.copy()
env[_ENV_CONFIG] = json.dumps(config)
# Add any other kwargs as CLI args if needed
proc = subprocess.Popen(uvicorn_args, env=env)
proc.wait()
Only kwargs["reload"] is ever consulted. reload_dirs, reload_excludes, reload_includes (and anything else a caller passes through app.run(...)) reach this function but are never translated into --reload-dir / --reload-exclude / --reload-include CLI args for the child process.
Suggested fix
Forward at least the reload-scoping kwargs onto uvicorn_args, e.g.:
for d in kwargs.get("reload_dirs") or []:
uvicorn_args += ["--reload-dir", str(d)]
for p in kwargs.get("reload_excludes") or []:
uvicorn_args += ["--reload-exclude", str(p)]
for p in kwargs.get("reload_includes") or []:
uvicorn_args += ["--reload-include", str(p)]
Since this is silent today (no error, no warning), a caller has no way to discover the gap short of reading this source file directly. At minimum, forwarding these three would close the gap for the common case; more generally it might be worth warning on any kwarg that isn't recognized/forwarded here.
Environment
- dash 4.4.1 (
dash[fastapi], backend="fastapi")
- uvicorn 0.52.0
- Windows 11, Python 3.11 (the affected code path is not inside a platform-specific branch, so this should reproduce on any OS)
Related
Possibly adjacent to #3818 / #3912 (uvicorn --reload subprocess handling in the FastAPI backend also has callback double-registration issues there), but this is a distinct symptom -- silently dropped reload-scoping kwargs / overly broad default watch scope, not callback duplication.
Summary
When using the FastAPI backend,
app.run(host=..., port=..., debug=True, **kwargs)(main-thread, non-Jupyter path) triggersdash/backends/_fastapi.py::FastAPIBackend.run(), which spawnspython -m uvicorn <module>:<app>.server --reloadas a subprocess. Extra kwargs such asreload_dirs=[...]orreload_excludes=[...]are accepted byDash.run(..., **flask_run_options)without error and flow straight intoFastAPIBackend.run()'s**kwargs— but onlykwargs.get("reload")is ever read there; every other reload-related kwarg is silently dropped when building the subprocess's CLI args. The comment directly above thesubprocess.Popen(...)call even says:...but nothing implements it.
Practical effect: without an explicit
--reload-dir, uvicorn/watchfiles fall back to watching the entire current working directory. In any project run withcwdabove the app's own package (e.g. a monorepo, or simplypython path/to/app/index.pyinvoked from a parent directory), the reload subprocess ends up recursively watching unrelated directories — including a shared virtualenv (.venv/site-packages/...) — so any unrelated package file being touched triggers a full, spurious app reload.Reproduction
uvicorn --reloadsubprocess only watchesapp_dir/, excludingapp_dir/tests/*.reload_dirs/reload_excludesare silently ignored. The subprocess inherits the parent's cwd (repo_root/) and uvicorn defaultsreload_dirsto[Path.cwd()], so it watchesrepo_root/recursively -- includingrepo_root/.venvif one exists there.Root cause
dash/backends/_fastapi.py,FastAPIBackend.run()(dash 4.4.1):Only
kwargs["reload"]is ever consulted.reload_dirs,reload_excludes,reload_includes(and anything else a caller passes throughapp.run(...)) reach this function but are never translated into--reload-dir/--reload-exclude/--reload-includeCLI args for the child process.Suggested fix
Forward at least the reload-scoping kwargs onto
uvicorn_args, e.g.:Since this is silent today (no error, no warning), a caller has no way to discover the gap short of reading this source file directly. At minimum, forwarding these three would close the gap for the common case; more generally it might be worth warning on any kwarg that isn't recognized/forwarded here.
Environment
dash[fastapi],backend="fastapi")Related
Possibly adjacent to #3818 / #3912 (uvicorn
--reloadsubprocess handling in the FastAPI backend also has callback double-registration issues there), but this is a distinct symptom -- silently dropped reload-scoping kwargs / overly broad default watch scope, not callback duplication.