Package name: sitos. Issue #22 validates CPython 3.12 wheels: the
manylinux_2_28_x86_64 production target on Ubuntu 24.04/Rocky Linux 10 and
non-publishing win_amd64 development coverage. Other Python versions and formal Windows
publication are deferred. The package is a nanobind wrapper for the C++ core plus a thin
pythonic layer [P01].
| payload v1 | Python (Get) | Python (types accepted by Put) |
|---|---|---|
| BOOL | bool |
bool |
| S64 | int |
int (OverflowError if outside the 64-bit range) |
| DP | float |
float |
| STR | str |
str |
| BYTES | bytes |
bytes or supported NumPy arrays |
Issue #27 adds exact NumPy ndarray conversion and zero-copy ParamCache.get_array. NumPy 2.x is
required; NumPy 1.x is not supported. NumPy inputs must be C-contiguous, object-free, fixed-width
numeric or boolean arrays and are copied once into owned BYTES; shape and dtype are not serialized.
General buffer objects, bytearray, and memoryview remain rejected. Payload v1 does not carry
dtype or shape metadata ([03] §2.1).
The initial Python wheel exposes only the payload-v1 conversion helpers. They are intentionally small and form the shared foundation for later ParamStore, ParamCache, and SessionView bindings.
import sitos
payload = sitos.encode_value(240.0) # type tag + little-endian payload-v1 body
value = sitos.decode_value(payload) # -> 240.0encode_value accepts bool, signed-64-bit-range int, float, str, bytes, and exact NumPy
ndarrays with supported C-contiguous, object-free, fixed-width numeric or boolean dtypes. Array
input is copied once into owned BYTES; shape and dtype are not serialized. Integers outside the
signed 64-bit range raise OverflowError; unsupported input raises TypeError. decode_value
accepts only bytes and raises ValueError for malformed, truncated, overlong fixed-width,
unknown-tag, or
invalid UTF-8 payloads. Existing codec behavior is preserved, including canonical NaN encoding and
nonzero BOOL decoding. General buffer-protocol inputs remain unsupported.
Issue #23 provides a non-callback, ack-less Python facade over the synchronous C++ ParamStore.
Each instance opens and owns its own Transport/session from zenoh_config_json; raw session or
Transport sharing is not part of this API. query_timeout_ms is a positive integer in the same
milliseconds unit as C++ ClientConfig.
import sitos
with sitos.ParamStore(prefix="sitos", zenoh_config_json=None,
query_timeout_ms=5000) as store:
store.put("base", "recon/fov", 240.0)
store.put_batch("base", [("recon/fov", 240.0), ("recon/kernel", "sharp")])
store.delete("base", "recon/tmp")
value = store.get("base", "recon/fov") # automatic Python type
value = store.get("base", "recon/fov", type=int) # C05 numeric conversion
exists = store.contains("base", "recon/fov")
rows = list(store.list("base", "recon")) # eager owned snapshotput_batch also accepts a Mapping. Pair iterables preserve caller order and duplicate keys;
all entries are validated before one wire submission. Writes are submission-only. list eagerly
queries, validates, sorts, and materializes owned (relative_key, value) pairs before returning
an iterator.
ParamStore exports sitos.SitosError and its NotFoundError, TypeMismatchError,
TimeoutError, DisconnectedError, and ReadOnlyError subclasses. Missing values raise
sitos.NotFoundError, unless an explicit default is supplied; the default is returned unchanged
only for NotFound. Type conversion failures raise sitos.TypeMismatchError; timeout,
disconnection, and read-only failures raise their corresponding subclasses. Status::Error raises
sitos.SitosError, while invalid keys and arguments raise built-in ValueError. close is
idempotent, rejects later calls, and allows already-admitted native operations to finish safely.
Subscriptions and acknowledgements remain outside Issue #23.
Issue #24 provides a non-callback, session-only Python facade over C++ ParamCache under ADR-0022 and
ADR-0023. Each instance opens and owns its Transport/session from zenoh_config_json; it has no
attach_base or raw Transport-injection API. query_timeout_ms is a positive integer used by the
initial snapshot and overlay fetches.
with sitos.ParamCache(prefix="sitos", zenoh_config_json=None,
query_timeout_ms=5000) as cache:
cache.attach(sid) # Fetch snapshot + overlay
fov = cache.get("recon/fov", default=240.0)
count = cache.get("recon/count", type=int) # C05 numeric conversion
cache.put("recon/progress", 0.5)
cache.put_batch([("recon/a", 1), ("recon/a", 2)])
exists = cache.contains("recon/fov")
rows = list(cache.items("recon")) # Eager owned local snapshot
cache.detach() # Re-attach remains possibleput_batch accepts either a Mapping in iteration order or an iterable of two-item pairs. Pair
iterables preserve order and duplicate keys; every entry is materialized and validated before one
canonical :batch submission. Successful non-empty writes submit first and then immediately apply
to the initiating cache. For duplicate keys, the last value serialized through each cache's local
sequencing path wins independently in the initiating cache and asynchronously updated peers. There
is no global publisher order or self-echo deduplication. Peer delivery is asynchronous, and success
is not an Issue #99 delivery barrier. Empty batches succeed without submission.
Reads and items are cache-local and perform no wire request. items uses raw-prefix matching,
lexical ordering, and returns an iterator over an eager owned snapshot that remains usable after
detach or close. Missing values, typed conversion, and Status exceptions use the same contract and
exception classes as ParamStore.
detach is idempotent and native-quiescent while leaving the object reusable. close is
idempotent, terminal, stops new operation admission, waits for already-admitted binding operations,
and returns
only after owned native resources are released. Operations after close raise
ValueError("ParamCache is closed"); a harmless repeated detach is permitted.
Stale/reconnect state, Python callbacks, and WaitForLocalDelivery remain
deferred to Issues #20, #26, and #99. Issue #27 provides
ParamCache.get_array(key, *, dtype=...) as a one-dimensional,
read-only zero-copy view over immutable cached BYTES. It accepts fixed-width numeric and boolean
NumPy dtypes, preserves explicit byte order without conversion, and does not infer or serialize
shape. The array keeps the exact cached value alive across overwrite, detach, close, and cache
destruction.
engine = sitos.InMemoryEngine() # opaque StorageNode-owned engine
with sitos.StorageNode(engine, prefix="sitos", zenoh_config_json=None) as node:
node.create_session(sid)
node.close_session(sid)
node.active_sessions() # -> sorted list[str]StorageNode starts during construction and stop() is terminal, idempotent, and quiescent.
The binding opens and owns its Zenoh transport; raw Transport/session injection is not public.
InMemoryEngine exposes no direct CRUD methods. RocksDB implementation belongs to Issue #8;
publication/distribution readiness belongs to Issue #35. Custom Python engines are deferred to
Issue #28.
Independent StorageNode, ParamStore, and ParamCache sessions must run in separate processes while using the pinned zenoh-c 1.9.0 runtime. Same-process independently opened sessions are unsupported.
SessionView is a read-only, owned view over the StorageNode session overlay and snapshot.
It exposes exact built-in typed reads, absence-only defaults, contains, and eager lexical
items snapshots:
view = node.session_view(sid)
value = view.get("recon/fov", default=240.0)
count = view.get("recon/count", type=int)
exists = view.contains("recon/fov")
rows = list(view.items("recon"))items uses raw-prefix semantics, resolves overlay before snapshot, validates and sorts all values
before constructing Python objects, and returns an owned iterator. Only an iterator already returned
by items remains usable after session close or node stop; subsequent reads through that
SessionView, including later items calls, report the documented lifecycle errors. put,
put_batch, delete,
callbacks, spans, NumPy, and zero-copy APIs are intentionally not part of SessionView.
Large binary values use the disk-backed buffers API, not the session overlay or ParamCache.
The repository provides examples/python/quickstart.py, examples/python/numpy_lut.py, and
examples/python/raw_zenoh.py as source-only tutorials. They are not installed into the wheel and do not add package entry points.
The quickstart and LUT examples use multiprocessing.get_context("spawn"): the coordinator owns
no sitos object, and separate node, writer, and cache processes each own at most one Zenoh session.
Independent StorageNode, ParamStore, and ParamCache objects in one process remain unsupported
with the pinned zenoh-c 1.9.0 runtime. The examples use default local discovery, unique prefixes and
session IDs, bounded Pipe handshakes, and failure-safe child cleanup. A successful put is only a
submission; examples resubmit the identical value while independently observing the expected result.
numpy_lut.py uses a caller-supplied <f4 dtype and demonstrates a one-dimensional read-only
zero-copy view over cached BYTES. Payload v1 does not transport shape or dtype metadata, and the
example makes no inference or zero-copy submission claim. raw_zenoh.py requires the test/example
installation eclipse-zenoh==1.9.0, uses only Zenoh, struct, and the standard library in its raw
role, and verifies the canonical zenoh/bytes;sitos.v1 payload-v1 representation without importing
sitos. The raw dependency is not a sitos runtime dependency.
- zenoh threads in the C++ core do not acquire the GIL
- Notifications to Python callbacks are one-way: “C++-side queue → dedicated Python dispatch thread (acquires the GIL)”. zenoh threads never block waiting for the GIL
- ParamCache
get,contains, anditemsare local C++ reads and keep the GIL.itemsmaterializes into C++-owned storage before constructing Python tuples - Client Open, Attach, Detach, terminal Close/destruction, Put, and PutBatch release the GIL while native work may block. Python input conversion and result construction occur with the GIL held
ParamCache.get_arrayretains the GIL while validating dtype and constructing the NumPy view; its ndarray owner releases only a C++ shared owner and never calls Python from a native callback- In a StorageNode that uses a Python engine (§2.3), zenoh threads call into Python, so GIL acquisition occurs. State explicitly that C++ engines are recommended for production use
- Issue #27 installs
py.typedand publicsitos/*.pyistubs and verifies them with blocking mypy; pyright remains nonblocking/deferred - docstrings must match the content of the C++ Doxygen comments
- Include an interoperability example in README for “talking to sitos with zenoh-python only” [C03]:
import zenoh, struct
session = zenoh.open(zenoh.Config())
# DP in sitos payload v1 (type tag 2 + double LE)
session.put("sitos/base/recon/fov", bytes([2]) + struct.pack("<d", 240.0),
encoding=zenoh.Encoding.ZENOH_BYTES.with_schema("sitos.v1"))Wheel structure and build are described in 06_build_test_packaging.md.
The standard wheel installed by pip install sitos bundles InMemoryEngine + zenoh-c,
and has only NumPy as an additional runtime dependency (simplified as a required dependency,
not optional sitos[numpy]) [P03].
(END OF DOCUMENT)