Hot reload development

Save a file, and the app restarts with the new code.

Stario does not ship a watcher; watchfiles does the job and needs no configuration beyond the command:

uv run watchfiles 'python -m app.main' app/

Add it once with uv add --dev watchfiles — a scaffolded app already has it, and uv run --with watchfiles watchfiles ... works without adding anything.

The first argument is the command to run, the second is the directory to watch. Run the app as watchfiles' direct child — python, not uv run python. watchfiles kills only its child; a uv run wrapper dies and leaves an orphaned server holding the port and any litewriter claim, so every later restart stalls. It is a restart, not an in-process swap of modules. Every reload is a fresh interpreter, a clean import, and a fresh read of the STARIO_* variables, so what you see after a save is exactly what python -m app.main would start. A one-file app runs uv run watchfiles 'python main.py' . the same way.

What it watches#

The directories you pass, recursively. List several roots as extra arguments, and exclude paths with a comma-separated --ignore-paths list:

uv run watchfiles 'python -m app.main' app/ static/ --ignore-paths data,static/generated

Ignoring the data directory keeps SQLite files from restarting the app every time it writes to them — a running app would otherwise restart itself. To limit restarts to Python files only, add --filter python.

When the import fails#

If the new code does not import, the process exits and watchfiles waits for the next change. Fix the file and save again; the restart shows the traceback or the page.

Live pages across a restart#

A restart closes every connection, including the open stream a live page holds. Datastar reconnects when the stream was opened with retry="always", the new process sends a fresh render, and the page catches up without a refresh. Nothing in your code needs to know a reload happened. Realtime tiles has the one line that makes this work: data.init(at.get(SUBSCRIBE.href(), retry="always")).

By the way#

  • The port and every other server setting are environment variables, not flags: STARIO_PORT=8001 uv run watchfiles 'python -m app.main' app/. Configuration lists them.

  • A busy port or a WriterBusy from litewriter after a reload means an old process still runs — usually an orphan from a uv run wrapper (see above). Find the holder with lsof on the port or the *.sqlite3-claim file and kill it.

  • Stario does not read .env files. Use your shell, direnv, or your process manager.

  • The watcher is for development. Production runs the module directly; see Deployment.

  • Want to watch signals live? debug_inspector() from stario.debug is a draggable overlay — opt in by adding it to the page body in development.