← Back to Blog

Editing pyrpc.json while the server runs

·8 min read

Restarting a dev session to change config is friction pretending to be safety. v0.13.0’s config watcher treats pyrpc.json as live state: save the file, and the session re-wires itself, including swapping the backend runtime entirely.

Flow diagram: file event -> parse BackendSpec -> diff -> restart backend or rewire codegen without restart

Diff parsed specs, not file bytes

The naive approach compares file contents. That fires on formatting churn, comment edits, editor atomic-save shims, all noise. Instead the watcher parses the new JSON into the same frozen dataclass used everywhere else and compares values:

new_spec = parse_backend(new_cfg) or spec
new_clients = [c["root"] for c in clients_from_config(new_cfg)]

backend_changed = new_spec != spec # dataclass eq, precise
output_changed = new_client_dirs != client_dirs

Reordering keys, adding whitespace, fixing a typo in a comment: nothing happens. Changing entrypoint: restart. Adding a client root: codegen callback re-points, no restart. The distinction falls out of comparing the right representation.

Restart means resolve again

On backend change the watcher imports the new types module (failing loudly but keeping the old spec alive if resolution fails), then, only if pyRPC owns the process, terminate, wait, and relaunch through the same pure resolver used at startup. Attaching to an externally-run server? Config changes still re-wire codegen; we just never pretend to manage a process we don’t own.

The test was harder than the feature

Threads plus mocked generators plus shutdown sequencing is where tests go flaky. Three races surfaced while testing this feature, and each got a structural answer rather than a sleep:

  • Two watchers, one writer. Both the Python-file and config watchers consume watch(); a scripted generator mutating the fixture from both contexts corrupted the JSON mid-read. Fix: dispatch on watched paths so exactly one generator writes.
  • Shutdown eating the batch. If stop set before the watcher processed its first yield, the change vanished. Fix: the fake keeps yielding batches forever, like watchfiles polling does, a dropped tick no longer loses the edit.
  • Detection vs teardown ordering. Fix: the mocked console blocks until the restart is observed, making session lifetime deterministic instead of racing thread scheduling.

The result passes across repeated full-suite runs with zero timing sensitivity. Flaky tests are rarely about the code under test, they are about unowned nondeterminism in the harness. Own it explicitly, or delete the test.