Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Dates are marked as DD-MM-YYYY
Unreleased¶
Added¶
SharedBox.read_into: copies an array field into an array the caller passes and returns it, without allocating a new array.SharedBox.writing: a context manager that holds the box's write lock and yields an array field as an array to fill in place.benchbox contention: times writes and reads of anintwhile several writer and reader processes share it, for a box,mp.ValueandSharedMemorywith aLock.
Changed¶
SharedBox: a field namedread_intoraisesTypeError.SharedBox: a field namedwritingraisesTypeError.
0.4.1 - 07-10-2026¶
Changed¶
SharedBox.update: takes about 7 to 9 ns less per call on Windows.
Fixed¶
SharedBox.events,SharedBox.watchandBoxEvents.follow: a process that exits while watcher threads still run no longer aborts withterminate called without an active exceptionon Linux with CPython 3.11 to 3.13. At exit, sharedbox stops every watcher thread and waits up to 2 seconds in all for them to end, including any callback they are running.
0.4.0 - 05-10-2026¶
Added¶
benchbox plot: draws the results ofbenchbox allas SVG charts, in a light and a dark variant. Thebenchmarksextra now includes matplotlib.SharedBox: fields of typecomplex,datetime.date,datetime.time,datetime.datetime,datetime.timedelta,uuid.UUIDandAnnotated[decimal.Decimal, Capacity(n)].SharedBox:enum.Enum,enum.Flagandtyping.Literalfields.SharedBox:X | Noneand union fields.SharedBox: record fields: dataclasses,NamedTuple, fixed-length tuples,TypedDict, attrs classes andmsgspec.Struct.SharedBox:list,tuple[T, ...],set,frozenset,dictandbytearrayfields and theircollections.abcforms, with aCapacityin elements.Shape,DType,SupportsDLPack,register_array_type: array fields through DLPack.sharedbox.hpp:type_view,handle::field_type,handle::types_table,handle::major_version,oldest_layout_major,time_value,datetime_value,timedelta_value, the kind codeskind_complextokind_decimalandkind_enumtokind_array,max_type_depth,max_types_size,max_mapping_size,first_described_kind,max_date_ordinal,unix_epoch_ordinal,micros_per_day,max_offset_minutes,literal_nonetoliteral_enum,type_head,dl_dtype,literal_value,encode_*anddecode_*functions for complex, date, time, datetime, timedelta, uuid, flag and position values,decode_bool,decode_present,decode_tag,decode_length,handle::read_used,handle::read_large,handle::read_record_largeandhandle::write_large.sharedbox_c.h:sbx_field_desc, the structssbx_time,sbx_datetimeandsbx_timedelta, and typed reads and writes of complex, date, time, datetime, timedelta, UUID, enum and literal positions, flag bits and optional presence.benchbox ops: rows for datetime, record, list and array fields.
class Frame(SharedBox):
taken: datetime.datetime
image: Annotated[np.ndarray, Shape(480, 640), DType("uint8")]
tags: Annotated[list[Annotated[str, Capacity(16)]], Capacity(8)] = field(default_factory=list)
Changed¶
- Segments use layout 2.0. Releases 0.3.0 and 0.3.1 cannot open a box made by this version; this version opens boxes made by them.
SharedBox.eventsandSharedBox.watch(): a field counts as changed when its stored bytes change. WritingNaNagain no longer emits;-0.0after0.0does.Capacity: also sets the most elements of a collection.sharedbox.hpp: the inline namespace isv2;handle::createandhandle::create_unpublishedtake a description table.- The
benchmarksextra includes numpy. SharedBox.updateandSharedBox.snapshot: native methods, unless a class defines or inherits its own.
Fixed¶
SharedBox: a class with more than one box base keeps the defaults and default factories of the fields it inherits from every base, not only the first.
0.3.1 - 01-10-2026¶
Added¶
sharedbox.__version__: the version of the installed package, as a string.
0.3.0 - 01-10-2026¶
Added¶
SharedBox: base class whose annotated fields live in a shared-memory segment.Capacity: byte capacity forstrandbytesfields.FieldWatch:forandasync forover new values of a field.SharedBox.events: psygnalSignalGroupwith one(new, old)signal per field.BoxClosedError,LockTimeoutError,SchemaMismatchError,SegmentExistsError,SegmentNotFoundError.benchbox: command withops,roundtrip,sizeandallbenchmarks, also run aspython -m sharedbox.benchmarks.benchmarksextra: installs Typer and pyperf forbenchbox.SharedBox:identityclass keyword; it enters the schema hash and names the box.SharedBox:max_waitersclass keyword, 1 to 4096 box handles that watch at once, default 64.SharedBox.__sharedbox_box__(): returns a"sharedbox_box"PyCapsule for other extensions.SupportsSharedBox: protocol for functions that accept a box.get_include(): folder holdingsharedbox/sharedbox.hpp,sharedbox/sharedbox_c.handsharedbox/sharedbox_c.cpp.sharedbox.hpp: header-only C++20 implementation of the segment layout, installed in the wheel with a CMake config (sharedbox::headers), which linksbcrypton Windows andrtandThreads::Threadson Linux.sharedbox-config-version.cmake: installed next tosharedbox-config.cmake;find_package(sharedbox 0.3)accepts 0.3 versions only.sharedbox_c.h: minimal C interface (sbx_open,sbx_import,sbx_schema_hash,sbx_read,sbx_write,sbx_release), built throughsharedbox::c.
class Frame(SharedBox, identity="camera/frame/1", max_waiters=16):
exposure: float = 0.01
count: int = 0
field(): per-fielddefault,default_factory,init,repr,kw_only,metadataanddocforSharedBoxfields.fields(): theFieldof each field of aSharedBoxsubclass or box.Field: read-only description of oneSharedBoxfield.SharedBox:dataclasses.InitVarannotations, passed to__post_init__and not stored.SharedBox.__post_init__(): runs after a box is created and before other processes can attach to it.SharedBox:inspect.signature()of a subclass gives its constructor parameters.sharedbox.hpp:handle::create_unpublished()andhandle::publish().SharedBox: reference fields, annotated with aSharedBoxsubclassXorX | None; reading one attaches the box it refers to, with that box's own class.BoxRef: frozen dataclass (name,schema_hash,create_id, and thebox_classproperty) thatsnapshot(),eventsandwatch()report for a reference field.BrokenReferenceError: raised when the box a reference field refers to was removed or created again since it was assigned.UnknownBoxClassError: raised when the box a reference field refers to has a class this process has not defined.SharedBox.snapshot():followkeyword;follow=Truereplaces each reference with the snapshot of the box it refers to.sharedbox.hpp:kind_refandbox_ref, the kind code and stored value of a reference field.
class Motor(SharedBox):
position: int
limit: int = field(default=100, kw_only=True, metadata={"unit": "mm"})
offset: InitVar[int] = 0
def __post_init__(self, offset: int) -> None:
self.position += offset
class Stage(SharedBox):
motor: Motor | None = None
BoxEvents: psygnalSignalGroupsubclass thatSharedBox.eventsreturns.BoxEvents.follow(): given a reference field, returns a group whose signals are emitted for changes inside the box the field refers to, whichever box that is; given no field, emits onnestedevery change inside the boxes the reference fields reach.BoxEvents.unfollow(): stops forwarding started withfollow().BoxEvents.nested:(path, new, old)signal of a class with reference fields.
motor_events = stage.events.follow("motor")
motor_events.position.connect(lambda new, old: print(new))
stage.events.follow()
stage.events.nested.connect(lambda path, new, old: print(path, new))
- Documentation site at https://jacopoabramo.github.io/sharedbox: tutorial, how-to guides, explanations and the API reference.
Changed¶
- Building the extension requires nanobind 3.1.0 or newer and a C++20 compiler.
- Wheels per platform:
cp311-cp311,cp312-abi3for CPython 3.12 and newer, andcp314-cp314tfor free-threaded CPython 3.14. SharedBoxfields are converted in the native module and packed by alignment; segments use layout 1.0, a plain named mapping.SharedBox: the default box name is 16 hex digits of SHA-256 over the identity, with nosharedbox-prefix. A box created by an earlier release undersharedbox-<hash>is not found under the new default name.- Shared-memory objects are named
sharedbox.<name>(/dev/shm/sharedbox.<name>,Local\sharedbox.<name>); boxes of earlier releases are not found under them. SegmentExistsError: says whether the box's creator is still running, runs in another pid namespace, or has exited, or that the name holds no published box.- A pickled
SharedBoxcarries its class's schema hash and its box's create id; unpickling with a different class, or after the box was created again, raisesSchemaMismatchError. SharedBox.attach(): shared memory that does not become a box within 1 s raisesSegmentNotFoundError.SharedBox.attach(): a segment of another layout major version raisesSchemaMismatchErrornaming the version.SharedBoxfields: reading or writing one field runs no Python code; deleting one raisesAttributeError.SharedBox: a subclass whose__slots__names_segmentraisesTypeError.SharedBox: an error about a field names the field asClass.field.SharedBox: a read or write that waits for another writer's lock lets other threads run.SharedBox.update(): takes about half the time; field names are checked only when one is unknown.SharedBox.snapshot(): builds its dict in the native module.SharedBox: thekw_onlyclass keyword and aKW_ONLYannotation apply only to the fields of the class that declares them; a subclass keeps each inherited field's keyword-only setting.SharedBox: a subclass that sets an unannotated class attribute on the name of an inherited field raisesTypeError.SegmentExistsError: for a box whose creator is still running__post_init__, says the box is being created by that pid.SharedBox.__sharedbox_box__(): raisesBufferErrorbefore the box is published.sharedbox.hpp:handle::openandhandle::from_capsuleaccept a field whose kind code they do not know and treat its bytes as opaque.sharedbox.hpp:handle::writereturnsstatus::rangefor a field whose kind code it does not know, andsbx_writereturnsSBX_E_RANGE.SharedBox.attach(): a segment with a field of a kind this version cannot read raisesSchemaMismatchErrornaming the kind.SharedBox: an annotation naming an undefined class raisesTypeError.SharedBox: a field namedfollow,unfollowornestedraisesTypeError.SharedBox.close(): called from an event callback, on a watcher thread, does not wait for the watcher threads of the boxes it closes, and drops the writes they had not delivered.
Fixed¶
SharedBox.watch(): no longer yields the same value twice.SharedBox.attach(): no longer fails when it runs while another process is still creating the box.SharedBox: a process that attaches while the box is being created sees the initial values, never a zeroed record.SharedBox.close(): no longer deadlocks while another thread's read or write waits for the write lock.SharedBox.close(): returns without waiting for the watcher thread's 0.1 s poll.SharedBox.watch()andSharedBox.events: on Windows, a write no longer reaches one of several waiting threads up to 50 ms late.SharedBox.watch()andSharedBox.events: keep delivering when every waiter slot is taken.SharedBox: on Linux, a box larger than the free space of/dev/shmraisesOSErrorat creation instead of the process receivingSIGBUS.
Removed¶
- Python 3.10 support.
SharedDictandsharedbox.utils.- Boost and vcpkg: the build no longer needs
VCPKG_ROOT. docs/api.md,docs/library-authors.mdanddocs/design/: their content is on the documentation site.
0.2.4 - 05-10-2025¶
Changed¶
- Rewrite codebase in nanobind
Fixed¶
- Parallelize CI so that each wheel is built with the correct version
- Also faster builds
0.1.0 - 29-09-2025¶
Added¶
- Initial release