Watcher (Hot Reload)

The Watcher provides hot-reload capability for Managers during development. It monitors Python source files for changes and automatically restarts the corresponding Manager when its file is modified.

The Watcher is a development-only feature. It is not intended for production use.


How it works

        flowchart LR
    FS["Filesystem changes"] -->|FileModifiedEvent| Obs["watchdog.observers.Observer"]
    Obs --> WH["WatcherHandler<br/>lookup component<br/>by file path"]
    WH --> T["Timer debounce<br/>1 s dev<br/>10 s prod"]
    T --> RE["Restart Event → Router Queue"]
    

Component tree

Class

Role

Watcher

Entry point — sets up watchdog.observers.Observer, schedules file paths

WatcherHandler

Handles FileModifiedEvent, debounces with a Timer

WatcherHandlerComponent

Dataclass: (type: Type[ManagerBase], timer: Timer | None)

Debounce mechanism

When a file is saved (e.g. by an IDE that writes multiple times), multiple FileModifiedEvent can be emitted in rapid succession. The Watcher debounces by:

  1. On first modification, starting a Timer with a delay.

  2. On subsequent modifications before the timer fires, cancelling the old timer and starting a new one.

  3. Only when the timer finally fires is the Restart event actually sent to the Router.

This prevents restarting the Manager multiple times during a single save operation.


Starting the Watcher

from edri import EDRI

edri = EDRI()
edri.add_component(MyManager(edri.router_queue))
edri.start_watcher()
edri.run()

The Watcher automatically detects which Python files define the added components and monitors them all.


Restart behavior

When a file change is detected and the debounce timer expires:

  1. The Watcher puts a Restart(manager=ManagerClass) event on the Router queue.

  2. The Router dispatches it to every Manager subscribed to Restart. Each Manager's built-in _restart handler checks whether event.manager matches its own class and ignores the event otherwise.

  3. The matching Manager reloads its own source module (importlib.reload), looks up the (possibly changed) class on the reloaded module, unsubscribes its events, starts a fresh instance of that class, and then exits.

Because the module is reloaded from disk, code changes in the Manager's file are picked up. The new Manager's __init__ runs again, so any in-memory state is rebuilt from scratch. Configuration read from environment variables or external sources is also re-read on restart.

Important: The Restart event carries the Manager class type (manager=...), not the instance. The restart is performed by the old Manager process itself — it reloads the class from disk and spawns the replacement before exiting; the Router only delivers the event.


Timer delays

Environment

Delay

Rationale

development

1 s

Fast feedback during active development

production

10 s

Prevent rapid restart loops if something is writing files

The delay is set based on ENVIRONMENT environment variable.


Limitations

  • Only Managers added via edri.add_component(...) are watched.

  • The Watcher monitors the file that defines the Manager class. If the Manager depends on other modules that change, those changes are not detected unless those files also define a watched Manager.

  • Route registrations (event subscriptions) are re-applied on restart because the new Manager instance re-registers them during initialization.

  • The Watcher does not monitor files inside packages that are installed as dependencies (only project source files).

  • There is no support for monitoring multiple files per component — only the file containing the Manager class is watched.


Configuration

The Watcher uses watchdog under the hood. It is configured through environment variables:

Variable

Effect on Watcher

ENVIRONMENT

Controls the debounce delay (1 vs 10)

No additional environment variables are needed for basic operation.