videoflow.wire package
Submodules
videoflow.wire.serialization module
Wire format used to move messages between nodes over the message broker
(videoflow.messaging.nats_messenger.NATSMessenger). Not used by anything
running purely in a single local process — it’s the boundary format for bytes
that cross a network/process boundary.
The wire is a single, language-neutral protobuf envelope (videoflow.v1.Envelope,
envelope version 4). Its payload is a typed protobuf message:
Tensorfor arrays, including video frames;Valuefor structured scalars/maps/lists — and aValuemay nest aTensor(tensor_value), so a mixed container like a(frame_index, frame)tuple has a neutral encoding (spec/PROTOCOL.mdWIRE-15);any vendor proto by its fully-qualified name.
Arbitrary Python objects are never put on the wire: there is no code-executing
fallback codec, because deserializing attacker-controlled bytes that way is remote
code execution (see spec/rfcs/0001). A payload type with no built-in encoding
registers one via register_payload_encoder; an unknown payload_type on decode
is handed back as opaque RawPayload bytes and never deserialized.
decode_envelope reads the envelope; a legacy msgpack (v2/v3) envelope is refused
with a clear error rather than decoded. Runs are version-homogeneous — streams are
run-scoped — so a single run never mixes versions.
- class videoflow.wire.serialization.BlobStore[source]
Bases:
objectInterface for the external blob store used for payloads over
MAX_INLINE_PAYLOAD_BYTES. Not tied to any particular broker — Redis is a convenient default (large string values, simple TTL-based expiry) even when NATS is the primary messaging broker.Reclamation (RFC 0002):
put_with_readers/releasedefault to TTL-only behaviour so a subclass that only implementsput/getkeeps working; a store that can refcount overrides both.- put(data: bytes, ttl_seconds: int = 3600) str[source]
Stores
dataand returns an opaque reference string thatget()can resolve later.
- put_with_readers(data: bytes, readers: int, ttl_seconds: int = 3600) str[source]
Stores
dataexpecting exactlyreadersdownstream reads, enabling the store to reclaim the blob once every reader has released it (BLOB-5).Default implementation ignores
readersand delegates toput()— a store with no reclamation support degrades to plain TTL expiry.
- videoflow.wire.serialization.COMPATIBLE_ENVELOPE_VERSIONS = (4,)
Versions this build can decode. The legacy msgpack wire (v2/v3) has been removed; such envelopes are refused on decode.
- videoflow.wire.serialization.DEFAULT_BLOB_TTL_SECONDS = 3600
Blob lifetime when nothing plumbs an explicit TTL. Serialization is flow-type agnostic, so the flow-aware defaults (3600s realtime / 86400s batch, BLOB-7) live with the messenger that knows the flow type; this is only the fallback.
- videoflow.wire.serialization.DEFAULT_ENVELOPE_VERSION = 4
a language-neutral
videoflow.v1.Envelope(protobuf). Overridable per run viaVF_ENVELOPE_VERSIONonly to a version this build speaks.- Type:
The sole envelope version
- videoflow.wire.serialization.EMITTABLE_ENVELOPE_VERSIONS = (4,)
Versions this build can emit.
- videoflow.wire.serialization.MAX_INLINE_PAYLOAD_BYTES = 524288
Payloads whose serialized size (in bytes) exceeds this threshold are written to a BlobStore instead of being inlined in the broker message. Large uncompressed video frames (a 1080p RGB frame is ~6.2MB) would otherwise blow past a typical broker’s per-message size limit (NATS defaults to a 1MB max_payload).
- videoflow.wire.serialization.MSG_TYPE_DATA = 'data'
Message kinds carried in the envelope
typefield.datais a normal payload;eosis an end-of-stream marker with no payload.
- videoflow.wire.serialization.PAYLOAD_ENTRY_POINT_GROUP = 'videoflow.payload_types'
Entry-point group for third-party payload codecs. Registration normally happens as a side effect of importing the component module (the worker imports it via
VF_NODE_CLASSanyway); the group covers host-side tools such asvideoflow debug decode, which must understand a vendor payload without knowing which package defines it.
- videoflow.wire.serialization.PAYLOAD_TENSOR = 'videoflow.v1.Tensor'
v4 protobuf payload-type identifiers (the
payload_typefield of anEnvelope). Proto messages use their descriptor FQN; these three are the ones the codec special-cases. Any other FQN round-trips as an opaqueRawPayload.
- class videoflow.wire.serialization.RawPayload(payload_type: str, data: bytes)[source]
Bases:
objectAn opaque, already-encoded payload: a
payload_typeFQN and its raw bytes. Produced when decoding a message whosepayload_typeis not registered (so a forwarding/storing node need not understand it), and accepted by the encoder so such a payload can be re-published losslessly (PROTOCOL.md WIRE-9).- data
- payload_type
- class videoflow.wire.serialization.RedisBlobStore(url: str | None = None)[source]
Bases:
BlobStoreUses a Redis server purely as a large-value TTL cache, independent of whether Redis is used for messaging.
- put(data: bytes, ttl_seconds: int = 3600) str[source]
Stores
dataand returns an opaque reference string thatget()can resolve later.
- put_with_readers(data: bytes, readers: int, ttl_seconds: int = 3600) str[source]
Stores
dataexpecting exactlyreadersdownstream reads, enabling the store to reclaim the blob once every reader has released it (BLOB-5).Default implementation ignores
readersand delegates toput()— a store with no reclamation support degrades to plain TTL expiry.
- videoflow.wire.serialization.decode_envelope(buf: bytes, blob_store: BlobStore | None = None) dict[source]
Decodes wire bytes back into a dict with keys
producer_name,flow_id,run_id,trace_id,seq,event_ts(Nonewhen absent),type,is_stop_signal(derived: True ifftype == MSG_TYPE_EOS),span_id,parent_span_id,replica_id,metadata,message(the fully decoded payload —Nonefor EOS), andblob_ref(the blob store reference the payload was resolved from, orNonewhen the payload was inline — lets the caller release the blob after the message is acked, BLOB-6). Only the protobuf v4 envelope is supported; a legacy msgpack (v2/v3) envelope is refused.
- videoflow.wire.serialization.derive_message_id(flow_id: str, run_id: str, producer_name: str, trace_id: str, seq: int, msg_type: str) str[source]
Deterministic, content-derived message id. Two publishes of the same logical message (e.g. a processor that crashed after publishing but is re-run and recomputes the same output for the same input group) produce the same id, so JetStream’s
Nats-Msg-Idde-duplication drops the retry copy. It is therefore essential that the inputs here are stable across retries — in particularseqmust be carried forward from the input group, not a local wall-clock or attempt counter.
- videoflow.wire.serialization.encode_envelope(producer_name: str, flow_id: str, run_id: str, trace_id: str, seq: int, msg_type: str, metadata: dict | None, payload: Any, span_id: str = '', parent_span_id: str = '', replica_id: int = 0, event_ts: float | None = None, blob_store: BlobStore | None = None, version: int | None = None, blob_readers: int | None = None, blob_ttl_seconds: int | None = None) bytes[source]
Encodes a full wire message and returns the bytes to publish to a broker subject.
- Arguments:
msg_type:
MSG_TYPE_DATAorMSG_TYPE_EOS. EOS carries no payload.run_id: the per-run identifier that scopes this flow execution.
span_id / parent_span_id: hex ids for log/trace correlation (optional).
replica_id: index of the emitting replica (0 for single-task nodes); distinguishes EOS markers from different replicas of one node.
event_ts: event time of the message in epoch seconds — when the underlying real-world event was captured — minted by the producer and carried forward unchanged; time-aligned joins group on it.
version: envelope version to emit. The only supported version is
4(protobuf); defaults toDEFAULT_ENVELOPE_VERSION.blob_readers: how many downstream reads an offloaded payload will receive; enables refcounted blob reclamation (BLOB-5).
None⇒ TTL-only blobs.blob_ttl_seconds: TTL for an offloaded payload (and its counter);
None⇒DEFAULT_BLOB_TTL_SECONDS.
- videoflow.wire.serialization.make_blob_store(url: str) BlobStore[source]
Builds the blob store for
url, dispatching on its scheme.- Arguments:
url: blob store URL, e.g.
redis://localhost:6379/0.
Returns: a ready
BlobStore.- Raises:
ValueError: the URL has no scheme, or no store is registered for it. The message names the known schemes and
register_blob_store.
- videoflow.wire.serialization.register_blob_store(scheme: str, factory: Callable[[str], BlobStore]) None[source]
Registers a
BlobStorefactory for a URL scheme.factoryreceives the full URL (not just the remainder) and returns a ready store.Third-party packages may register either by calling this on import, or by declaring an entry point in the
videoflow.blob_storesgroup — the latter is what lets a store be selected purely by configuration, with nothing in the flow importing the package that provides it.- Arguments:
scheme: URL scheme without the separator, e.g.
's3'. Case-insensitive: normalized here to match howmake_blob_storeparses a URL, soregister_blob_store('S3', ...)is reachable froms3://bucket/key.factory: callable taking the blob URL and returning a
BlobStore.
- videoflow.wire.serialization.register_payload_encoder(python_type: type, encoder: Callable[[Any], Tuple[str, bytes]]) None[source]
Registers an encode rule mapping instances of
python_typeto a(payload_type, serialized_bytes)pair on the v4 wire — the encode-side complement ofregister_payload_type.Rules are checked in registration order, and only after every built-in check (ndarray,
RawPayload, protobuf message, and the JSON-like types that becomeValue). That ordering is deliberate and worth preserving: it lets a vendor type get a real wire type instead of aTypeError, while making it impossible for any registration — even one matchingobject— to change how a built-in payload encodes. Those mappings are fixed byspec/PROTOCOL.md§4.4 and proven by the golden vectors, so they are not an extension point. This is the only way to give a type with no built-in encoding a place on the wire — there is no code-executing fallback (seespec/rfcs/0001).The corollary: a type that is already encodable (a
dictsubclass, say) is encoded as the built-in it resembles, and its registered rule never runs.Pair this with
register_payload_type(or a decoder that understands thepayload_typestring) so the receiving side can decode what you emit.- Arguments:
python_type: the type to match with
isinstance.encoder: callable taking the payload and returning
(payload_type, serialized_bytes).
- videoflow.wire.serialization.register_payload_type(message_cls: type[Message]) None[source]
Register a protobuf message class so envelopes carrying its FQN decode to an instance of it (rather than an opaque
RawPayload). The well-known videoflow types are pre-registered; vendors register their own payload messages.