Skip to content

[BUG] Dash.run(debug=True, backend="fastapi") reload subprocess silently ignores reload_dirs/reload_excludes kwargs #3962

Description

@biyani701

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2considered for next cyclebugsomething brokensize: 1

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions