Skip to content

Comments

fix(run): hot-reload regenerates server.py on route changes#207

Closed
deanq wants to merge 7 commits intomainfrom
experiment-simplified
Closed

fix(run): hot-reload regenerates server.py on route changes#207
deanq wants to merge 7 commits intomainfrom
experiment-simplified

Conversation

@deanq
Copy link
Member

@deanq deanq commented Feb 18, 2026

Summary

  • Parent flash run process watches project .py files via watchfiles in a background thread and regenerates .flash/server.py on any change
  • Uvicorn now watches only .flash/server.py (--reload-dir .flash --reload-include server.py) instead of the whole project, so it reloads exactly once — after regeneration is complete
  • Watcher thread is stopped cleanly on KeyboardInterrupt or exception via stop_event + join(timeout=2)

Reload flow after fix:

user edits api/routes.py
  → parent thread detects change (watchfiles)
  → parent thread rescans + regenerates .flash/server.py
  → uvicorn detects .flash/server.py changed → reloads app
  → new routes visible

No double-reload. No infinite loop (parent ignores .flash/).

Test plan

  • make quality-check passes (998 + 34 tests, 69% coverage)
  • TestRunCommandHotReload — verifies uvicorn args point to .flash/server.py, watcher thread lifecycle
  • TestWatchAndRegenerate — verifies regeneration on .py changes, ignores non-.py files, scan errors don't crash watcher
  • Manual: start flash run, add a new @remote route, confirm uvicorn logs WatchFiles detected changes in '.flash/server.py' and new route returns 200

…covery

LB @Remote functions (with method= and path=) now return the decorated
function unwrapped with __is_lb_route_handler__=True. The function body
executes directly on the LB endpoint server rather than being dispatched
as a remote stub. QB stubs inside the body are unaffected.

Scanner gains three path utilities (file_to_url_prefix,
file_to_resource_name, file_to_module_path) that convert file paths to
URL prefixes, resource names, and dotted module paths respectively.
RemoteFunctionMetadata gains is_lb_route_handler to distinguish LB route
handlers from QB remote stubs during discovery.
Remove _serialize_routes, _create_mothership_resource, and
_create_mothership_from_explicit — all referenced unimported symbols and
caused F821 lint errors. The manifest now emits a flat resources dict
with file_path, local_path_prefix, and module_path per resource; no
is_mothership flag.
flash run now scans the project for all @Remote functions, generates
.flash/server.py with routes derived from file paths, and starts uvicorn
with --app-dir .flash/. Route convention: gpu_worker.py -> /gpu_worker/run
and /gpu_worker/run_sync; subdirectory files produce matching URL prefixes.

Cleanup on Ctrl+C is fixed: _cleanup_live_endpoints now reads
.runpod/resources.pkl written by the uvicorn subprocess and deprovisions
all live- prefixed endpoints, removing the dead in-process _SESSION_ENDPOINTS
approach which never received data from the subprocess.
…project validation

LBHandlerGenerator is now called from run_build() for all is_load_balanced
resources, wiring the build pipeline to the new module_path-based handler
generation. validate_project_structure switches from glob to rglob so
projects with files only in subdirectories (e.g. 00_multi_resource) are
not incorrectly rejected.

lb_handler_generator loses the mothership reconciliation lifespan
(StateManagerClient, reconcile_children) in favour of a clean
startup/shutdown lifespan.
is_deployed skips the health check when FLASH_IS_LIVE_PROVISIONING=true.
Newly created endpoints can fail RunPod's health API for a few seconds
after creation (propagation delay), causing get_or_deploy_resource to
trigger a spurious re-deploy on the second request (e.g. /run_sync
immediately after /run).

_payload_exclude now excludes template when templateId is already set.
After first deployment _do_deploy sets templateId on the config object
while the set_serverless_template validator has already set template at
construction time. Sending both fields in the same payload causes RunPod
to return 'You can only provide one of templateId or template.'

Also adds _get_module_path helper and injects FLASH_MODULE_PATH into LB
endpoint environment at deploy time so the deployed handler can import
the correct user module.
Parent process watches project .py files via watchfiles and regenerates
.flash/server.py on change. Uvicorn now watches only .flash/server.py
instead of the whole project, so it reloads exactly once per change
with the updated routes visible.

- Add _watch_and_regenerate() background thread using watchfiles
- Change --reload-dir from '.' to '.flash', --reload-include to 'server.py'
- Start watcher thread when reload=True, stop on KeyboardInterrupt/Exception
- Add TestRunCommandHotReload and TestWatchAndRegenerate test classes
@deanq deanq closed this Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant