fix(server): scope --reload watching to source dirs (#569)#611
fix(server): scope --reload watching to source dirs (#569)#611voidborne-d wants to merge 2 commits intoOpenBMB:mainfrom
--reload watching to source dirs (#569)#611Conversation
When ``python server_main.py --reload`` was used, uvicorn's default reload_dirs is the current working directory and StatReload walks the whole tree for ``*.py`` files. Agent-generated code under ``WareHouse/session_<uuid>/code_workspace/<file>.py`` therefore triggers a server restart mid-workflow; the webui is left waiting indefinitely and the in-flight session is cancelled. The project already ships a warning in both READMEs telling users to drop ``--reload``, but that is exactly the tool dev loops need. Fix: pass an explicit ``reload_dirs`` list containing only the server's Python source folders (check, entity, functions, mcp_example, runtime, schema_registry, server, tools, utils, workflow) and a matching ``reload_excludes`` set (WareHouse, logs, data, temp, node_modules) so watchfiles-backed installs also stop observing output directories. Users can override either list via repeatable ``--reload-dir`` and ``--reload-exclude`` flags. The reload-kwargs construction is extracted into a pure helper so the behaviour is unit-tested without spinning up a real server; nine new tests cover the default behaviour, user overrides, argparse wiring, and that the returned lists are defensive copies. README / README-zh have been updated to reflect the new default. Fixes OpenBMB#569
|
Great improvement overall — this should make the dev reload experience much safer. I tested it locally and found two small follow-up points that may be worth addressing.
Would you consider switching to directory-style excludes (e.g.
Could we either:
Optional: a small regression test for nested paths under |
Review feedback on OpenBMB#611: 1. `Path.match` (used by uvicorn to filter reload candidates) does not expand `**` on Python < 3.13, so the flat `WareHouse/*` default only caught direct children — the agent-generated files that actually triggered OpenBMB#569 live at `WareHouse/<project>/<file>` and deeper. Expand defaults to multi-depth glob patterns (up to 10 levels) for each excluded dir. 2. `--reload-exclude` is only honoured by the watchfiles-backed reloader; without watchfiles uvicorn silently falls back to StatReload and drops every exclude pattern. Add `watchfiles` to requirements.txt so the filter works out of the box, and log a WARNING when --reload runs without watchfiles installed instead of failing silently. Test coverage: - `TestExcludePatternDepth` parametrised over 4 WareHouse depths plus logs/data/node_modules cases; also asserts real source paths like `server/app.py` are NOT matched (no over-exclusion regression). - `TestWatchfilesWarning` covers the new `_watchfiles_available` probe and the WARNING log path. 20/20 tests pass; 8 new.
|
Thanks for the careful look, both points are valid — fixed in 646f0d6: 1) Pattern depth. You're right: 2) watchfiles dependency. Also correct — StatReload ignores exclude patterns entirely, so without watchfiles the
Final tally: 20/20 tests pass (8 new on top of the original 12). The primary defence remains the |
|
Thanks for taking the time to test this locally @zxrys — both of those would absolutely be real bugs if the code matched the PR summary verbatim. The summary is abbreviated; the actual implementation already handles both. Let me point to the exact lines. (1) # server_main.py L29-45
_RELOAD_EXCLUDE_DIRS = ("WareHouse", "logs", "data", "temp", "node_modules")
_RELOAD_EXCLUDE_MAX_DEPTH = 10
RELOAD_EXCLUDES = [
f"{d}{'/*' * (depth + 1)}"
for d in _RELOAD_EXCLUDE_DIRS
for depth in range(_RELOAD_EXCLUDE_MAX_DEPTH)
]So the resulting set is There's also a parametrized regression test in @pytest.mark.parametrize("relative_path", [
"WareHouse/foo.py",
"WareHouse/demo/foo.py",
"WareHouse/demo/sub/foo.py",
"WareHouse/a/b/c/d/e/foo.py",
])plus a negative test ( (2)
On the PR summary. The description used If there's any remaining behaviour you'd want covered (e.g. a test that asserts |
Summary
Closes #569.
When
python server_main.py --reloadis used during development, uvicorn defaultsreload_dirsto the current working directory andStatReloadrecursively walks the tree for*.pyfiles. Because the agent writes generated code intoWareHouse/session_<uuid>/code_workspace/<file>.py, every time a workflow produces code the server restarts mid-run, the in-flight session is cancelled, and the webui is left waiting indefinitely.Both READMEs already ship a warning telling users to drop
--reload, but that removes exactly the feedback loop developers need.Fix
server_main.pynow passes uvicorn:an explicit
reload_dirslist (RELOAD_SOURCE_DIRS) restricted to the project's Python source folders (check,entity,functions,mcp_example,runtime,schema_registry,server,tools,utils,workflow), soStatReload— which ignoresreload_excludesentirely — never walksWareHouse/orlogs/.a
reload_excludeslist for watchfiles-backed installs. Important: becausePath.matchon Python < 3.13 doesn't expand**and matches a pattern of N components only against the last N path parts, a bareWareHouse/*would only catch direct children. The list is therefore constructed as a depth-expansion (up to 10 levels) of the dir name set("WareHouse", "logs", "data", "temp", "node_modules"):This is why a 5-level-deep
WareHouse/a/b/c/d/e/foo.pystill matches.watchfilesadded torequirements.txtso fresh installs get the watchfiles-based reloader by default. For users whose env still lacks watchfiles,main()prints an explicit runtime warning when--reloadis passed, so silent StatReload fallback (which would ignore everyreload_excludespattern and re-introduce the bug) is an observable failure mode rather than a mystery.Users can override either list via repeatable
--reload-dir/--reload-excludeflags.The reload-kwargs construction is extracted into a pure helper (
build_reload_kwargs) so the behaviour is unit-testable without spinning up a server.Changes
server_main.py: defineRELOAD_SOURCE_DIRS,_RELOAD_EXCLUDE_DIRS+ depth expansion intoRELOAD_EXCLUDES,_watchfiles_available()probe,build_reload_kwargs()+build_parser()helpers, wire new CLI flags, emit missing-watchfiles warning, pass the resulting kwargs touvicorn.run(...).tests/test_server_main_reload.py: 9 tests including a parametrizedtest_nested_paths_are_excludedthat exercisesWareHouse/foo.py,WareHouse/demo/foo.py,WareHouse/demo/sub/foo.py,WareHouse/a/b/c/d/e/foo.py, plus a negativetest_source_files_not_excludedassertingserver/app.py/runtime/bootstrap.pystay watched. Uses animportlib.utilspec loader +monkeypatch.setitem(sys.modules, ...)so stubs forruntime.bootstrap/server.appare fully isolated from the rest of the test suite.requirements.txt: addwatchfiles.README.md/README-zh.md: replaced the "remove--reload" workaround note with a description of the new default and the override flags.Verification
build_parser().print_help()renders the new--reload-dir/--reload-excludeoptions.--reloadcode path.Validate YAML Workflows) does not watch these paths, so it should stay green.