tempestweb.devserver¶
O servidor de desenvolvimento por trás do tempestweb dev: observa arquivos e recarrega, sem saber qual modo está servindo. Interno — a referência está aqui para quem depura o fluxo de dev.
Guia com exemplos: Usando a CLI.
tempestweb.devserver ¶
tempestweb dev server — transport-agnostic file watch + reload.
See docs/plan.md §5. This package owns the two halves of the dev loop:
- :class:
ReloadSignal— a publish/subscribe hub that decouples "something changed" from "how the reload reaches the app". A transport subscribes; the watcher (or the interactive cockpit) triggers. - :class:
FileWatcher— observes a project directory and triggers the signal on every reload-worthy change.
Neither half names a transport. Mode A wires the signal to a browser reload; Mode B wires it to a session restart. The watcher and the signal stay identical.
ReloadEvent
dataclass
¶
A single reload notification.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
ReloadKind
|
Whether to restart (clean state) or reload (preserve state). |
paths |
tuple[str, ...]
|
The project-relative paths whose change triggered the reload.
Empty when the reload was triggered manually (e.g. the |
generation |
int
|
A monotonically increasing counter, starting at 1, that lets a consumer detect missed reloads after a slow tick. |
Source code in tempestweb/devserver/reload.py
ReloadKind ¶
Bases: StrEnum
The kind of reload a change should trigger.
Attributes:
| Name | Type | Description |
|---|---|---|
RESTART |
Re-run the app from scratch with clean state (the v1 default —
"hot restart"). See |
|
RELOAD |
Re-run preserving state ("hot reload"). Reserved for post-v1; the watcher never emits this yet but the type exists so transports can branch on it ahead of time. |
Source code in tempestweb/devserver/reload.py
ReloadSignal
dataclass
¶
A transport-agnostic publish/subscribe hub for reload events.
The watcher (or the interactive cockpit) is the producer; a transport is the consumer. Neither side imports the other — they meet at this object.
Example
signal = ReloadSignal() seen: list[ReloadEvent] = [] unsubscribe = signal.subscribe(seen.append) event = signal.trigger(paths=["app.py"]) seen[0] is event True event.generation 1 unsubscribe()
Source code in tempestweb/devserver/reload.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
generation
property
¶
Return the number of reloads emitted so far.
Returns:
| Type | Description |
|---|---|
int
|
The current generation counter (0 before the first reload). |
subscribe ¶
Register a synchronous callback invoked on every reload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
ReloadCallback
|
A function called with each :class: |
required |
Returns:
| Type | Description |
|---|---|
Callable[[], None]
|
A zero-argument function that unregisters the callback when called. |
Source code in tempestweb/devserver/reload.py
trigger ¶
trigger(*, kind: ReloadKind = ReloadKind.RESTART, paths: list[str] | tuple[str, ...] = ()) -> ReloadEvent
Emit a reload event to every subscriber and waiter.
Increments the generation counter, builds a :class:ReloadEvent, invokes
every registered callback synchronously, and resolves any pending
:meth:wait futures.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
ReloadKind
|
The reload kind. Defaults to :attr: |
RESTART
|
paths
|
list[str] | tuple[str, ...]
|
The paths whose change caused the reload. Defaults to empty (a manual reload). |
()
|
Returns:
| Type | Description |
|---|---|
ReloadEvent
|
The emitted :class: |
Source code in tempestweb/devserver/reload.py
wait
async
¶
Await the next reload event.
Returns:
| Type | Description |
|---|---|
ReloadEvent
|
The next :class: |
Source code in tempestweb/devserver/reload.py
FileWatcher ¶
Turns filesystem changes under a project root into reload events.
The watcher filters changes by suffix, deduplicates a batch into a sorted
tuple of project-relative paths, and triggers the shared
:class:ReloadSignal once per batch.
Source code in tempestweb/devserver/watcher.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | |
handle_batch ¶
Process one change batch and trigger a reload if anything matched.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
ChangeBatch
|
The paths reported as changed in this batch. |
required |
Returns:
| Type | Description |
|---|---|
ReloadEvent | None
|
The emitted :class: |
ReloadEvent | None
|
batch matched a watched suffix (no reload triggered). |
Source code in tempestweb/devserver/watcher.py
run
async
¶
run(stream: ChangeStream | None = None, *, stream_factory: Callable[[Path], ChangeStream] | None = None) -> None
Consume a change stream until it is exhausted, triggering reloads.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stream
|
ChangeStream | None
|
An async iterable of change batches to consume directly.
Mutually exclusive with |
None
|
stream_factory
|
Callable[[Path], ChangeStream] | None
|
A factory that builds the change stream from the
resolved root. Defaults to a :func: |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If both |
Source code in tempestweb/devserver/watcher.py
create_dev_app ¶
Build a Starlette app that serves out_dir with optional livereload.
When signal is provided the app exposes /__livereload (an SSE stream
that emits a reload event on every signal trigger) and /__livereload.js
(the browser snippet), and injects the snippet's <script> tag into the
served index.html. Without a signal it is a plain static host for the
bundle (used by run --mode wasm).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
out_dir
|
str | Path
|
The built artifact directory to serve at |
required |
signal
|
ReloadSignal | None
|
The reload hub to bridge to the browser. |
None
|
Returns:
| Type | Description |
|---|---|
Starlette
|
A configured :class: |
Source code in tempestweb/devserver/http.py
livereload_frames
async
¶
Yield SSE frames for the livereload stream: open comment, then reloads.
Emits an initial ": connected" comment frame so the client knows the
stream is open, then one reload event per :meth:ReloadSignal.trigger,
carrying the reload generation as the SSE data. Runs until the consumer
(the HTTP response) is closed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
ReloadSignal
|
The reload hub to await reloads from. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[str]
|
SSE wire text blocks ( |
AsyncIterator[str]
|
|
Source code in tempestweb/devserver/http.py
make_server ¶
Build a non-started uvicorn server for app bound to host:port.
Splitting construction from running lets the dev loop drive server.serve()
concurrently with the file watcher under one event loop, and lets tests assert
the bind config without opening a socket.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Starlette
|
The Starlette app to serve. |
required |
host
|
str
|
The bind address. |
required |
port
|
int
|
The bind port. |
required |
Returns:
| Type | Description |
|---|---|
Server
|
A configured (but not started) :class: |
Source code in tempestweb/devserver/http.py
serve ¶
Serve app under uvicorn until stopped (blocking).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Starlette
|
The Starlette app to serve. |
required |
host
|
str
|
The bind address. |
required |
port
|
int
|
The bind port. |
required |