Repository navigation
Releases
v0.9.0
Added
segment_status:segment_status("lab:counts")returns aSegmentStatus,
missingwhen no box or stream is published under the name,runningwhile the
process that created it runs andcreator_exitedonce it has ended.
core.hppaddssegment_statusandsegment_statefor C++. (#94)SharedReceiveStream.fetch,fetch_intoandfetch_future:reader.fetch(120)
returns the item at position 120, waiting until the sender has sent it, and
leaves the reader's receive cursor andmissedunchanged.ItemGoneError, a
LookupErrorwhoseoldestis the oldest position the ring held, is raised
once the ring no longer holds the item.stream.hppadds
stream_reader::fetchandfetch_withfor C++. (#95)MaxShapeon box fields: a box field declared as
Annotated[npt.NDArray[np.uint16], MaxShape((2048, 2048))]holds an array of
any shape up to that maximum, as kind 75 of box layout 3.3, and every read
returns an array of the stored shape. A write stores the shape and only the
elements in use.SharedBox.writingtakesshape, so
box.writing("image", shape=(480, 640))sets the shape and yields a view of
it under one lock.read_intoraisesTypeErrorfor aMaxShapefield. (#97)BoundedList,BoundedSetandBoundedDicttake their element types as the
first arguments when the class fixes none:BoundedList(int, [1, 2], capacity=4)
equalsBoundedList[int]([1, 2], capacity=4), and
BoundedDict(str, float, {"a": 1.0}, capacity=8)takes the key and value types. (#97)TimedandTimed.now: a box field, record member, collection element or
stream item declared asTimed[float]stores the value with a timestamp in
nanoseconds since the Unix epoch, andbox.temperature = Timed.now(21.5)stamps
it with the system clock when the write holds the box's lock, or just before a
stream send publishes the item. ATimedwith a timestamp of its own is stored as
given. Timed values are kind 77, and streams use stream layout 1.5. (#99)Attr,attr,AttrValue,RequestFutureandRequestServer: a box
field declared asexposure: Attr[float] = attr(timed=True, settable=True)
holds a value its owner writes with
box.exposure.publish(value=..., setpoint=...)and any process reads with
.value,.setpoint,.timestamp_nsorread(), or watches with
watchandevents. An attribute with arguments, such as
move: Attr[float, [float]] = attr(), or a settable one carries a typed
request to the one process that serves it:box.move.serve(handler)
answers with a sync or async handler, andbox.move(5.0)or
box.exposure.set(0.02)returns aRequestFuturewaited on with
.result(timeout)orawait. Of the requests sent while the handler
runs, only the newest is handled next.RequestError,
RequestSupersededErrorandServerExitedErrorreport a failed, replaced
or abandoned request, andAlreadyServedErrora second server. Attribute
fields are kind 76 of box layout 3.3, andbox.hppaddsattr_publish,
attr_read,attr_published,attr_value_atand the request protocol
steps onhandlefor C++, andtype_viewgives an attribute's flags and
where its value, timestamp and setpoint start. (#103)
Fixed
- On Windows, an operating system failure to create or open a box or stream raises
OSErrorwith the Windows error code inwinerror, such asPermissionError
for a segment the process may not open, instead ofRuntimeError. (#96)
v0.8.0
Breaking
-
sharedbox.Shapetakes its dimensions as one tuple,Shape((480, 640)), in
place of one argument per dimension. See
How to migrate from 0.7. (#88) -
SharedBoxreserves the namesmetadata,metadata_versionand
metadata_version_of: a field orInitVarwith one of them raises
TypeErrorwhen the class is defined. Rename the field:# 0.7 class Parcel(SharedBox): metadata: Annotated[str, Capacity(64)] = "" # 0.8 class Parcel(SharedBox): label: Annotated[str, Capacity(64)] = ""
SharedBox.updatetakes the keywordmetadata, so a type checker refuses
update(**values)when the values of the dictionary have a type that the
box's metadata type does not accept, such asstr:# 0.7 def restock(parcel: Parcel, values: dict[str, str]) -> None: parcel.update(**values) # 0.8 def restock(parcel: Parcel, values: dict[str, Any]) -> None: parcel.update(**values)
See
How to migrate from 0.7. (#91)
Added
MaxShape: an array of a stream item declared with a maximum shape, as in
Annotated[npt.NDArray[np.uint16], MaxShape((2048, 2048))], stores its own
shape with each item, as kind 75 of stream layout 1.2.writing()views the
slot in the shape of the template's array,slot.sendpublishes the first
rows of that view without a copy, andreceive_intoreturns a view of the
stored shape overout. (#88)SharedStream.sender(on_full="release"): a send that has waited its whole
timeoutreleases the lossless readers a full ring behind and sends, as in
stream.sender(on_full="release").send(item, timeout=0.1). Each released
reader raisesReaderReleased, whosemissedcounts the items it lost, and
goes on at the newest item;ReceiveEvents.releasedreports the count to
callbacks.SharedSendStream.asendandawritingtake atimeout. In
stream.hpp,send,send_withandbegintake anon_fullpolicy, and a
released reader'sreceivegivesstatus::released, in stream layout 1.3. (#89)MetadataFullErrorandMetadataGoneError: raised when an add finds no
room for a version, and when the version a read asks for is no longer held. (#91)SharedStream.metadata: a stream keeps metadata the same way.
SharedStream.create(Reading, capacity=8, metadata={"unit": "kg"})stores
version 1.SharedSendStream.send,asend,send_nowaitand
SendSlot.sendtakemetadata=and add a version that applies from that
item.SharedReceiveStream.metadataandmetadata_versiongive the version
of the item the reader received last.SharedStream.createtakes
metadata_capacityandmetadata_versions, andcreateandattachtake
metadata_type.sharedbox0.7.0 and earlier cannot open a stream created
with metadata or ametadata_capacity. (#91)box.hpp,stream.hppandsharedbox_c.h: box layout 3.2 and stream
layout 1.4 hold metadata after the record or the description table.
handleandstreamgainmetadata_headandread_metadata, and
stream_readergainsmetadata_version_at.handleandstream_sender
gainadd_metadata.handle::write(values, doc, lock_timeout)and
write_largewith the same arguments write fields and add a version in one
write, and
stream_sender::can_add_metadatachecks an add without making it.
sharedbox_c.haddssbx_metadata_head,sbx_read_metadata,
sbx_add_metadataandSBX_E_METADATA_FULL. (#91)SharedBox.metadata: a box keeps metadata, JSON-like values stored as CBOR
in its segment, in numbered versions.Scale.create(name, metadata={"unit": "kg"})stores version 1, andSharedBox.update(metadata=...)adds the next
one from any process, with or without field values, in one write.
metadata,metadata_versionandmetadata_version_ofread the versions,
a map as a read-only mapping and a list as a tuple.BoxEvents.metadatais
emitted with each new version. The class keywordsmetadata_capacityand
metadata_versionssize the room for versions, and
class Scale(SharedBox[Calibration])gives the metadata's type to type
checkers. (#91)
Changed
sharedbox.DTypeis aStrEnumwith one member per element type:
DType(numpy.uint8)returnsDType.uint8, which equals"uint8". (#88)
Fixed
SharedBoxandSharedStreamat exit: theatexithook ofsharedbox
closes every box and stream the process still has open and releases the
native objects ofSharedBoxsubclasses, their watches and events groups,
so nanobind reports no leaked instances when a box class defined at module
level outlives shutdown. A box or stream used after the hook raises
BoxClosedErrororStreamClosedError, and creating or attaching a box
raisesBoxClosedError. (#90)
v0.7.0
Added
np.int8,np.int16,np.int32,np.uint8,np.uint16,np.uint32,
np.uint64,np.float16,np.float32,np.complex64andnp.datetime64
as field types, record members, collection elements and stream items,
stored at their own width as kinds 13 to 23 of box layout 3.1 and stream
layout 1.1. A field reads back as its numpy type, and a value that does not
fit the width raisesOverflowError. Anp.datetime64field stores
nanoseconds since 1970. (#66)sbx_read_float16,sbx_write_float16,sbx_read_float32and
sbx_write_float32insharedbox_c.h. (#66)- An array annotated
X | Noneas a field, record member or stream item,
holdingNoneor an array of its sizes, as in
scan: npt.NDArray[np.uint8] | None = field(default=None, shape=(32, 32)).
receive_intodoes not fill one. (#67) field(capacity=...): the capacity of a box field of text, bytes,
bytearray,Decimalor a collection, as in
samples: list[float] = field(capacity=16). (#68)- An array member of a dataclass or attrs record takes its sizes from
metadata={"shape": (32, 32)}on its field. (#68) BoundedList[T]: a list value that keeps the stored bytes of a list field,
as insamples: Annotated[BoundedList[float], Capacity(65536)]. It takes
every element typelist[T]takes and is stored aslist[T]is. A read
copies the bytes without decoding an element, a write of aBoundedListof
the field's element type copies them back, indexing decodes or encodes one
element, and a change past the capacity raisesValueError. Subclasses,
generic or with a fixed element type, can be declared in its place. (#73)BoundedSet[T]: a set value that keeps the stored bytes of a set field,
as inseen: Annotated[BoundedSet[int], Capacity(65536)]. It is stored as
set[T]is. A read copies the bytes without decoding an element, a write
of aBoundedSetof the field's element type copies them back, andin,
addandremoveencode one key and compare stored bytes, through an
index after 64 lookups in a set of more than 8 elements. The set operators take any set and return a
BoundedSetof the same class and capacity, the comparisons take any set,
and a change past the capacity raisesValueError. A declaration raises
TypeErrorfor an element type whose equal values could be stored
differently, such asDecimalorint | float. Subclasses, generic or
with a fixed element type, can be declared in its place. (#77)BoundedDict[K, V]: a dict value that keeps the stored bytes of a dict
field, as inweights: Annotated[BoundedDict[int, float], Capacity(4096)].
It is stored asdict[K, V]is. A read copies the bytes without decoding
an item, a write of aBoundedDictof the field's key and value types
copies them back, andd[key],getandinencode one key and compare
stored bytes, through an index after 64 lookups in a dict of more than 8
keys. It keeps insertion order,|takes any mapping and returns a
BoundedDictof the same class and capacity, and a change past the
capacity raisesValueError. Keys follow the rules ofBoundedSet
elements, and values can be of any type adictfield holds. Subclasses,
generic or with fixed key and value types, can be declared in its place. (#82)BoundedListofint,float,bool,complexor NumPy integer, float,
complex orbool_elements supports the buffer protocol:np.asarray(value)
andmemoryview(value)share its elements without decoding them, read only
forboolandbool_, and a change of length raisesBufferErrorwhile
such a view exists. (#85)
Changed
- Reading a
list,tuple,set,frozensetordictfield allocates and
copies less, and reading a record whose class takes every member by position
no longer builds a dict of keyword arguments. (#65) - A union stores a number through its first member of the same kind of
number, sonp.int64(5)written tofloat | intreads back as5. (#66) int,float,complexandboolfields take numpy's integer, floating,
complex and bool scalars, and read back the Python type.np.int64,
np.float64,np.complex128andnp.bool_fields read back as those types
and hash asint,float,complexandbool. (#66)- The extension is compiled for speed instead of size. Writing a
list,set
ordictfield, and reading, indexing or looking up aBoundedListor
BoundedSet, take less time. The wheels are about 75 KB larger on Linux and
17 KB larger on Windows. (#79)
Fixed
- A
list,setordictwhose slots end short of its alignment, such as
Annotated[list[Color], Capacity(3)]for anEnumColor, is accepted;
its size is rounded up to the alignment.sharedbox0.6 refuses a segment
that holds one. (#66) - A
TypeErrorabout the sizes of an array member of a dataclass or attrs
record names its metadata shape, as inmetadata shape (3), instead of
field(shape=...). (#70)
v0.6.0
Added
-
status::foreignandSBX_E_FOREIGN(-12): the segment's magic is not
one this version knows. Python raisesSchemaMismatchError. (#47) -
sharedbox::to_expectedandsharedbox::from_expected: convert a
sharedbox::resultto astd::expectedand back on C++23. (#47) -
KindMismatchError, aSchemaMismatchErrorraised when a name holds a
segment of another kind;status::kind_mismatchandSBX_E_KIND(-11)
insharedbox.hppandsharedbox_c.h. (#47) -
sharedbox::stream,sharedbox::stream_senderandsharedbox::stream_reader
instream.hpp, included bysharedbox.hpp: a ring in shared memory with
one sender and up tomax_readersreaders, each reading
read_mode::lossless,read_mode::lossyorread_mode::latest, laid out
as stream layout 1.0. C++ only.stream_sender::begin,publishand
abandonreserve a slot, fill it in place and publish it or give it up. (#48) -
status::busy(-13),status::ended(-14) andstatus::interrupted
(-15), withSBX_E_BUSY,SBX_E_ENDEDandSBX_E_INTERRUPTEDin
sharedbox_c.h. (#48) -
SharedStream,SharedSendStreamandSharedReceiveStream: send items of
any type aSharedBoxfield takes from one process to up tomax_readers
readers, each readinglossless,lossyorlatest, with blocking,
non-blocking and asyncio calls,receive_intointo the caller's arrays and
receive_future, aconcurrent.futures.Future. A stream passed to another
process arrives attached, or raisesSchemaMismatchErrorif its name now
holds a stream created since, andSharedStream.createwithout a name gives
the stream a new one. The ends cannot be pickled;SharedSendStream.stream
andSharedReceiveStream.streamreturn their stream. (#49) -
SharedReceiveStream.events, aReceiveEventspsygnal group whose
receivedandendedsignals deliver a reader's items to callbacks. (#49) -
WouldBlock,EndOfStream,StreamBusyErrorandStreamClosedError. (#49) -
SharedStreamStatisticsandReceiveStreamStatistics, returned by
SharedStream.statistics(). (#49) -
WaiterSlotsFullErroris exported fromsharedbox; opening a reader
raises it whenmax_readersreaders are open. (#49) -
benchbox ops: the rowwatched write int, a write while another process
has a watcher open. (#50) -
sharedbox::handle::sleepers(): the number of threads inside a wait on
the box, in any process. (#51) -
benchbox stream: items per second ofSharedStreamreaders in each mode
againstmp.Queueand aSharedMemoryring with a lock, and a table of
sendandasendwithreceive,async forandevents.received,
with the 50th, 90th and 99th percentile latency.--short,--table,
--jsonand--markdownare its options;benchbox allruns it and
writesstream.json. (#53) -
Streamsworkflow:benchbox streamon the base branch against the pull
request, withscripts/stream_compare.pywriting the table. (#53) -
benchbox roundtrip: the rowSharedStream. (#53) -
benchmarks/test_bench_stream.py: a CodSpeed benchmark ofsendplus
receive_into. (#53) -
FieldWatch.future(), returning aFieldFuture, a
concurrent.futures.Futureof the next write to the field;FieldFuture
is exported fromsharedbox. (#55) -
field(shape=...): the sizes of an array field of a box.class Grid(SharedBox): cells: npt.NDArray[np.uint8] = np.zeros((4, 6), np.uint8) weights: npt.NDArray[np.float32] = field(shape=(3,))
A box array field also takes its sizes from a plain default array, so it
needs noShapeand, with a dtype in its annotation, noDType. (#56) -
SharedSendStream.writing()andSharedSendStream.awriting(), which yield a
SendSlot:blank = Frame(0, np.empty((0, 0), np.uint16)) with sender.writing(blank) as slot: slot.item.image.fill(0) slot.send(replace(slot.item, index=1))
slot.itemis an item built from a template, with arrays over the next slot
to fill in place, andslot.send(item)publishes an item without copying the
arrays already in the slot. (#58)
Changed
sharedbox.hpp: split intocore.hppandbox.hpp, which it includes.
sharedbox::resultis the library's own type on C++20 and C++23 alike,
and itserror()is asharedbox::errorholding the status, the OS error
and what was found. The C++ names moved fromsharedbox::v2to
sharedbox::v3. (#47)sharedbox::header: the magic, versions, schema hash, creator, waiter
slot count and size moved into its first member,common, a
sharedbox::common_header;layout_majorandlayout_minorbecame
common.kind_majorandcommon.kind_minor. (#47)sharedbox::error: astatus::osfailure carries the OS error in
error().os;errnoandGetLastError()no longer hold it reliably
when the call returns. (#47)- Segment layout: every segment starts with a common first line (magic,
core version, layout version of its kind, schema hash, creator), and
boxes use layout 3.0. A box made by 0.5 cannot be opened by 0.6, nor the
other way round. (#47) sharedbox::inspect: returnsresult<common_header>. (#47)sharedbox::magic: renamedsharedbox::box_magic. (#47)box_ref: a reference field stores the name of its target in 240 bytes,
so the field takes 256 bytes. (#47)SharedBoxnames: one or more segments of[A-Za-z0-9_-]joined by
:, up to 240 characters, such asbl01:camera:det1:frames..is no
longer allowed. The shared memory is/dev/shm/SBX:<name>on Linux and
Local\SBX:<name>on Windows, and Windows waiter events end in#w<i>. (#47)SharedBox.attach: a segment whose magic this version does not know,
such as a box made by 0.5, raisesSchemaMismatchErrorat once, instead
ofSegmentNotFoundErrorafter the timeout. (#47)SharedBoxwrite: makes a wake call only when a thread is waiting on the
box.sleepers, at offset 84 of box layout 3.0, counts the threads inside
a wait. It adds 1 towake_wordonly whensleepersis not 0. (#51)sharedbox::waiter_slot: 32 bytes, withasleep_on, at offset 24, the
offset of the count the slot's current wait added 1 to, and a reserved
word. Freeing the slot of a process killed inside a wait takes that
thread out ofsleepersfor a box, and out ofdata_waitingor
space_waitingfor a stream. (#51)benchbox plot: writes plotly figure JSON (charts/ops.json,
roundtrip.json,stream-throughput.json,stream-matrix.json) instead
of SVG files. (#53)benchmarksextra and group: depend onplotlyinstead ofmatplotlib. (#53)- Array fields: an annotation that names its number of dimensions, as in
numpy.ndarray[tuple[int, int], numpy.dtype[numpy.uint8]], raises
TypeErrorwhen itsShapehas another number, in a box, a record and a
stream item. (#56) - Array fields: a default array whose shape differs from the field's
Shape
raisesTypeErrorinstead ofValueErrorwhen the class is defined. (#56)
Removed
v0.5.0
What's Changed
- docs(changelog): release 0.4.1 by @jacopoabramo in #33
- feat(benchmarks): add benchbox contention by @jacopoabramo in #34
- feat(box): add read_into for array fields by @jacopoabramo in #35
- feat(box): add writing to fill an array field in place by @jacopoabramo in #36
- ci: drop the temporary array measurement workflow by @jacopoabramo in #37
- docs: rewrite the documentation for new readers by @jacopoabramo in #38
- docs(explanation): show a box's memory and each type's bytes by @jacopoabramo in #39
- build(docs): show board text above diagrams with markdown-d2 0.4.2 by @jacopoabramo in #40
- test(benchmarks): time reading a list of 16 floats by @jacopoabramo in #41
- perf(types): convert plain members of collections and records directly by @jacopoabramo in #42
- test(benchmarks): add a CodSpeed benchmark for a record read by @jacopoabramo in #43
- perf: reuse a decoded value while its field is unwritten by @jacopoabramo in #44
- fix(box): raise SchemaMismatchError for a header with another field count by @jacopoabramo in #45
- docs(changelog): release 0.5.0 by @jacopoabramo in #46
Full Changelog: v0.4.1...v0.5.0
v0.4.1
What's Changed
- ci: move artifact actions to Node 24 and add Dependabot by @jacopoabramo in #29
- ci: bump the actions group with 2 updates by @dependabot[bot] in #30
- perf(native): stop copying each encoded value through the stack by @jacopoabramo in #31
- fix(events): stop watcher threads before the interpreter exits by @jacopoabramo in #32
New Contributors
- @dependabot[bot] made their first contribution in #30
Full Changelog: v0.4.0...v0.4.1
v0.4.0
What's Changed
- docs: do minor tidy up by @jacopoabramo in #25
- perf: make update and snapshot native, and add benchmark charts by @jacopoabramo in #26
- fix: keep the field defaults of every box base by @jacopoabramo in #27
- feat: store dates, records, collections and arrays in a box by @jacopoabramo in #28
Full Changelog: v0.3.1...v0.4.0
v0.3.1
What's Changed
- feat: export the package version by @jacopoabramo in #24
Full Changelog: v0.3.0...v0.3.1
v0.3.0
What's Changed
- feat!: replace SharedDict with SharedBox by @jacopoabramo in #12
- feat: convert fields natively and check process liveness by @jacopoabramo in #13
- feat: publish the segment layout and a PyCapsule interface by @jacopoabramo in #14
- perf: speed up update(), snapshot() and the watcher by @jacopoabramo in #15
- feat: add field(), InitVar and post_init by @jacopoabramo in #16
- feat: nested boxes by @jacopoabramo in #17
- test: update docstrings by @jacopoabramo in #18
- fix: follow and close long chains of boxes by @jacopoabramo in #19
- feat(events): add BoxEvents.follow to forward events of referenced boxes by @jacopoabramo in #20
- chore: import at module top by @jacopoabramo in #21
- docs: move the documentation to Zensical by @jacopoabramo in #22
- docs(changelog): mark 0.3.0 release by @jacopoabramo in #23
Full Changelog: 0.2.4...v0.3.0
v0.3.0rc0
What's Changed
- feat!: replace SharedDict with SharedBox by @jacopoabramo in #12
Full Changelog: 0.2.4...v0.3.0rc0