sofab.decoder module¶
SofaBuffers push decoder (CORELIB_PLAN §5.2).
Bytes go in through Decoder.feed(), in chunks of any size, and each
call returns the three-valued sofab.Status for the bytes so far. There
is no finish/finalize step: an INCOMPLETE at end of input is
truncation, and only the caller’s framing can say so (§5.2.4).
Fields come out through a caller-supplied handler — a
sofab.Visitor, which §5.3.1 makes the decode surface, or a
sofab.Binding, a table mapping field ids to slots in storage the caller
owns, or both at once. There is no pull API: no next(), no cursor, no typed
read a caller issues.
Hot-path model — “advance a cursor over a contiguous buffer” (protobuf’s
trick). Incoming bytes are accumulated into a single contiguous buffer
(self._buf) and parsed by advancing an integer cursor (self._pos) with
direct indexing — no per-byte function call, no intermediate copies. When the
cursor reaches the end mid-item the decoder transparently refills from the
reader and continues, so the same code path serves both a fully-buffered
message and a reader that dribbles one byte at a time. See _varint /
_read_varints / _read_exact below.
Suspend and resume (CORELIB_PLAN §5.2). A reader that runs dry mid-field —
a socket with nothing buffered yet — makes the call raise
SofaIncompleteError. That is a first-class outcome, not an error, so
the call consumes nothing: the cursor goes back to where it started, the bytes
already read stay buffered, and re-issuing the same call once more bytes have
arrived parses the field from its first byte. See the “resume transactions”
section in Decoder.
Typical use:
class Sink(sofab.Visitor):
def on_unsigned(self, field_id, value):
...
dec = Decoder(visitor=Sink())
for chunk in stream:
status = dec.feed(chunk)
- class sofab.decoder.Decoder(*, binding=None, visitor=None, words=None, objects=None, max_dyn_array_count=None, max_dyn_string_len=None, max_dyn_blob_len=None, reassembly=None)[source]¶
Bases:
objectDecodes a SofaBuffers stream, pushing each field at a handler.
Construct it with a
visitor, abinding, or both, then hand it bytes withfeed(). Where both are given the binding takes every field it names and the visitor gets the rest.A declared type that contradicts the field on the wire is not an error (MESSAGE_SPEC §7.3, CORELIB_PLAN §6.3). A binding entry whose wire type or subtype does not match the field is skipped: its destination is left untouched and the decode stays COMPLETE. Nothing is raised, nothing is materialized, nothing is validated — and the fallback visitor is not offered it either, because a skipped field is skipped for the whole handler.
- Parameters:
- property error: SofaError | None¶
The failure that made
feed()returnStatus.INVALID, orNone. Mirrorssofab.Encoder.error: the statusfeed()returned is the answer, this is the reason behind it.There is deliberately no
statuscounterpart. The outcome is whatfeed()hands back and nothing repeats it: a second way to ask the same question is a second thing to keep in step, and this port shipped that drift once already.
- feed(data)[source]¶
Consume
dataand report the outcome for the bytes so far (§5.2).Accepts anything with a buffer —
bytes,bytearray, amemoryviewover either. The chunk is borrowed only for the duration of this call (§6, chunk lifetime): whatever the decoder still needs afterwards — a construct split across the boundary, a decoded string or blob — is copied out before it returns, so the caller may reuse or overwrite that memory the momentfeedcomes back.Returns
Status.COMPLETE,Status.INCOMPLETEorStatus.INVALID. There is deliberately nofinish/endcounterpart: anINCOMPLETEat end-of-input is truncation, and only the caller’s framing can say so.INVALIDis terminal — every laterfeedreturns it again without consuming anything, and the reason stays onerror.A receiver-side limit rejection (§6.2.1) is not one of the three outcomes: the message is well-formed and the receiver simply declined it, so it is raised as
SofaLimitErrorrather than folded intoINVALID(§6.3).- Parameters:
data (Any)
- Return type:
- reset()[source]¶
Forget the stream and start a new message, keeping the handler and its destinations. Lets one decoder serve many messages without rebuilding the binding — the destinations are the caller’s to clear (or not: a slot the next message does not write keeps whatever is in it, which is how absence is reported).
- Return type:
None