Nodes and Flows
This page describes the building blocks of a Videoflow application and the rules that make a graph deployable in a distributed setting.
The three node types
Type |
Base class |
Method to implement |
Role |
|---|---|---|---|
Producer |
|
|
Creates data from an external source (file, camera,
stream). Set |
Processor |
|
|
Transforms inputs into an output. Supports
|
Consumer |
|
|
Terminal sink; produces no output. Supports
|
Every node also has open() and close() lifecycle hooks for acquiring and
releasing resources. Any lifecycle or processing method may be an async def (the
worker awaits it) and may take a final ctx parameter to receive a
runtime context — see
Writing your own components.
Node identity: the name
Each node has a stable, unique string name. In a distributed flow that name
is the node’s identity everywhere it matters: the broker subjects it publishes and
subscribes to, and the Kubernetes resources generated for it. If you do not pass a
name, one is generated from the class name, but for anything you intend to
deploy you should give each node an explicit, meaningful name:
detector = ObjectDetector(name='detector')(frame)
Names must be unique within a flow; a duplicate raises a ValueError when the
flow is built.
Constructor arguments must be serializable
Because a worker reconstructs only its one node from configuration (it never receives a live Python object), a node’s constructor arguments must be JSON-serializable — numbers, strings, booleans, and lists/dicts of those.
Two consequences follow:
Do heavy or stateful setup in
open(), not__init__. Opening a camera, loading a model, or creating a database connection belongs inopen(), which runs inside the worker just before processing begins.Store each constructor argument on
selfunder the same name so it can be captured automatically for reconstruction. Always accept and forward**kwargstosuper().__init__().
See Writing your own components for full examples.
Building a Flow
A Flow is created from the consumers (the leaves of the graph); producers
are discovered automatically:
from videoflow.core import Flow
from videoflow.core.constants import REALTIME
flow = Flow([consumer_a, consumer_b], flow_type=REALTIME, flow_id='my-flow')
flow_typeisREALTIME(drop stale messages, never block producers) orBATCH(at-least-once, loss-free delivery). See Batch versus realtime mode.flow_idnamespaces the broker subjects and Kubernetes resources; pass a stable value when you want to redeploy the same logical flow.
When the flow is built it validates the graph: it must be acyclic, every consumer
must be reachable from a producer, all names must be unique, and a replicated
multi-parent join node must set partition_by (see Scaling and node allocation).
Running a Flow
A flow runs through an execution engine:
from videoflow.engines.local import LocalProcessEngine
flow.run(LocalProcessEngine()) # local subprocesses; or KubernetesExecutionEngine
flow.join() # block until done
# flow.stop() # signal termination early
flow.run(engine) mints a fresh run_id and is non-blocking. flow.join()
blocks until the flow finishes naturally (all producers exhausted). flow.stop()
publishes a termination signal on the broker’s control channel that every worker is
subscribed to, then waits for them to drain and exit.
See Distributed execution for the available engines.
Reliability
Each call to flow.run() is scoped by a run id, so re-running a flow gets a
fresh set of broker streams instead of colliding with a previous run.
Delivery is at-least-once: a worker acknowledges a message only after it has
processed it (and published its output), so a crash mid-processing causes
redelivery, not loss. In BATCH mode a message that keeps failing is retried a few
times and then dead-lettered to a DLQ stream (vf-<flow>-<run>-dlq) with the
error attached, instead of being dropped or crashing the pod. See
Distributed execution and Batch versus realtime mode.