falaw.events

Structured progress events for fal calls.

Replaces the implicit “stream raw log strings to stdout” model with a small, machine-readable event stream that orchestrators can subscribe to (UI progress bars, cost telemetry, billing dashboards, etc.).

A ProgressEvent is emitted at every major lifecycle transition of a single call_fal invocation:

  • queued — call submitted to fal

  • progress — fal pushed an InProgress update with no message body

  • log — fal pushed an InProgress update with a log line

  • done — call returned a result

  • error — call raised

  • cache_hitfalaw.cached_call_fal() found a hit and skipped the network

Subscribers can be registered globally via subscribe() or per-call via the on_event= argument on call_fal() / cached_call_fal(). The on_log parameter still works (legacy: a string-only stream); see call_fal() for compatibility notes.

class falaw.events.ProgressEvent(*, kind: Literal['queued', 'progress', 'log', 'done', 'error', 'cache_hit'], application: str, call_id: str, message: str = '', pct: float | None = None, elapsed_s: float = 0.0)[source]

One step in the lifecycle of a fal call.

kind

Lifecycle stage. See EventKind.

Type:

Literal[‘queued’, ‘progress’, ‘log’, ‘done’, ‘error’, ‘cache_hit’]

application

fal model id (e.g. "fal-ai/flux/dev").

Type:

str

call_id

A short hex string that uniquely identifies the call. All events for one call_fal invocation share the same call_id.

Type:

str

message

Free-form text. For "log" events this is the log line; for "error" it’s repr(exc); otherwise empty.

Type:

str

pct

Optional progress percentage in [0.0, 100.0]. fal’s current API doesn’t surface this; included for forward compatibility.

Type:

float | None

elapsed_s

Seconds since the call started.

Type:

float

falaw.events.clear_subscribers() None[source]

Drop all registered subscribers. Mostly for tests.

falaw.events.emit(event: ProgressEvent, *, also: Iterable[Callable[[ProgressEvent], None]] = ()) None[source]

Send event to every registered subscriber + the also list.

Subscriber exceptions are swallowed (with a printed warning) so a misbehaving UI hook can’t bring the rendering pipeline down.

falaw.events.subscribe(callback: Callable[[ProgressEvent], None]) Callable[[ProgressEvent], None][source]

Register callback to receive every emitted ProgressEvent.

Returns the callback unchanged so it can be used as a decorator:

@subscribe
def log_to_file(ev: ProgressEvent) -> None:
    ...
falaw.events.unsubscribe(callback: Callable[[ProgressEvent], None]) None[source]

Remove a previously subscribe()’d callback. No-op if absent.