SofaBuffers
Structured Objects For Anyone
... so optimized, feels amazing.
SofaBuffers Python library
A streaming, dependency-free implementation of the SofaBuffers (Sofab)
serialization format — a compact, TLV-like binary format. It is the runtime
stream core, driven by generated code: a schema-driven generator emits one
class per message with the streaming serialize / deserialize pair and the
one-shot encode / decode wrappers over it, all of which call the Encoder /
Decoder primitives here.
The public API is that one pair of classes, backed by two interchangeable
engines selected at import: the hot path (varint / zigzag / buffer management)
ships as an optional compiled native accelerator (Cython → C,
sofab._speedups), with a pure-Python fallback used when it is absent. The
two are byte-for-byte interchangeable, so the library runs anywhere CPython
runs, with or without a C compiler.
Requirements
Python 3.9 or newer (CPython or PyPy); CI runs 3.9–3.14. The optional native accelerator additionally needs a C compiler and Cython, both build-time only.
Dependencies
None at runtime — the pure-Python path uses only the standard library
(struct, io). The one third-party build dependency is Cython
(PEP 517), used to compile the accelerator
and never imported at runtime.
Packaging
Distribution sofa-buffers-corelib on PyPI; import package sofab. The
namespace is the fixed half (CORELIB_PLAN §6: sofab, in every target); the
registry name is derived — the organization slug sofa-buffers plus corelib,
in PyPI's own convention.
pip install sofa-buffers-corelib
import sofab # Encoder, Decoder, Visitor, wire-format types and limits
print(sofab.__version__) # release of this runtime
sofab.__version__ is the only place the release version is written down;
pyproject.toml declares the distribution version dynamic and reads it there.
Native accelerator
Encoder / Decoder / Field are re-exported from the compiled
sofab._speedups extension when present, and from the pure-Python
encoder.py / decoder.py otherwise. Both engines import wire constants, enums
and exception classes from the shared types.py, so a SofaArgumentError is the
same class from either, and the two produce byte-for-byte identical output
(tests/test_native_parity.py).
The active engine is reported by sofab.IMPL ("native" or "python"):
import sofab
print(sofab.IMPL) # "native" when the compiled extension is loaded
Force the pure-Python path with SOFAB_PUREPYTHON=1.
Released wheels ship the accelerator already compiled, so a plain
pip install sofa-buffers-corelib gets the native engine without a toolchain
wherever a wheel exists: CPython 3.9–3.14 on Linux (glibc and musl, x86-64 and
ARM64), macOS Intel and Apple Silicon, and Windows x86-64. Anywhere else pip builds the sdist, which
compiles the accelerator if a C compiler is present and installs the pure-Python
engine if not.
Feature flags
The package always builds the full format (unsigned / signed varints, fp32 /
fp64, strings, blobs, arrays and nested sequences). The one build toggle is
SOFAB_DISABLE_NATIVE=1, which builds a native-free (pure-Python) distribution;
it changes only speed, never the wire format or the public API.
Why this design
| Goal | How |
|---|---|
| Streaming out | Encoder writes into a fixed buffer and drains it to any binary stream (file, socket, BytesIO) as the message is written — never after it — so a message can exceed RAM. |
| Streaming in | Decoder is a push decoder: feed(chunk) takes bytes of any size and hands each field to your handler, never materializing the whole message. Every feed returns COMPLETE / INCOMPLETE / INVALID for the bytes so far; a construct split across a boundary is retained and finished by the next chunk. |
| Native speed, zero runtime deps | The hot path ships as an optional Cython accelerator (sofab._speedups); when it can't be built it falls back to pure Python. No runtime third-party deps either way. |
| Runs everywhere | With no compiler or wheel, pip still installs a working pure-Python build (py3-none-any). Native and pure paths are byte-for-byte identical; falling back changes only speed. |
| Sticky errors | Encoder(sticky=True) records the first failure and turns later writes into no-ops, so generated serialize code can check enc.error once. |
| No silent truncation | An integer field accepts what Python accepts wherever an integer is required — anything with __index__ (int, bool, IntEnum, NumPy integers). A float is refused with SofaArgumentError, 3.0 included. |
| No unreadable message | The format-wide ceilings (CORELIB_PLAN §6.2) bind the encoder too: a field id above ID_MAX, an array count above ARRAY_MAX, nesting past MAX_DEPTH, and a string/blob payload above FIXLEN_MAX (2 GiB − 1) are each refused with SofaArgumentError before the field header is written. An oversized blob is refused on its length, before it is copied. |
| Floats narrow by IEEE rules | A Python float is a C double, so write_float32 (scalar and array) narrows on the way out: round-to-nearest, and a magnitude past FLT_MAX overflows to ±inf — the same bytes a native-fp32 corelib writes, and identical in both engines. NaN payloads, signaling ones included, keep their exact bits. |
| Reserve-offset | Encoder.over_buffer(buf, offset=…) leaves room at the front of the buffer for a lower-layer protocol header; a sink calling buffer_set(buf, offset) re-arms that room for every flushed packet. |
| Sparse sequences | write_sequence_begin_lazy holds a sequence header back until the sequence receives content, so a sequence-typed field equal to its declared default is omitted rather than framed empty (MESSAGE_SPEC §2) — decided in one forward pass, without buffering the sub-message. write_sequence_end drops a contentless sequence; write_sequence_end_keep forces the frame out where presence itself carries meaning, such as a wrapper-array element, which is always framed even when all-default. |
| Typed | Fully type-annotated with a py.typed marker (PEP 561); clean under mypy --strict. |
| Forward/backward compatible | Unknown fields are consumed with skip() — consumed, not copied: the payload is stepped over, never materialized. A field whose wire type contradicts the read takes the same path (MESSAGE_SPEC §7.3, see Deserialize). |
Usage
The codec has four use cases — serialize a message that fits in one buffer, serialize one too large for the buffer (streamed out in chunks), deserialize a whole message, and deserialize one arriving in chunks — plus the generated-code path that wraps them.
Serialize
Write fields into an Encoder and take the finished bytes:
from sofab import Encoder
enc = Encoder()
enc.write_unsigned(1, 42)
enc.write_signed(2, -7)
enc.write_string(3, "hi")
data = enc.getvalue()
Encoder() writes into a fixed 1 KiB scratch buffer and appends each bufferful
to the result it hands back — it never grows a buffer mid-message (see Memory
handling). Pass a writer (anything with write(bytes)) and
the same bufferfuls go there instead, as they are produced:
with open("msg.sofab", "wb") as fh:
enc = Encoder(fh) # streams out; nothing accumulates
enc.write_unsigned(1, 42)
enc.flush() # push the tail
Serialize stream
Encoder.over_buffer is the same mechanism over a buffer you supply: it
writes in place and calls a flush sink whenever the buffer fills, so an
arbitrarily large message streams out through however much memory you chose to
give it:
from sofab import Encoder
out = bytearray() # or a socket / file write
enc = Encoder.over_buffer(bytearray(16), offset=0, flush=out.extend) # tiny buffer
for i in range(1_000_000):
enc.write_unsigned(i % 128, i)
enc.flush() # push the tail
With no sink the buffer is all the encoder gets: it holds the message or
reports SofaBufferError. That is the shape generated code uses when the schema
bounds the message — allocate MAX_SIZE, encode in one pass, no flush possible:
buf = bytearray(Point.MAX_SIZE)
enc = Encoder.over_buffer(buf, offset=0)
point.serialize(enc)
wire = memoryview(buf)[: enc.bytes_used()] # no copy
Deserialize
Decoder is a push decoder (CORELIB_PLAN §5.2): you hand it bytes, it hands
your handler fields. There is no reader and no caller-driven loop — the decoder
owns the walk, which is what lets it resolve a field to its destination without
crossing into Python at all.
Give it a handler and feed it:
from sofab import Decoder, Status, Visitor
class Handler(Visitor):
def on_unsigned(self, field_id, value): ...
def on_string(self, field_id, value): ...
dec = Decoder(visitor=Handler(), # the four are all required
max_dyn_array_count=65536, # see "Decode limits" below
max_dyn_string_len=1 << 20, max_dyn_blob_len=1 << 20,
reassembly=64 * 1024) # see "reassembly" below
for chunk in socket_chunks():
st = dec.feed(chunk)
if st is Status.INVALID:
raise ValueError(dec.error)
Every feed returns the outcome for the bytes consumed so far:
| outcome | meaning |
|---|---|
Status.COMPLETE |
the bytes end exactly at a field boundary — a valid message may end here, and more fields may also still follow |
Status.INCOMPLETE |
the bytes end inside a construct, or inside a sequence still open. Not an error. The partial tail is retained; the next feed continues from it |
Status.INVALID |
malformed regardless of what follows. Terminal — every later feed returns it again — with the reason on dec.error |
There is deliberately no finish()/end(): the status feed returned is
the answer, and whether an INCOMPLETE at end-of-input is acceptable is your
framing's call. A receiver-side limit (max_dyn_array_count and friends) is not
one of the three outcomes — the message is well-formed and you declined it — so
it raises SofaLimitError rather than folding into INVALID.
A fed chunk is borrowed only for the duration of the call. Anything the
decoder still needs afterwards is copied out before feed returns, so you may
reuse or overwrite that buffer the moment it comes back. feed accepts bytes,
bytearray or a memoryview over either. reset() starts a new message on the
same decoder, keeping the handler.
Integer arrays: on_array_begin
on_unsigned_array / on_signed_array receive an array already decoded, and
two decisions have been made for you by then.
The first is the element width your schema declares. Checking the list you are handed only rejects an array that arrived; one truncated behind a bad element never produces a list at all, so the bad value goes unreported. The second is where the elements went — into a list the decoder built, and a list handed over afterwards is a list already built.
on_array_begin runs at the count header, before a single element is read:
from array import array
class Handler(Visitor):
def __init__(self):
self.ports = array("H", bytes(2 * 64)) # your storage, your size
def on_array_begin(self, field_id, wtype, count):
if field_id == 7: # `ports: { array, items: u16 }`
return (self.ports, None, 0xFFFF) # dst, elem_min, elem_max
return None # anything else: the list
Return None and nothing changes. Return (dst, elem_min, elem_max) and:
elem_min/elem_maxare applied at each element, so a value outside them isINVALIDwhether the array completes or is truncated behind it, which is alsoINVALID-over-INCOMPLETEfor free. Either side may beNone.dstis a writable, contiguous buffer of at leastcountslots — anarrayof the right typecode, or amemoryviewover one. The decoder fills it and does not call the typed hook; on the native engine no element is ever boxed. A buffer too short isSofaArgumentError: the decoder never grows one, and the refusal comes at the header, before anything is written.dst=Nonestates the width and keeps the list.
Slots may be 1, 2, 4 or 8 bytes. A narrower one needs a declared width that fits it, so a value can never be silently truncated into it.
Handing over a destination is what makes an array cheap: on the native engine a
1 000-element u16 array costs 68% less than the same array as a list, and a
u64 array 64% less — the list route spends most of its time building and
freeing Python integers. On the pure-Python engine there is nothing to save (it
has to box either way) and the destination route costs 7–18% more, so use it
there for the bound and the storage, not for speed.
on_array_begin is not called for float arrays: they carry no declared width to
state. Their destination hook is on_float_array_begin, below.
Blobs: on_blob_begin
on_bytes receives a bytes the decoder had to build, and the only size it
could build it from is the wire's — a megabyte blob costs a megabyte allocation
per message. §6.6.3's other shape is a destination you hand back once you know
the size:
class Handler(Visitor):
def __init__(self):
self.frame = bytearray(1 << 20) # your buffer, your ceiling
def on_blob_begin(self, field_id, size):
if field_id == 9:
return self.frame # filled; on_bytes is not called
return None # anything else: a bytes as before
A buffer too short is SofaArgumentError — refused at the length word, before a
byte is written, and never grown.
Strings: on_string_begin
The same bargain for a string. The hook is told the payload's wire byte
length — what a schema maxlen bounds (MESSAGE_SPEC §1), not a character
count — and what lands in your buffer is the payload's own UTF-8:
class Handler(Visitor):
def __init__(self):
self.name = bytearray(256)
def on_string_begin(self, field_id, size):
if field_id == 3:
return self.name # filled; on_string is not called
return None # anything else: a str as before
The bytes are still validated — §6.7.2 makes a field you read both
materialized and validated — by a byte walk (§6.4.3's utf8_valid), so the
check does not build the str the destination exists to avoid. Invalid UTF-8 is
INVALID and your buffer is left untouched. The verdict is CPython's own on
every payload: tests/test_aggregate_destinations.py pins both engines to
bytes.decode("utf-8") over the whole RFC 3629 boundary set.
Float arrays: on_float_array_begin
One hook for both fixlen subtypes, naming which it is, taking count 8-byte
slots — a Python float is a double, and that is what the values become:
from array import array
class Handler(Visitor):
def __init__(self):
self.samples = array("d", [0.0] * 4096)
def on_float_array_begin(self, field_id, subtype, count):
return self.samples if field_id == 5 else None
An array("f") is refused rather than silently narrowed. A consumer that needs
an fp32's wire bits intact takes on_float32_array_bits instead — see
Bit-exact floats below.
Bit-exact floats: on_float32_bits and write_float32_bits
Python's only float is a double, and widening an fp32 to one sets the quiet
bit: a signaling NaN's payload is destroyed the instant the value passes
through the wider float, and no later code can recover it. CORELIB_PLAN §6.5
therefore requires a double-only target to carry an fp32 to a bit-exact
consumer as wire bits:
class Transcoder(Visitor):
def on_float32_bits(self, field_id, bits): # instead of on_float32
out.write_float32_bits(field_id, bits) # verbatim, no float
def on_float32_array_bits(self, field_id, count, payload):
out.write_float32_array_bits(field_id, payload)
Both hooks are opt-in by override, and both replace their value-carrying twin
for every fp32 the message holds. payload is a read-only view of the bytes
you fed; it is released when the callback returns, so copy what you need to keep.
A producer that has a value rather than bytes writes write_float32 as before.
Bounding what a decode holds: reassembly
A construct split across two fed chunks has to be joined somewhere. CORELIB_PLAN §6.6.2 says where:
A payload split across fed chunks has to be joined somewhere. That somewhere is storage the caller supplied [...] A codec MUST NOT grow a private accumulator instead.
So there is one buffer, sized once and never grown — and, like the three caps, it is required. The size decides which well-formed messages this receiver can stream, which makes it a receiver policy, and §6.2.1 leaves the codec no policy to invent. Name a size, or hand over the storage:
caps = dict(max_dyn_array_count=65536, # required; see "Decode limits"
max_dyn_string_len=1 << 20, max_dyn_blob_len=1 << 20)
dec = Decoder(visitor=handler, reassembly=64 * 1024, **caps) # decoder sizes it
dec = Decoder(visitor=handler, reassembly=bytearray(1 << 20), **caps) # or you do
The number to pass is the largest single value this receiver reads, not the
largest field a sender might send: a field you skip — an unknown id, or one
MESSAGE_SPEC §7.3 says is mistyped — is discarded as it arrives and needs no room
at all, whatever its size. The floor is sofab.MIN_REASSEMBLY (16), what a single
construct's framing can need.
The pieces are copied into that buffer as they arrive, and a construct that does
not fit is SofaArgumentError — refused, never accommodated. That is what lets
you bound a decode's memory by construction instead of by measurement: whatever
the sender claims, this decoder holds what you named and nothing more.
It is also what makes §6's chunk-lifetime promise literal: the unconsumed tail is
copied out before feed returns, so the chunk is yours again the moment it does
— overwrite it in place if you like.
A message that arrives in one piece never touches the buffer at all, whatever its
size. It is a chunked reader that has to size it for the largest string, blob
or array payload it will take across a chunk boundary — including one it only
means to skip, which is still buffered while it is walked.
Arrays of strings, blobs or structs: sofab.collectors
An array whose elements are not packed scalars — strings, blobs, structs — is a sequence whose child ids are the array indices (MESSAGE_SPEC §5.1). Growing a list from that event stream has the same shape for every schema, so it ships here rather than being emitted into every generated package: three functions a flat visitor calls from its own callbacks.
from sofab import (UNBOUNDED, FixlenSubtype, Visitor, WireType,
reserve_elem, reserve_leaf)
MAX_DYN_ARRAY_COUNT = 256 # the receiver's cap, stated by you
class Row:
def __init__(self):
self.x = 0
class Doc(Visitor):
def __init__(self):
self.tags: list[str] = [] # array<string>, no count: the cap applies
self.rows: list[Row] = [] # array<Row, count 16>
self._scope = [None]
self._ix = 0
def on_sequence_begin(self, field_id):
scope = self._scope[-1]
if scope is None and field_id in (3, 4):
self._scope.append(field_id)
elif scope == 4: # a struct element: reserve, then route
reserve_elem(self.rows, field_id, Row, 16, MAX_DYN_ARRAY_COUNT)
self._ix = field_id
self._scope.append("row")
else:
return False
return None
def on_sequence_end(self):
self._scope.pop()
def on_field(self, field):
if self._scope[-1] == 3: # a string element: reserve at its header
if field.type is not WireType.FIXLEN or field.subtype is not FixlenSubtype.STRING:
return False # MESSAGE_SPEC §7.3: skip a mistyped element
reserve_leaf(self.tags, field.id, "", UNBOUNDED, MAX_DYN_ARRAY_COUNT)
return None
def on_string(self, field_id, value):
self.tags[field_id] = value
def on_unsigned(self, field_id, value):
if self._scope[-1] == "row" and field_id == 0:
self.rows[self._ix].x = value
reserve_leaf (a string or blob: the gap is a shared "" / b""),
reserve_elem (a struct, union or native matrix row: each new slot gets its own
make()) and reserve_row (a row that is itself a wrapper array: replaced by a
fresh list) are the set. Each bounds the index, then grows the list to
id + 1, filling the gap an omitted interior element left — appending would
shorten the array by every gap, and would take a reopened id as a second
element. None of them stores the value: that, and the routing into a framed
element, stay with the caller. Like Encoder and Decoder, the three resolve
to compiled twins when the native engine is active (sofab.IMPL == "native");
the contract and the refusals are the same.
Which bound applies is the schema's choice, and every call states both. cap is
the schema's declared element count — a capacity, not a length — and an id at
or past it is INVALID (SofaDecodeError); pass UNBOUNDED where the schema
declares none. rcap is the receiver limit and applies only then, because
§6.2.1 forbids a receiver limit on a field the schema already bounds; an id past
it raises SofaLimitError, and an rcap that states no number (negative,
None, not an int) raises SofaArgumentError. Either way the id is judged
before the list grows, so an index near 2³¹ costs a comparison and not an
allocation, and a refused id leaves the list as it was.
An element's maxlen is not an argument here: declare it from
on_schema_bound and the decoder judges it at the length word.
These are the static helper layer of CORELIB_PLAN §6.6.1 — beside the codec, not part of it. They allocate on the generated layer's behalf; the codec does not allocate a container of its own. What the codec does allocate is listed under Memory handling.
Decode into your own storage (Binding)
A Binding declares once where every field id belongs, so a decode fills
your slots without a handler written by hand.
It is not a second decoder. CORELIB_PLAN §5.3.1 allows exactly one decode
surface, and a table is reached through it: a handler declares its slots once
from Visitor.destinations(), and Decoder(binding=…, words=…, objects=…) is
the constructor shorthand for a handler that declares exactly that. Same feed,
same header walk, same verdicts.
What makes that one surface is where the rules live, not where the value
lands. The receiver cap, the schema bound, the §7.3 tag test, the UTF-8 check,
the declared element width and the resume transaction each exist once and run
for every field alike — a field the table names and a field it does not run the
same code right up to the assignment itself. It is the same bargain
on_string_begin and on_array_begin already strike per field — name a
destination and the codec writes there instead of calling you back — made once
for the whole message.
from sofab import Binding, Decoder
b = (Binding()
.unsigned(1, at=0, count_at=2) # -> words slot 0, arrival in slot 2
.string(3, at=0, maxlen=64) # -> objects[0]
.unsigned_array(4, at=8, cap=16, count_at=3))
words = bytearray(b.tree_words_required * 8) # you allocate; you size it
objs = [None] * b.tree_objects_required
dec = Decoder(binding=b, words=words, objects=objs, reassembly=64 * 1024,
max_dyn_array_count=65536, max_dyn_string_len=1 << 20, max_dyn_blob_len=1 << 20)
dec.feed(payload)
u = memoryview(words).cast("Q") # as many typed views as you like
u[0] # field 1
objs[0] # field 3
list(u[8:8 + u[3]]) # field 4's u[3] elements
Two pieces of storage, both yours: words, a writable byte buffer whose
length is a multiple of 8 — every numeric field is one 64-bit slot, floats
widened to a native double, arrays cap consecutive slots — and objects, a
pre-sized list for string and blob, which have no fixed-width machine form.
Read the slots back through .cast("q") / .cast("Q") / .cast("d") over the
same buffer, at no copy.
An objects slot takes either shape, and the row says which. .string(…) /
.bytes(…) name a slot to put a value in, and the decoder builds that
str/bytes — the one thing on this route the wire still sizes. .string_into(…)
/ .blob_into(…) name a slot that already holds a writable byte buffer you
put there; the payload is copied into it, count_at receives the byte length, and
nothing is sized from the wire at all. See Memory handling.
What follows from the caller owning the storage:
- Nothing is ever sized from the wire.
capandmaxlenare the schema's bounds, so a message declaring more isINVALIDat the count/length header, before an element is read (MESSAGE_SPEC §7.1). It is not aSofaLimitError— that is for fields the schema leaves unbounded, and declaring a bound here is what takes the receiver-side cap off the field. - A declared integer width is checked at the value. A slot is 64 bits
whatever the field declares, so the width is an explicit bound:
.unsigned(…, max_value=0xFF)for au8or a narrowbitfield,.signed(…, min_value=-128, max_value=127)for ani8or a narrowenum, andelem_max/elem_minon the array binders. A value outside it isINVALIDbefore it is stored, even when the message is truncated behind it (§1, §5.2). - A boolean is not the unsigned binding, and carries no width.
.boolean(…)and.boolean_array(…)are the read half of §4.4: a boolean has no wire type of its own, so it arrives under the unsigned tag, but every value other than0istrueand the slot receives a normalized0/1— never the42the sender happened to write. Such a value is notINVALID; there is nothing to reject, only something to normalize. That is why neither binder takes a declared width the way.unsigned(…)takesmax_value: §4.4 gives a boolean no width bound at all, unlike anenumor abitfield.Encoder.write_boolandEncoder.write_bool_arrayare the canonical-on-encode half —truegoes out as1, so a re-encode of a tolerantly decoded value is canonical. - Absence needs no sentinel. A slot the decoder does not write keeps what you
put there.
count_atnames a slot receiving1for a scalar that arrived, the element count for an array, the occurrence count for a sequence. - A contradicting wire tag is skipped, not rejected (§7.3) — like an unknown
id, and the decode stays
COMPLETE. - Nested messages share the same storage.
b.sequence(id, child)descends into a child table in the same two buffers, so a whole tree decodes into one flat pair. A sequence with no binding is skipped whole. - A binding is build-once. Building a decoder freezes the table and derives
its destination map, so a table changed afterwards cannot leave a decoder
reading a stale copy. The map is cached on the
Binding, so building a decoder per message costs no recompilation.
A Binding and a Visitor compose: bind the fields you know, the visitor gets
the rest — every hook, including the begin destinations and the raw fp32
channel. A declaration is about its own field, so the two never collide: an
fp32 the table names lands in its slot as the widened double the table asked
for, and one it does not name reaches on_float32_bits if the visitor overrides
it. The same holds for scopes: a sequence the table names is the table's, so the
visitor hears neither its on_sequence_begin nor its on_sequence_end — begins
and ends always pair up, which is what lets a flat visitor track its depth.
That same silence is a trap for an id a child table does not name: the
visitor still thinks the walk is in the parent's scope, so it would get the id
under the parent's identity — an unknown field a newer sender added inside a
struct would land in the parent's field with the same id. Build child tables
with Binding(closed=True) and such an id is skipped instead, exactly as if
there were no visitor: no hook, nothing materialized, decode stays COMPLETE.
Declaring the slots on the handler instead is the same thing without the constructor keywords, and is what generated code should emit:
from sofab import Visitor
class Telemetry(Visitor):
def __init__(self):
self.words = bytearray(b.tree_words_required * 8)
self.objects = [None] * b.tree_objects_required
def destinations(self): # asked once, when the decoder is built
return (b, self.words, self.objects)
t = Telemetry()
Decoder(visitor=t, max_dyn_array_count=65536, # one handler argument, plus the policy
max_dyn_string_len=1 << 20, max_dyn_blob_len=1 << 20,
reassembly=64 * 1024).feed(payload)
assert memoryview(t.words).cast("Q")[0] == u[0]
Anything a Binding declares, a hand-written Visitor can declare too:
on_schema_bound is where a handler names the count/maxlen the schema
puts on a field, which is what makes a receiver-side max_dyn_* cap stop
applying to it (§6.2.1) and makes exceeding it INVALID rather than a policy
rejection. It is told the wire's tag alongside the id, so it can apply §7.3 to
its own declaration exactly as the table route does.
Code generator
The common real use is driving the library through generated code:
sofabgen --lang python emits a @dataclass per message with exactly four
methods (CORELIB_PLAN §6.1.1 fixes the names) — the streaming pair serialize /
deserialize, which talks to the primitives above, and the one-shot pair
encode / decode, thin wrappers over it. A hand-written stand-in:
from dataclasses import dataclass
from sofab import Binding, Decoder, Encoder, Status
# generated by: sofabgen --lang python
@dataclass
class Point:
x: int = 0
y: int = 0
#: Field id -> slot, built once from the schema.
BINDING = Binding().signed(1, at=0, count_at=2).signed(2, at=1, count_at=3)
def serialize(self, e: Encoder) -> None: # streaming out: write into any encoder
e.write_signed(1, self.x)
e.write_signed(2, self.y)
def deserialize(self, words) -> None: # streaming in: read your slots back
q = memoryview(words).cast("q")
u = memoryview(words).cast("Q")
if u[2]:
self.x = q[0]
if u[3]:
self.y = q[1]
# The receiver caps generated code bakes in from the sofabgen config: the
# numbers are the generated layer's, never the codec's (§6.2.1).
MAX_DYN_ARRAY_COUNT = 65536
MAX_DYN_STRING_LEN = 1 << 20
MAX_DYN_BLOB_LEN = 1 << 20
#: Derived from the schema by the generator, the same way: the largest
#: single value this receiver READS, since a skipped field needs no room.
REASSEMBLY = 1 << 20
@classmethod
def decoder(cls): # §6.1.1: the streaming reader
words = bytearray(cls.BINDING.tree_words_required * 8)
return Decoder(binding=cls.BINDING, words=words,
max_dyn_array_count=cls.MAX_DYN_ARRAY_COUNT,
max_dyn_string_len=cls.MAX_DYN_STRING_LEN,
max_dyn_blob_len=cls.MAX_DYN_BLOB_LEN,
reassembly=cls.REASSEMBLY), words
def encode(self) -> bytes: # one-shot wrapper over serialize()
e = Encoder()
self.serialize(e)
return e.getvalue()
@classmethod
def decode(cls, data: bytes) -> "Point": # one-shot wrapper over deserialize()
dec, words = cls.decoder()
if dec.feed(data) is not Status.COMPLETE:
raise ValueError(dec.error or "incomplete message")
o = cls()
o.deserialize(words)
return o
wire = Point(x=3, y=4).encode()
got = Point.decode(wire) # got.x == 3, got.y == 4
The one-shot pair holds the whole message in memory; serialize / deserialize
are the same code without that requirement. Out, serialize writes into an
encoder over a buffer you sized, draining to a sink as it fills; in, the
decoder from decoder() takes chunks of any size:
# streaming out: a 2-byte buffer, drained to the sink as the message is written
packets = bytearray() # or a socket / file write
enc = Encoder.over_buffer(bytearray(2), offset=0, flush=packets.extend)
Point(x=3, y=4).serialize(enc)
enc.flush() # push the tail
streamed = bytes(packets) # == wire, out of a buffer half its size
# streaming in: the same object, fed one byte at a time
dec, words = Point.decoder()
for i in range(len(streamed)):
st = dec.feed(streamed[i : i + 1]) # COMPLETE / INCOMPLETE / INVALID
got_streamed = Point()
got_streamed.deserialize(words)
Every feed returns the outcome for the bytes so far, so a source that runs dry
before the message ends adds no obligation to the generated code beyond looking
at it: INCOMPLETE means "feed me the next chunk", and only your framing knows
whether more can still come.
Decode limits
A field the schema leaves unbounded lets the sender decide what the receiver
allocates, so every decoder carries receiver-side limits that reject an
oversize field on its count/length word alone — before any allocation or
payload buffering. All three are required constructor arguments — as is
reassembly, which is the same kind of number:
dec = Decoder(binding=b, words=words, reassembly=64 * 1024,
max_dyn_array_count=65536, max_dyn_string_len=1 << 20, max_dyn_blob_len=1 << 20)
Required, because the numbers are yours. CORELIB_PLAN §6.2.1 lets the codec
perform the comparison — "a corelib MAY take a limit as an argument and
perform the check itself" — but not own the number: it "MUST NOT hold a limit
of its own, MUST NOT supply a default for one it was not given, MUST NOT
read an omitted argument as unlimited, and MUST NOT clamp to one". Omitting
one raises SofaArgumentError (§6.3's InvalidArgument), never
SofaLimitError, which would promise a limit to raise that was never
configured.
A field whose declared count/length exceeds its limit raises SofaLimitError: a
policy rejection, distinct from malformed input, and a sibling of
SofaDecodeError under SofaError rather than a subclass, so except SofaDecodeError does not catch it. It governs what this decoder would
allocate; the two sections below say which fields those are.
The five exceptions carry CORELIB_PLAN §6.3's five codes: SofaBufferError is
BufferFull, SofaArgumentError is InvalidArgument, SofaDecodeError is
InvalidMessage, SofaLimitError is LimitExceeded, and SofaIncompleteError
is the INCOMPLETE outcome, which §6.3 is explicit is not an error at all.
§6.3 lets a port "adapt casing and idiom"; SofaArgumentError was once
SofaRangeError, which read narrower than its code, and the old name is kept as
an alias.
There is no unset state and no unlimited mode. None is refused rather than
read as "no limit", and there is no default to fall back on. A caller that wants
the widest limit there is states the format-wide ceiling itself — ARRAY_MAX for
the count, FIXLEN_MAX for the two lengths — above which the value is already
INVALID, so that configuration rejects nothing a looser one would accept. That
is then your number: a ceiling reached because nobody stated a cap is the
format's bound, not a receiver cap, and §6.2.1 forbids a codec from
presenting it as one. 0 is a real setting, not an unset one. The numbers are
supplied by generated code, which knows the schema and the deployment.
Independent of any limit, the decoder never pre-allocates from an untrusted array
count — a truncated oversize claim fails promptly as an INCOMPLETE.
The verdict is reached on the count/length word alone, before a single payload byte is read or buffered — the point CORELIB_PLAN §6.2.1 requires it to be decided.
A schema-bounded field is exempt
A cap is capacity the deployment commits where the sender chooses the size.
Where the schema already states a count:/maxlen:, that bound governs
instead and an over-bound value is malformed input, so §6.2.1 forbids the cap
there ("MUST NOT be applied to a field the schema already bounds") and §6.3
forbids SofaLimitError on such a field.
Only the schema knows which fields those are, so the handler is where it says
so. A Binding declares it in the table — cap on an array and maxlen on a
string or blob are the schema's bound — and a hand-written visitor declares
it with on_schema_bound:
from sofab import Binding, FixlenSubtype, Visitor, WireType
b = Binding().string(1, at=0, maxlen=4194304) # `name: { string, maxlen: 4194304 }`
class Names(Visitor): # the same statement, by hand
def on_schema_bound(self, field_id, n, wtype, subtype):
if field_id == 1 and wtype is WireType.FIXLEN and subtype is FixlenSubtype.STRING:
return 4194304
return -1 # not the field the schema declared
Declaring it does two things at once: the receiver-side cap stops applying to
that field, and the decoder enforces the declared bound itself — an over-bound
length is INVALID (SofaDecodeError, MESSAGE_SPEC §7.1), never
SofaLimitError. on_schema_bound is asked at the count/length header, for a
string, a blob or an array the handler has accepted, and for nothing else.
The tag is passed with the id because this is the only hook that spans more than
one kind. on_string_begin fires for a string and nothing else, on_array_begin
for an integer array and nothing else — the decoder has already matched the wire's
tag before calling them. Here it has not, and an id the schema bounds can arrive
under a tag the schema never declared for it. MESSAGE_SPEC §7.3 skips such a field
like an unknown id, so answer -1 for a tag you did not declare: a bound
answered for someone else's field turns a §7.3 skip into an INVALID. A Binding
entry gets that same test run for it by the decoder, which is why the two routes
agree. subtype is None for an integer array, which carries none on the wire.
Nothing here costs an allocation — two plain integers and two interned enum
members — and that is deliberate: it is the one hook generated code overrides on
every message, so overriding it must cost nothing per field. It is also why
generated code needs no on_field to pre-filter the tag for it. on_field is the
only hook that takes a Field, and therefore the only one that makes the decoder
build one.
So is a field nobody materializes
A cap prevents an allocation, so it applies where there is one to prevent. Two routes make none, and neither is capped:
- a field the handler declines at
on_field— including one a binding does not name, or names with a contradicting wire tag (§7.3) — the decoder walks past the payload without building anything from it; - a field the handler wants, having handed back its own buffer from
on_blob_begin,on_string_begin,on_array_beginoron_float_array_begin. The hook is told the announced length or count first, and a receiver that does not want that many bytes says so there — the decision is the handler's, and a limit the decoder applied on its behalf would only take it away.
What is left is the default route, and it is the one §6.2.1 is about: with no
destination back, the decoder itself has to build a str, a bytes or a list,
and the only size it could build one from is the wire's. That allocation is
refused on the count/length word, before a payload byte is read.
on_float32_array_bits is the one route that is not in that list: it hands
over the wire bytes without asking, so the handler has no place to refuse and
the configured ceiling stays the only one.
The ceiling on a buffer you supply is that buffer's own size. Too short for what
the hook was told, and the decoder refuses it — SofaArgumentError
(InvalidArgument), never a silent truncation and never a resize. That is a fact
about your storage rather than a verdict on the message, which is why it is not
SofaLimitError.
reassembly= is the other way round: a skipped payload never enters that buffer
at all. It has no value to rebuild, so it is discarded as it arrives, across as
many chunks as it takes — which is what makes MESSAGE_SPEC §7.3's "the receiver
ignores this field" true at any size.
A schema bound is the opposite kind of thing from a cap: it is part of the
message definition, so breaching it is malformed input, not policy. The
array bindings take the declared element width for exactly that reason —
unsigned_array(..., elem_max=255) for a u8 array,
signed_array(..., elem_min=-128, elem_max=127) for an i8 one (either half may
be given alone; the other side stays open). An
element outside the declared width raises SofaDecodeError the moment its own
bytes are decoded, so the verdict never depends on how much of the array
followed it or on which engine read it (MESSAGE_SPEC §7.1). Omit them for
u64/i64, whose range is the value domain, or for an unbounded consumer.
Memory handling
The key point for Python: decoding allocates results for you unless you ask it
not to. The visitor's typed hooks hand back fresh int/str/bytes/list
objects; a Binding, or one of the five begin hooks, writes into storage you
supplied and sized instead. Encoding never allocates an output buffer at all
(§5.1).
Every aggregate has a route that does not size an allocation from the wire (§6.6.3). Each hook is told the announced count or byte length first, before a byte is decoded, and refuses a destination too short rather than growing one:
| aggregate | destination route | returns |
|---|---|---|
blob |
on_blob_begin(id, size) |
a writable buffer of size bytes |
string |
on_string_begin(id, size) |
a writable buffer of size bytes — the payload's UTF-8, validated on the way in |
| unsigned / signed array | on_array_begin(id, wtype, count) |
(dst, elem_min, elem_max) |
fp32 / fp64 array |
on_float_array_begin(id, subtype, count) |
a writable buffer of count 8-byte slots |
fp32 / fp64 scalar |
— | a value; a scalar is not storage (§6.6.3) |
§6.6.3 has a third shape — the same destination, declared once before the
decode instead of per field — and a Binding is it. Every numeric field and both
array kinds already landed in words slots you sized from the schema; the two
aggregates with no fixed-width machine form now do too:
| row | slot holds | on decode |
|---|---|---|
string(id, at, maxlen) / bytes(id, at, maxlen) |
anything | the decoder builds a str/bytes there — sized by the wire |
string_into(id, at, maxlen) / blob_into(id, at, maxlen) |
your writable byte buffer | the payload is copied into it; count_at gets the byte length |
A destination too short is SofaArgumentError at the length word, before a byte
is copied, and is never grown. A string_into payload is still validated as
UTF-8 (§6.7.2). With string_into/blob_into a whole message — scalars,
arrays, strings and blobs — decodes without a single allocation the sender chose.
fp32 additionally has §6.5's raw channel — on_float32_bits and
on_float32_array_bits, paired with Encoder.write_float32_bits /
write_float32_array_bits — for a consumer that has to reproduce the wire bytes
rather than the value.
Where this port stands against CORELIB_PLAN §6.6, stated plainly. The codec
allocates nothing a wire number sizes on the paths above — encode, a Binding
decode, and a visitor decode that takes the destination routes — and
tests/test_allocation.py, tests/test_aggregate_destinations.py and
tests/test_declared_destinations.py measure exactly that: a payload a thousand
times larger costs the same. It does not hold where a handler asks for the
value: on_string, on_bytes, on_unsigned_array and the float-array hooks
each hand back a whole object, and the only size available to build one from is
the wire's, which is what §6.6.3 says such a callback obliges. Those hooks are
kept because they are the convenient way to read a message, and every one of them
now has an opt-out — three of them, counting the declared-once form above.
Where a handler does take the value, it costs that value once. Nothing else
on the way to it is sized from the wire: no scratch copy of the payload before
the str is built, no second container for a second pass. That is the difference
between an allocation the caller asked for and one the codec made for itself, and
it is pinned by measurement — on_string costs the str, a signed array costs
what its unsigned twin costs, an fp64 array costs what an fp32 array of the
same length costs. Beneath all of it, CPython allocates for every object a
handler is given, so a literal zero is not reachable in this language whatever
the API looks like.
-
Decode: no value outlives the callback, and nothing is aliased. A handler receives a fresh
str, independentbytes, a freshint/floator a newlist, and every one of them stays valid after the decoder advances. The one exception ison_float32_array_bits, which is §6.7's pass-through route: it is handed a read-only view of the bytes you fed, and the view is released when the callback returns, so it cannot be kept by accident. -
Decode: a chunk-straddling construct is joined in one bounded buffer.
Decoder(reassembly=…)takes abytearrayyou supply, or anintfor the decoder to size one from at construction. It is required and has no default: the size decides which well-formed messages this receiver can stream, so it is a receiver policy and §6.2.1 leaves the codec none to invent. There is no other shape and it never grows: a construct that does not fit isSofaArgumentError, which is what bounds a decode's memory by construction. CORELIB_PLAN §6.6.2 requires exactly that — no sender can enlarge this buffer by sending different bytes. Size it for the largest single value you read; what you skip needs none of it. A message fed in one call never touches the buffer, whatever its size. -
Decode: a decoded value can land in your storage too.
Bindingwrites every field into slots you supply —string_into/blob_intointo a byte buffer you put in the slot — and the five begin hooks above take a buffer for every aggregate, so an array of any length costs no list and no object per element. Astringis still validated when it goes into your buffer (§6.7.2: a field the handler reads is materialized and validated); the check runs over the bytes in fixed windows, so it does not build thestrthe destination exists to avoid. What is left materialising is the scalars, and those are values, not storage. -
Decode: a suspended construct keeps its bytes, and only its bytes. Everything fed is retained until it is consumed, so a field split across chunks is never half-decoded; the consumed prefix is dropped on the next
feed, down to the first byte of the construct in flight — that byte is the one the retry re-reads from. The window held is therefore one field (for a declined sequence, one sequence), not one message. -
Decode: a fed chunk is borrowed for the call and no longer (§6). Anything the decoder still needs when
feedreturns — the tail of a construct split across the boundary, a decoded string or blob — has been copied out, so the same calling code is correct whatever the chunk boundaries are. -
Decode into your own slots. With a
Bindingthe numeric fields never become Python objects at all: they land in thewordsbuffer you passed, sized from the schema. The decoder allocates no destination and grows nothing, and a wire count past your capacity is rejected rather than honoured (§6.6). -
Decode: measuring a payload costs nothing. A string or blob is bounded against its schema
maxlenby the binding, on the length the sender declared — no re-encoding a decodedstrjust to measure it. -
Decode: a value you don't want costs nothing to get rid of. A field no binding names and no visitor wants — or one a visitor declines — walks a string, blob or fixlen-array payload by advancing the cursor, so nothing is allocated for bytes that are being discarded: skipping a 1 MiB blob already in the buffer is a pointer bump. Nothing is buffered for it either when the payload straddles a chunk boundary — a construct being discarded has no value to rebuild, so it is dropped a chunk at a time and never enters the reassembly buffer. A skipped field of any size therefore costs nothing and can never end a decode, which is what MESSAGE_SPEC §7.3 and CORELIB_PLAN §6.2.1's "a skipped field is never capped" require.
-
Encode: one ownership model — the output buffer is fixed, and never grows. CORELIB_PLAN §5.1 forbids a corelib to allocate an output buffer or to grow one, so there is a single mechanism here with three ways to reach it:
Encoder.over_buffer(buf, offset, flush)is the primitive and the only caller-supplied form: it writes in place through amemoryview, drains to the sink when full and reuses the buffer — or, without a sink, holds the message or reportsSofaBufferError. That is the shape generated code uses for a schema whoseMAX_SIZEbounds the message.Encoder(writer)installs a 1 KiB scratch buffer with a sink that forwards each bufferful towriter.write. A 100 MB message costs 1 KiB of encoder memory, and the bytes leave while the message is written, not atflush(). Nothing is retained, sogetvalue()raisesSofaArgumentError.Encoder()is the same scratch buffer with the sink appending into the result — the messagegetvalue()hands back, joined from the drained chunks (a message that fits in the scratch is never chunked at all). What grows is the message being returned, not a buffer being written into:bytes_used()never exceeds 1 KiB. One deviation, on this shape only: astring/blobrun at least a bufferful long is appended to the result directly instead of being copied through the scratch. §5.1.6 says every byte a sink receives lies inside the installed buffer, and here it does not. The run is the encoder's own immutable copy, the output bytes are identical, and no caller buffer and no caller sink exist on this path —Encoder(writer)andover_buffer(…, flush), which have both, copy everything through the buffer.tests/test_encode_buffer_ownership.pypins all three.
The scratch is one allocation per encoder, made at construction and never resized — §6.6.1's first row, the convenience "the caller … then calls the corelib", whose storage the codec keeps nothing of. §5.1.2 puts even that in the generated layer, which knows the schema: a caller who wants zero library allocation supplies the buffer with
over_buffer, and generated code that can bound its message fromMAX_SIZEshould. -
MIN_OUTPUT_BUFFERis1, and it applies to a buffer installed with a sink.sofab.MIN_OUTPUT_BUFFERis the smallest output buffer this port accepts for streaming: one byte, because the encoder splits every atomic unit — a header varint, afixlen_word, an element count, a scalar, one float — at any byte boundary.Encoder.over_buffer(buf, offset, flush)and every mid-streambuffer_set(buf, offset)that carries a flush sink requirelen(buf) - offset >= MIN_OUTPUT_BUFFERand raiseSofaArgumentErrorright there — where the buffer is handed over, never partway through a message. A buffer installed without a sink is subject to no minimum: no flush can occur, so the buffer simply holds the message or reportsSofaBufferError, and sizing it from a generatedMAX_SIZEstays exact. Nothing but the installed buffer reaches a caller's sink: astring/blobrun is copied into the output buffer like any other output, and every flush hands the sink amemoryviewover that buffer — the installed buffer itself, never a copy of it and never any other memory (§5.1.6). The one exception is the in-memoryEncoder(), which has no caller sink; it is stated with that constructor above. A sink that only reads or copies during the call may let the view go; one that keeps it has taken the buffer and must install a replacement before it returns (below). -
The handles this codec allocates, in full. CORELIB_PLAN §6.6.2 lets a codec allocate a typed handle where the language will not express a copy without one, provided it carries no message bytes and no wire number sizes it — and asks for the list. Python's only way to name a region of someone else's buffer is a
memoryview, so this port allocates four kinds and no others:handle when over the output buffer one per installation ( buffer_set), plus one full-buffer slice kept for the installation and handed to the sink at every flush of it; a short final flush builds and drops oneover the input buffer one per bytestaken out of an accumulatingbytearray, so the payload is copied once instead of twiceover a decode destination one per on_array_begin/on_blob_beginthat returns a bufferover the words buffer three per Bindinghandler, at construction, plus one per array rowNone of them holds a decoded value, and each costs the same whatever the payload's size. Everything else the codec touches after construction is the caller's storage.
-
The start offset belongs to the installation, not to the buffer. A flush sink that returns without installing anything has copied the bytes it was handed: the same buffer stays active and encoding resumes at offset 0. A sink that takes the buffer — queues it for an async write, hands it to a transport — must install a replacement with
buffer_set(buf, offset)before it returns, and that call'soffsetis where encoding resumes. Re-installing is therefore how a sink gets fresh framing-header room in every flushed packet, including when it passes the same buffer back. -
Lazy sequence framing holds no buffer. The ids
write_sequence_begin_lazyholds back are encoder state, never buffer content: the pending run isMAX_DEPTHslots wide, sized at construction and never grown (§6.6), so the hold-back reaches the full depth and every depth is canonical. A flush therefore cannot split a held-back run, and a tiny output buffer yields exactly the one-shot bytes.
Build & test
python -m venv .venv && . .venv/bin/activate
pip install -e . pytest ruff mypy # compiles the native accelerator if a C compiler is present
pytest # vectors + roundtrip + streaming + malformed + native↔pure parity
ruff check src/sofab tests # lint
mypy --strict src/sofab # type-check
assets/test_vectors.json is the shared cross-language suite, copied verbatim
from corelib-c-cpp (never hand-edited or merged here; its format is described
by test_vectors_README.md,
which stays in that repo). tests/test_conformance_vectors.py replays every
vector through encode, chunked encode, decode, byte-at-a-time decode, roundtrip
and — for the 58 vectors carrying skip_ids — a skip pass and its
byte-at-a-time variant, in which those ids are declined at every nesting level
(declining a sequence drops its whole sub-tree) and the surviving fields must
still decode to their exact values with the message fully consumed. 36 of those
58 are the skip matrix — the cross product of the construct read against the
construct skipped, so an off-by-one in any one length rule (§4.6–§4.9) shifts
the anchor field behind it and fails — and 16 more are the axes beside it:
empty payloads, lengths and counts needing two varint bytes, an 8-byte element
width, a three-byte id, and a skip at each message edge. A run prints what it
covered:
========================= shared conformance vectors =========================
test_vectors.json: 131 vectors (36 skip/matrix, 16 skip), 58 carry skip_ids; ...
vectors exercised: 131/131 (58 through the skip scenarios)
checks executed: 1075 (1075 passed, 0 failed), 0 gated out by `requires`, ...
A vector naming a capability this port lacks is gated out by its requires tag
and counted on that line; this port implements all five, so nothing is gated.
Its sequence_growth block describes a wrapper array's
container growing as elements arrive — a length no wire word announces, since
MESSAGE_SPEC §5.1 makes it highest present id + 1. That container belongs to
the layer above the codec, and this port ships that layer: sofab.collectors
(reserve_leaf, reserve_elem, reserve_row). It allocates, on the
generated layer's behalf (CORELIB_PLAN §6.6.1) — its lists are not a §6.6
breach, because the codec never calls into it: a helper is reached only from
inside a visitor callback the codec made, and the codec keeps no reference to
anything it takes.
tests/test_sequence_growth.py replays every case in the block against it, and
tests/test_collectors.py measures the growth geometry with tracemalloc
— extending to at least id + 1 in one pass, so a sparse array costs O(n) and
not O(n²).
Its header_limits block carries the other shape a ceiling has to answer: bytes
that declare a length or a count and then end, with no payload behind them.
The verdict is reached at that word — a claimed 100-byte string with nothing
after it is rejected, not reported as truncated — and it is terminal, so a
further feed re-issues it rather than consuming. Which ceiling speaks decides
the category: a receiver cap (max_dyn_string_len and its two siblings) raises
SofaLimitError, a bound the schema declared on a Binding is Status.INVALID
(§6.2.1/§6.3 vs MESSAGE_SPEC §7.1). Every rejection in the block is paired with
the same shape at a length the ceiling admits, which still answers
Status.INCOMPLETE and completes when its payload arrives.
Its header_limits_nested block is that same assertion one or two sequence
frames deeper, which is its own axis rather than more of the same: those bytes
end with their frames still open, so a decoder has a second, unrelated reason
to answer Status.INCOMPLETE, and a ceiling wired only into the top-level scope
produces a plausible-looking one. The runner pairs every rejection with a
negative control — the same bytes and the same receiver with that one ceiling
lifted out of reach, which must change the answer — because otherwise a rejection
coming from some unrelated guard would be indistinguishable from the ceiling
firing at depth. All eight nested cases run here, none gated.
tests/test_header_limits.py replays both blocks off one shared leaf, so a
nested case cannot pass by a different route than its flat twin;
tests/test_schema_bounded.py and tests/test_receiver_limits.py specify the
two ceilings in full.
If the compile fails or no compiler is available, the install falls back to
pure-Python (the extension is marked optional in setup.py). Both engines
ship, so both are run:
pytest # whichever engine is active (native if built)
SOFAB_PUREPYTHON=1 pytest # force the pure-Python engine
SOFAB_REQUIRE_ENGINE=native|python makes a run assert that sofab.IMPL is the
engine it claims to be exercising, so a missing accelerator fails the run instead
of skipping every native-gated test out of it:
SOFAB_REQUIRE_ENGINE=native pytest # fails if the accelerator is missing
SOFAB_PUREPYTHON=1 SOFAB_REQUIRE_ENGINE=python pytest # the fallback engine, pinned
CI runs the full suite twice on every supported Python — once per engine, each
pinned that way — and adds a leg installed with SOFAB_DISABLE_NATIVE=1 that
proves the compiler-less install still passes.
Benchmarks
bench/perfbench.py implements the three tools BENCH_SPEC requires — the same
workloads, on the same data, measured the same way and printed in the same
grammar as every other port, so the numbers are comparable across languages.
bench/compare_protobuf.py is extra and language-native: it compares the native
accelerator, the pure-Python fallback, and protobuf's Python runtime (upb C
backend), materializing fully on both sides so it is apples-to-apples with a
SofaBuffers visitor that takes its values:
python bench/perfbench.py bench # throughput on this machine, MB/s (MB = 1e6)
python bench/perfbench.py perf # per-op cost for the shared 12-field message
bash bench/run_callgrind.sh # instructions/op (Callgrind) — clock-independent
pip install protobuf # optional; the column is dropped if absent
python bench/compare_protobuf.py # best-of-5 MB/s table
bench / perf measure this machine and move with its load; run_callgrind.sh
counts instructions retired, which is deterministic and comparable across hosts,
so it is the one to trust when judging a change to the library itself.
(time still works as a synonym for bench.)
The workloads are BENCH_SPEC's:
| dataset | what it is there for |
|---|---|
u64 array (1000) |
the compact scalar-array path, 1..10-byte varints |
typical message |
seven mixed fields, ~37 bytes — the small-message case |
perf message (perf only) |
twelve fields, 170 bytes on every port — a size parity check |
blob 1MB |
buffer handling: 1,000,005 encoded bytes, one-shot vs. streaming vs. chunk-fed decode |
composite |
956 bytes exercising a wrapper array, non-ASCII UTF-8, depth-3 nesting, an omitted default field and a two-byte header |
The three blob 1MB rows are read against each other, never next to
typical message: five of its bytes are metadata and a million are payload, so
the absolute figure is this machine's memory bandwidth. One-shot is one
contiguous write into a 1,000,005-byte caller buffer with no sink; streaming is
the same bytes through a 4096-byte caller buffer with a flush sink, i.e. ~245
flushes; decode is fed in 4096-byte chunks. This port grants no pass-through, so
BENCH_SPEC's optional blob 1MB passthrough row is absent rather than stubbed.
Read those two encode rows as MB/s rather than Ir/op: the one-shot row is a
single 1,000,000-byte memcpy, which glibc serves from its ERMS (rep movsb)
path and Valgrind counts at about one instruction per byte, while the streaming
row's 4096-byte copies take the vectorised path at a fraction of that. Ir/op
therefore reports one-shot as the dearer of the two although it does strictly
less work. Every other row is Ir/op's to tell.
The native accelerator is worth roughly an order of magnitude over the pure
engine on the message-shaped rows and two on the array-heavy ones, and it beats
protobuf everywhere except the smallest decode, where the two are level. That
last workload is where the streaming decode costs the most: a visitor crosses the
Python↔C boundary once per field, whereas protobuf parses the whole message in
one C call. What a declared destination buys is that crossing: a field the
handler's table names is written into its slot without one, and an array of any
length costs no crossing at all rather than one per element. Every rule still
runs on the one path either way (§5.3.1).
bench/compare_protobuf.py runs that comparison.
Measured figures are not reproduced here — they belong to the cross-language benchmark arena, which runs every port on one host under one methodology. This section says how to obtain them, not what they came out as.
Release files for sofa-buffers-corelib 0.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sofa_buffers_corelib-0.11.0.tar.gz | 647.4 kB | Details |
Built distributions (wheels)
Total release size: 30.9 MB
Release files / sofa_buffers_corelib-0.11.0.tar.gz
| Download URL | sofa_buffers_corelib-0.11.0.tar.gz |
|---|---|
| Size | 647.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
91f64e01fb4f3121b9c3614c784b47f13849f62d8526739779d0fa3c2b4fef47
|
|
BLAKE2b-256 checksum How to use checksums |
a25123defaa550e0c2c9460004246deeb48c57c911f9595756e92f88b0349594
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp314-cp314-win_amd64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp314-cp314-win_amd64.whl |
|---|---|
| Size | 223.8 kB |
| Tags | CPython 3.14 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
15670c318e2d23a182d240ccc5a22aa53baf7718604987ced9860236a2bf8bbc
|
|
BLAKE2b-256 checksum How to use checksums |
b3f097df861fab743ba364fad7387801d41d17b14c2e055e36142cce7a6b6f84
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.14 Linux musl 1.2+ x86-64 |
|
SHA-256 checksum How to use checksums |
9e29870dc33f6a4fafbdf2f32c413e1ae0ea137785b9899af462393b0adc284c
|
|
BLAKE2b-256 checksum How to use checksums |
95d2bb3667f2c6041821c648c826b8dfb46b83db2393747b362cee9d676108ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | CPython 3.14 Linux musl 1.2+ ARM64 |
|
SHA-256 checksum How to use checksums |
da03c246b6dc266146c9fca2a98f514d6a98a434375e557f1215fa2ed78c0caa
|
|
BLAKE2b-256 checksum How to use checksums |
42ff8ce6dd16136b17381729ac8b4f56b79b4efa259121cd35455215eb01e9b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.14 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
1c56981646977193011c2dca23e9e1aafbdcf8cd54f5804b06f4b31b41df7057
|
|
BLAKE2b-256 checksum How to use checksums |
a76483d01572d0a271808192c4fcc64a0f5eb043112cc72fa9506f175da71dbb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | CPython 3.14 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
a3d87d08b145494dfc73d4c27a15aefe024630607a14aa73d4c5d000290276ff
|
|
BLAKE2b-256 checksum How to use checksums |
bcb6aff686188411cd1c4d6ead2776b511404de5c349a52f68e789b78a0abcc3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp314-cp314-macosx_11_0_arm64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp314-cp314-macosx_11_0_arm64.whl |
|---|---|
| Size | 239.5 kB |
| Tags | CPython 3.14 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
3df4d5613c0a87284057af1f399cff201f6cba4854b8cbf7d3108e113bc33b75
|
|
BLAKE2b-256 checksum How to use checksums |
0a5b19285ad6b3f677b6ac7e6c7a6669c206c9c696f43a11197ae28f0fc3e3b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp314-cp314-macosx_10_15_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp314-cp314-macosx_10_15_x86_64.whl |
|---|---|
| Size | 244.0 kB |
| Tags | CPython 3.14 macOS 10.15+ x86-64 |
|
SHA-256 checksum How to use checksums |
68b6eedf262a890895c2f95e564e6d4902b45e3c0ac5151bb6b23097e6263121
|
|
BLAKE2b-256 checksum How to use checksums |
39c1101e0a1733f7c2213690382a52dca30a2f03814a45ff1a0b2093e479ac87
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp313-cp313-win_amd64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 220.9 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
a185e5035f807a37a8f0f64143e8a921d24ed66b1893ae53dc021e25ff995356
|
|
BLAKE2b-256 checksum How to use checksums |
f2eb151ef8929251a9cf7ede340e9ee78cf49d18611910d1d5af7acf732dd896
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.13 Linux musl 1.2+ x86-64 |
|
SHA-256 checksum How to use checksums |
b6bb2e868192e6545d68736b110f8d3dafc361e234d2f53c307eb7f3da2f07c8
|
|
BLAKE2b-256 checksum How to use checksums |
8bce36da1d85882cae43c0aa6eb6b678b7962393b77ddb63997010fd6542066e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | CPython 3.13 Linux musl 1.2+ ARM64 |
|
SHA-256 checksum How to use checksums |
a1995686a4dd322f896d939dc81c0571187e0e84329cbbd456db98fa51321aa4
|
|
BLAKE2b-256 checksum How to use checksums |
b08b89ae9ee0c70bac89f3ecf66bfbc4363c2d88e2b0f672392edc4285c28e56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.13 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
5d2127cf1a5a9e0f55166aeb96a3e7e30cb26867bfd9d9748a13c883b7bb7abd
|
|
BLAKE2b-256 checksum How to use checksums |
4d4c07775105f5300e74e45c929cb94db5bc9c51a8af71396bb019bfea561f9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | CPython 3.13 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
0396d169fc967a6beff56b6d386016448e84abfd7e0b3df00e9fcfae331fc896
|
|
BLAKE2b-256 checksum How to use checksums |
d831a500d10bdcb420acd59b4e9d672e1051a90c1ac07cb34bd24b20af389e30
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 238.2 kB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d315d896710af11f8bca405c288be173fd602a7bbc5cb965742470e000a0f30f
|
|
BLAKE2b-256 checksum How to use checksums |
c5fe004705158368b85c88f899beb43a80bdf30b8b16485501aa7f79750cef56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl |
|---|---|
| Size | 243.6 kB |
| Tags | CPython 3.13 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
c98c75ec035111a67577cfbe5e74a5ab5b5074946c0be170feb9233f552f3563
|
|
BLAKE2b-256 checksum How to use checksums |
8ea215fb4f774def1a0deb8462954e08df158ef2d9183769580aef183d89bb08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp312-cp312-win_amd64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 221.5 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
5510f891dc7c05248482b8240ce4d1d2685a207824cf2ba74d313c772e19e395
|
|
BLAKE2b-256 checksum How to use checksums |
7e270aa664276462faebc65763f726529d1ce4f51f300aa812f5cd17bd092b7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 Linux musl 1.2+ x86-64 |
|
SHA-256 checksum How to use checksums |
7da2e10f0de86015b4ecd07a3093e72cf5b5f1ad9d169945182356e81e73cba2
|
|
BLAKE2b-256 checksum How to use checksums |
8166a671a7a5ef193a86f025328ab354a2b543c351e0a9c579df19bf33cac849
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.0 MB |
| Tags | CPython 3.12 Linux musl 1.2+ ARM64 |
|
SHA-256 checksum How to use checksums |
9d65dd809235c23e5d294358ed3509c6186a24f5796a64153297f57d58963654
|
|
BLAKE2b-256 checksum How to use checksums |
a8e8721a3df4a73df30fbf30faf2d7c4818fa2442623f2628e1ee931a7905fd4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
f656d360d3c4be91add0c2de55934a54468a2614cca1280219b4ded5f884aea9
|
|
BLAKE2b-256 checksum How to use checksums |
7400568dd6c1eb5befebf9fbfa0f012e05cf3a2605eaa952eda9bbf2b4b97aff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
5fff0f594a7bbe1514ec0dadb8f7b2d69e8e38d3a89530b4c0dbd93894498b3d
|
|
BLAKE2b-256 checksum How to use checksums |
ed7a45c8828f05ef7c538d3ec23d9762eaad564740fe6270485609d9f8735036
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 238.7 kB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
2b71eedeee90a1f6f524b51821d4bc3d3b3273cb6e39d69f987b1f8d45cba0e2
|
|
BLAKE2b-256 checksum How to use checksums |
12fee1d1647138eebfaae5aaf905ef348a598b4e2229c199f76407bae98f98c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl |
|---|---|
| Size | 244.3 kB |
| Tags | CPython 3.12 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
5af2ac264ce6ea509941369f110fc6b23258aa6a3807a9a9f0637492d4a3255d
|
|
BLAKE2b-256 checksum How to use checksums |
42192a41abd69d3809fe631fd9c1d4a2b01d81b0a907826a2823719b77760000
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp311-cp311-win_amd64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 228.1 kB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
3e249d19a5cc990605855b43d83eb689413fa24f6a23df9d836dc52cf92e3ca2
|
|
BLAKE2b-256 checksum How to use checksums |
a086db2f82e1b8c815b7e27e38ef1ffa094f9aea29ddaad1ff7dd994acec7194
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.11 Linux musl 1.2+ x86-64 |
|
SHA-256 checksum How to use checksums |
7471ca53c15a74288f9ef9286caed988fa9d7493835979db79efaf106c0d9505
|
|
BLAKE2b-256 checksum How to use checksums |
9835e88e13a051c2023f2083e79f5962eda5c68887b9387a0f8bf63f9788fbde
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 Linux musl 1.2+ ARM64 |
|
SHA-256 checksum How to use checksums |
62ac6b5a11d1a7a79f6f606e9988b1a50ab3b2c0bf509df383782399ccafed1f
|
|
BLAKE2b-256 checksum How to use checksums |
3fc137bf3ac0e17718a2ad1091a0812f60e201134cb2a7d1bd1205483ef0429e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
a70f4bdf6403bf6e8c6cb5df685887fea4ac8e8851dc00f5d24e9789486596a7
|
|
BLAKE2b-256 checksum How to use checksums |
5b8dfde02811ca96607b9a4d128673a85afbbe24acfc05a6697132081bd893d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
b69f48950c53ecd153d554ffb4d8899ed3185cd27388a4b44adc9aac75b89c12
|
|
BLAKE2b-256 checksum How to use checksums |
d7a58a468064252c5f78cad8b00fbff688952c8564a1225758a7e9262a958b0a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 241.9 kB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
72ef3209ee4e1c95c2651ff37d6ad838dabc3051d53a05297732f0ba9a683023
|
|
BLAKE2b-256 checksum How to use checksums |
c3ee539c13969bfc1683609a623b3f66be9f97029a91443b61152b69ce3fed1a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl |
|---|---|
| Size | 252.3 kB |
| Tags | CPython 3.11 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
5b952f6b3f9141ef75d805bb9ad711f1620c5d171ba047cf5fe2d068b7bdec50
|
|
BLAKE2b-256 checksum How to use checksums |
fcc43d79849542c466e4b1d2cadc178bc96a1dbc6d87291a933c2e626b25619d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp310-cp310-win_amd64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp310-cp310-win_amd64.whl |
|---|---|
| Size | 226.5 kB |
| Tags | CPython 3.10 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
a450218e7c1a03ebc48765a127ca6432c3d95d05eb94522953085a68aafb16fa
|
|
BLAKE2b-256 checksum How to use checksums |
690fd8c30215fa07f4e534f5b5214e982a9897a06ca54a1d6f6488f3ad896b32
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 |
|
SHA-256 checksum How to use checksums |
a56edd746a7c2521d8a3b4f73242d36a3c0f756d0510f160c769710ece0986f4
|
|
BLAKE2b-256 checksum How to use checksums |
af9879c38bbe17b560639149e5dc73e973157840189f97df3aca09faccb771d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 |
|
SHA-256 checksum How to use checksums |
1db5e3c7cf28265cc51b9bf019db4a1cc1c6bec9c5dc1de8a053abf117652d16
|
|
BLAKE2b-256 checksum How to use checksums |
0adb620b21c10db5edba40c10b7f76e360b57b4b5ebcd459b9de3f5799ef31f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
527121c059c66155a6761cfaab8babfd4f8a06cfc80bee83f7ecb36ebd94bf31
|
|
BLAKE2b-256 checksum How to use checksums |
fab2f97a02a5d1c34c0033764cd71188d5bc9ba9e11832accd4d272a44dfea4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
14659c9e5651bf7d24c4fe2afd9895de7456113f9ecdd8016b05ed43d767fccd
|
|
BLAKE2b-256 checksum How to use checksums |
93b6f3a6f15d236341e370d2d0c8923d1e0ec5e871103a801c69bbdfd0f38775
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp310-cp310-macosx_11_0_arm64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp310-cp310-macosx_11_0_arm64.whl |
|---|---|
| Size | 243.0 kB |
| Tags | CPython 3.10 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
000abec99476c750229e9e60b5310e75b8ad6f692196505c59abff970b81a322
|
|
BLAKE2b-256 checksum How to use checksums |
07f942afa43c715e5ceecb7b94e9bed67d80fb522340da48866c5892906f4b05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl |
|---|---|
| Size | 251.7 kB |
| Tags | CPython 3.10 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
60be2f71cdeb7c8cc00e78784f8df7bed4157e4e8e72f948375e9974bef63877
|
|
BLAKE2b-256 checksum How to use checksums |
a31edea76a08ab495a23b4db5f45cea07e2d6719f02eb6bf9f55e468d53216b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp39-cp39-win_amd64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp39-cp39-win_amd64.whl |
|---|---|
| Size | 226.7 kB |
| Tags | CPython 3.9 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
de4669273006d653450ecbf74faa537c4d9be16d0b9cb4f404e47e8b2c62cc80
|
|
BLAKE2b-256 checksum How to use checksums |
db30c888e377b2b24f0f6353662025594274452c58c4e7a573b09c9997b902d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.9 Linux musl 1.2+ x86-64 |
|
SHA-256 checksum How to use checksums |
d361e6f67f2578472ad2ccf24efe8906f1e37642ab06bf54a65d39da6f8d3b7e
|
|
BLAKE2b-256 checksum How to use checksums |
66371cce69f56ccb3ce7022b92404cbe535ea08e9e90cea30e83ed9da5f85057
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.9 Linux musl 1.2+ ARM64 |
|
SHA-256 checksum How to use checksums |
369a55fedce89e698fd2eea634f0872178c56df573e1aaa4e8f5e42432e4d9c1
|
|
BLAKE2b-256 checksum How to use checksums |
cb378f6052c664b403a475f8b29994340913a77cecbe23c78da015e56498fd57
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
b479b2c0a7b6c194da1603009ceb6c658a88b71ca770bf515fb43b19cca12ecf
|
|
BLAKE2b-256 checksum How to use checksums |
acbbf32a313ed27be9b9d3a43fc25b33001cc202782b49c637a87204db90f1a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
b1a7d49a3827f137efc3719f3efe0f5dfb99d28621adf1dc9f2ec992ca20cdbc
|
|
BLAKE2b-256 checksum How to use checksums |
69a36898ae626e03788a3d57f2a98edbe9957629e05c48eacf32a40660493fb9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp39-cp39-macosx_11_0_arm64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp39-cp39-macosx_11_0_arm64.whl |
|---|---|
| Size | 243.3 kB |
| Tags | CPython 3.9 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
cafe7c655b0629d390266d77c9628f7bcc40cbad4f022f25de88c5591bd5404f
|
|
BLAKE2b-256 checksum How to use checksums |
d0e629a578a7a002ad37a2ea7fba60d2e3fe58be372dded96265cc51b485fd5f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / sofa_buffers_corelib-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl
| Download URL | sofa_buffers_corelib-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl |
|---|---|
| Size | 252.1 kB |
| Tags | CPython 3.9 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
b3679647c67a8adcaa2dcd97349829e4b51897cd312044724c6eb7e1777be0a6
|
|
BLAKE2b-256 checksum How to use checksums |
f863a02c21a877800fddc67e2fa5a2cbca6cfe278aacea4f0fc5d8a5d52a6cf5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log