Skip to content

Releases

v0.9.0

Choose a tag to compare

@jacopoabramo jacopoabramo released this 04 Nov 19:11
b6bb0a2

Added

  • segment_status: segment_status("lab:counts") returns a SegmentStatus,
    missing when no box or stream is published under the name, running while the
    process that created it runs and creator_exited once it has ended.
    core.hpp adds segment_status and segment_state for C++. (#94)
  • SharedReceiveStream.fetch, fetch_into and fetch_future: reader.fetch(120)
    returns the item at position 120, waiting until the sender has sent it, and
    leaves the reader's receive cursor and missed unchanged. ItemGoneError, a
    LookupError whose oldest is the oldest position the ring held, is raised
    once the ring no longer holds the item. stream.hpp adds
    stream_reader::fetch and fetch_with for C++. (#95)
  • MaxShape on 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.writing takes shape, so
    box.writing("image", shape=(480, 640)) sets the shape and yields a view of
    it under one lock. read_into raises TypeError for a MaxShape field. (#97)
  • BoundedList, BoundedSet and BoundedDict take their element types as the
    first arguments when the class fixes none: BoundedList(int, [1, 2], capacity=4)
    equals BoundedList[int]([1, 2], capacity=4), and
    BoundedDict(str, float, {"a": 1.0}, capacity=8) takes the key and value types. (#97)
  • Timed and Timed.now: a box field, record member, collection element or
    stream item declared as Timed[float] stores the value with a timestamp in
    nanoseconds since the Unix epoch, and box.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. A Timed with 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, RequestFuture and RequestServer: a box
    field declared as exposure: 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_ns or read(), or watches with
    watch and events. 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, and box.move(5.0) or
    box.exposure.set(0.02) returns a RequestFuture waited on with
    .result(timeout) or await. Of the requests sent while the handler
    runs, only the newest is handled next. RequestError,
    RequestSupersededError and ServerExitedError report a failed, replaced
    or abandoned request, and AlreadyServedError a second server. Attribute
    fields are kind 76 of box layout 3.3, and box.hpp adds attr_publish,
    attr_read, attr_published, attr_value_at and the request protocol
    steps on handle for C++, and type_view gives 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
    OSError with the Windows error code in winerror, such as PermissionError
    for a segment the process may not open, instead of RuntimeError. (#96)

v0.8.0

Choose a tag to compare

@jacopoabramo jacopoabramo released this 30 Oct 22:20
51d89a5

Breaking

  • sharedbox.Shape takes its dimensions as one tuple, Shape((480, 640)), in
    place of one argument per dimension. See
    How to migrate from 0.7. (#88)

  • SharedBox reserves the names metadata, metadata_version and
    metadata_version_of: a field or InitVar with one of them raises
    TypeError when 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.update takes the keyword metadata, 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 as str:

    # 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.send publishes the first
    rows of that view without a copy, and receive_into returns a view of the
    stored shape over out. (#88)
  • SharedStream.sender(on_full="release"): a send that has waited its whole
    timeout releases the lossless readers a full ring behind and sends, as in
    stream.sender(on_full="release").send(item, timeout=0.1). Each released
    reader raises ReaderReleased, whose missed counts the items it lost, and
    goes on at the newest item; ReceiveEvents.released reports the count to
    callbacks. SharedSendStream.asend and awriting take a timeout. In
    stream.hpp, send, send_with and begin take an on_full policy, and a
    released reader's receive gives status::released, in stream layout 1.3. (#89)
  • MetadataFullError and MetadataGoneError: 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_nowait and
    SendSlot.send take metadata= and add a version that applies from that
    item. SharedReceiveStream.metadata and metadata_version give the version
    of the item the reader received last. SharedStream.create takes
    metadata_capacity and metadata_versions, and create and attach take
    metadata_type. sharedbox 0.7.0 and earlier cannot open a stream created
    with metadata or a metadata_capacity. (#91)
  • box.hpp, stream.hpp and sharedbox_c.h: box layout 3.2 and stream
    layout 1.4 hold metadata after the record or the description table.
    handle and stream gain metadata_head and read_metadata, and
    stream_reader gains metadata_version_at. handle and stream_sender
    gain add_metadata. handle::write(values, doc, lock_timeout) and
    write_large with the same arguments write fields and add a version in one
    write, and
    stream_sender::can_add_metadata checks an add without making it.
    sharedbox_c.h adds sbx_metadata_head, sbx_read_metadata,
    sbx_add_metadata and SBX_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, and SharedBox.update(metadata=...) adds the next
    one from any process, with or without field values, in one write.
    metadata, metadata_version and metadata_version_of read the versions,
    a map as a read-only mapping and a list as a tuple. BoxEvents.metadata is
    emitted with each new version. The class keywords metadata_capacity and
    metadata_versions size the room for versions, and
    class Scale(SharedBox[Calibration]) gives the metadata's type to type
    checkers. (#91)

Changed

  • sharedbox.DType is a StrEnum with one member per element type:
    DType(numpy.uint8) returns DType.uint8, which equals "uint8". (#88)

Fixed

  • SharedBox and SharedStream at exit: the atexit hook of sharedbox
    closes every box and stream the process still has open and releases the
    native objects of SharedBox subclasses, 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
    BoxClosedError or StreamClosedError, and creating or attaching a box
    raises BoxClosedError. (#90)

v0.7.0

Choose a tag to compare

@jacopoabramo jacopoabramo released this 28 Oct 04:33
6ef133c

Added

  • np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32,
    np.uint64, np.float16, np.float32, np.complex64 and np.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 raises OverflowError. A np.datetime64 field stores
    nanoseconds since 1970. (#66)
  • sbx_read_float16, sbx_write_float16, sbx_read_float32 and
    sbx_write_float32 in sharedbox_c.h. (#66)
  • An array annotated X | None as a field, record member or stream item,
    holding None or an array of its sizes, as in
    scan: npt.NDArray[np.uint8] | None = field(default=None, shape=(32, 32)).
    receive_into does not fill one. (#67)
  • field(capacity=...): the capacity of a box field of text, bytes,
    bytearray, Decimal or 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 in samples: Annotated[BoundedList[float], Capacity(65536)]. It takes
    every element type list[T] takes and is stored as list[T] is. A read
    copies the bytes without decoding an element, a write of a BoundedList of
    the field's element type copies them back, indexing decodes or encodes one
    element, and a change past the capacity raises ValueError. 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 in seen: Annotated[BoundedSet[int], Capacity(65536)]. It is stored as
    set[T] is. A read copies the bytes without decoding an element, a write
    of a BoundedSet of the field's element type copies them back, and in,
    add and remove encode 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
    BoundedSet of the same class and capacity, the comparisons take any set,
    and a change past the capacity raises ValueError. A declaration raises
    TypeError for an element type whose equal values could be stored
    differently, such as Decimal or int | 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 in weights: Annotated[BoundedDict[int, float], Capacity(4096)].
    It is stored as dict[K, V] is. A read copies the bytes without decoding
    an item, a write of a BoundedDict of the field's key and value types
    copies them back, and d[key], get and in encode 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
    BoundedDict of the same class and capacity, and a change past the
    capacity raises ValueError. Keys follow the rules of BoundedSet
    elements, and values can be of any type a dict field holds. Subclasses,
    generic or with fixed key and value types, can be declared in its place. (#82)
  • BoundedList of int, float, bool, complex or NumPy integer, float,
    complex or bool_ elements supports the buffer protocol: np.asarray(value)
    and memoryview(value) share its elements without decoding them, read only
    for bool and bool_, and a change of length raises BufferError while
    such a view exists. (#85)

Changed

  • Reading a list, tuple, set, frozenset or dict field 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, so np.int64(5) written to float | int reads back as 5. (#66)
  • int, float, complex and bool fields take numpy's integer, floating,
    complex and bool scalars, and read back the Python type. np.int64,
    np.float64, np.complex128 and np.bool_ fields read back as those types
    and hash as int, float, complex and bool. (#66)
  • The extension is compiled for speed instead of size. Writing a list, set
    or dict field, and reading, indexing or looking up a BoundedList or
    BoundedSet, take less time. The wheels are about 75 KB larger on Linux and
    17 KB larger on Windows. (#79)

Fixed

  • A list, set or dict whose slots end short of its alignment, such as
    Annotated[list[Color], Capacity(3)] for an Enum Color, is accepted;
    its size is rounded up to the alignment. sharedbox 0.6 refuses a segment
    that holds one. (#66)
  • A TypeError about the sizes of an array member of a dataclass or attrs
    record names its metadata shape, as in metadata shape (3), instead of
    field(shape=...). (#70)

v0.6.0

Choose a tag to compare

@jacopoabramo jacopoabramo released this 24 Oct 11:12
3ce160f

Added

  • status::foreign and SBX_E_FOREIGN (-12): the segment's magic is not
    one this version knows. Python raises SchemaMismatchError. (#47)

  • sharedbox::to_expected and sharedbox::from_expected: convert a
    sharedbox::result to a std::expected and back on C++23. (#47)

  • KindMismatchError, a SchemaMismatchError raised when a name holds a
    segment of another kind; status::kind_mismatch and SBX_E_KIND (-11)
    in sharedbox.hpp and sharedbox_c.h. (#47)

  • sharedbox::stream, sharedbox::stream_sender and sharedbox::stream_reader
    in stream.hpp, included by sharedbox.hpp: a ring in shared memory with
    one sender and up to max_readers readers, each reading
    read_mode::lossless, read_mode::lossy or read_mode::latest, laid out
    as stream layout 1.0. C++ only. stream_sender::begin, publish and
    abandon reserve a slot, fill it in place and publish it or give it up. (#48)

  • status::busy (-13), status::ended (-14) and status::interrupted
    (-15), with SBX_E_BUSY, SBX_E_ENDED and SBX_E_INTERRUPTED in
    sharedbox_c.h. (#48)

  • SharedStream, SharedSendStream and SharedReceiveStream: send items of
    any type a SharedBox field takes from one process to up to max_readers
    readers, each reading lossless, lossy or latest, with blocking,
    non-blocking and asyncio calls, receive_into into the caller's arrays and
    receive_future, a concurrent.futures.Future. A stream passed to another
    process arrives attached, or raises SchemaMismatchError if its name now
    holds a stream created since, and SharedStream.create without a name gives
    the stream a new one. The ends cannot be pickled; SharedSendStream.stream
    and SharedReceiveStream.stream return their stream. (#49)

  • SharedReceiveStream.events, a ReceiveEvents psygnal group whose
    received and ended signals deliver a reader's items to callbacks. (#49)

  • WouldBlock, EndOfStream, StreamBusyError and StreamClosedError. (#49)

  • SharedStreamStatistics and ReceiveStreamStatistics, returned by
    SharedStream.statistics(). (#49)

  • WaiterSlotsFullError is exported from sharedbox; opening a reader
    raises it when max_readers readers are open. (#49)

  • benchbox ops: the row watched 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 of SharedStream readers in each mode
    against mp.Queue and a SharedMemory ring with a lock, and a table of
    send and asend with receive, async for and events.received,
    with the 50th, 90th and 99th percentile latency. --short, --table,
    --json and --markdown are its options; benchbox all runs it and
    writes stream.json. (#53)

  • Streams workflow: benchbox stream on the base branch against the pull
    request, with scripts/stream_compare.py writing the table. (#53)

  • benchbox roundtrip: the row SharedStream. (#53)

  • benchmarks/test_bench_stream.py: a CodSpeed benchmark of send plus
    receive_into. (#53)

  • FieldWatch.future(), returning a FieldFuture, a
    concurrent.futures.Future of the next write to the field; FieldFuture
    is exported from sharedbox. (#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 no Shape and, with a dtype in its annotation, no DType. (#56)

  • SharedSendStream.writing() and SharedSendStream.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.item is an item built from a template, with arrays over the next slot
    to fill in place, and slot.send(item) publishes an item without copying the
    arrays already in the slot. (#58)

Changed

  • sharedbox.hpp: split into core.hpp and box.hpp, which it includes.
    sharedbox::result is the library's own type on C++20 and C++23 alike,
    and its error() is a sharedbox::error holding the status, the OS error
    and what was found. The C++ names moved from sharedbox::v2 to
    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_major and layout_minor became
    common.kind_major and common.kind_minor. (#47)
  • sharedbox::error: a status::os failure carries the OS error in
    error().os; errno and GetLastError() 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: returns result<common_header>. (#47)
  • sharedbox::magic: renamed sharedbox::box_magic. (#47)
  • box_ref: a reference field stores the name of its target in 240 bytes,
    so the field takes 256 bytes. (#47)
  • SharedBox names: one or more segments of [A-Za-z0-9_-] joined by
    :, up to 240 characters, such as bl01: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, raises SchemaMismatchError at once, instead
    of SegmentNotFoundError after the timeout. (#47)
  • SharedBox write: 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 to wake_word only when sleepers is not 0. (#51)
  • sharedbox::waiter_slot: 32 bytes, with asleep_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 of sleepers for a box, and out of data_waiting or
    space_waiting for 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)
  • benchmarks extra and group: depend on plotly instead of matplotlib. (#53)
  • Array fields: an annotation that names its number of dimensions, as in
    numpy.ndarray[tuple[int, int], numpy.dtype[numpy.uint8]], raises
    TypeError when its Shape has 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
    raises TypeError instead of ValueError when the class is defined. (#56)

Removed

  • Reading box layouts 1.0 and 2.0. (#47)
  • sharedbox::oldest_layout_major. (#47)

v0.5.0

Choose a tag to compare

@jacopoabramo jacopoabramo released this 08 Oct 23:24
ff63dee

What's Changed

Full Changelog: v0.4.1...v0.5.0

v0.4.1

Choose a tag to compare

@jacopoabramo jacopoabramo released this 07 Oct 09:49
cd153a5

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

Full Changelog: v0.4.0...v0.4.1

v0.4.0

Choose a tag to compare

@jacopoabramo jacopoabramo released this 05 Oct 13:20
6937a5b

What's Changed

Full Changelog: v0.3.1...v0.4.0

v0.3.1

Choose a tag to compare

@jacopoabramo jacopoabramo released this 30 Sep 23:05
d755615

What's Changed

Full Changelog: v0.3.0...v0.3.1

v0.3.0

Choose a tag to compare

@jacopoabramo jacopoabramo released this 30 Sep 22:44
998185c

What's Changed

Full Changelog: 0.2.4...v0.3.0

v0.3.0rc0

v0.3.0rc0 Pre-release
Pre-release

Choose a tag to compare

@jacopoabramo jacopoabramo released this 25 Sep 10:23
69f4a1a

What's Changed

Full Changelog: 0.2.4...v0.3.0rc0