[0.3.0] - 2026-05-15
Unified Device-Library API (v1)
Aligns alicatlib with the sibling-library spec (UNIFIED_API_HANDOFF.md) so consumers (capa, future SDKs) see one shape across alicatlib / watlowlib / sartoriuslib / nidaqlib. Breaking changes, no compat shims.
Added
Reading.from_parsed(...)— new canonical name for the timing-wrapped device read (formerlyDataFrame.from_parsed).alicatlib.Sample.t_mono_ns/Sample.t_utc/Sample.t_midpoint_mono_nsper the cross-lib timestamp contract (§C).alicatlib.Recording[T]— context-manager payload carryingstream, mutablesummary,rate_hz. Iterable (delegates tostream) soasync for batch in recordingworks.alicatlib.PollSourceAdapter(name, device)— single-device adapter that satisfies the recorder'sPollSourceProtocol. Honours thenamesfilter per spec §E.DeviceResult.success(value)/DeviceResult.failure(error)— additive classmethod factories (keyword construction still works).alicatlib.DeviceSnapshot/alicatlib.AlicatDeviceSnapshot— cross-lib status snapshots built from cached identity + session counters; no I/O.Device.snapshot()/SyncDevice.snapshot()— return aAlicatDeviceSnapshotwithout touching the wire.Session.recoverable_error_count— public int incremented every time the session swallows-and-retries a transient (currently: streaming-mode idle-window timeouts).Session.last_error— most recent enrichedErrorContextfrom a failedexecute(), surfaced onsnapshot().alicatlib.units.to_pint(unit)— return a pint-compatible unit string (orNone). Lossy by design: PSIA / PSIG / PSID all collapse to"psi".alicatlib.ProtocolKind— single-member enum (ASCII) for cross-lib protocol-kind tagging inDiscoveryResult/ErrorContext.ErrorContext.address— cross-lib@propertyreturningself.unit_idso consumers can read the bus address uniformly.ErrorContext.protocol: ProtocolKind | None— new base field, alwaysNonefor direct alicat use today.alicatlib.sample_to_row— top-level re-export ofalicatlib.sinks.sample_to_row(documented path).alicatlib.sync.SyncRecording[T]— sync analog ofRecording, iterable.
Changed (breaking)
alicatlib.DataFrame→alicatlib.Reading. File renamed fromdevices/data_frame.pytodevices/reading.py. Theformat=constructor kwarg is nowreading_format=. Themonotonic_nsfield onReadingis nowt_mono_ns.Sample.frame→Sample.reading. Same rename on every wrapper dataclass that previously carried aframe: DataFramefield (TareResult,DisplayLockResult,TotalizerResetResult,ValveHoldResult,SetpointState).Sample.monotonic_ns→Sample.t_mono_ns;Sample.midpoint_at→Sample.t_utc.requested_at,received_at, andlatency_sremain as I/O-boundary provenance.Device.stream(rate_ms=...)→Device.stream(rate_hz=...). Convert at the protocol boundary (rate_ms = round(1000 / rate_hz)). Pass a very highrate_hzto request the device's "as-fast-as-possible" setting.DiscoveryResultshape conforms to spec §B:unit_id→address,info→device_info, addsprotocol: ProtocolKind | None, addselapsed_s: float.record()yields aRecording(stream, summary, rate_hz)instead of a bare async iterator.AcquisitionSummaryis now mutable. The recorder is the sole writer; counters update in place during the run.finished_atisNonewhile in flight and set on context-manager exit.sample_to_row()'s output schema: dropsmidpoint_at, addst_utc(ISO 8601) andt_mono_ns(int).Unit_ID/received_atdedup behaviour is unchanged (hardware-validation finding 2026-04-17).
Deferred (intentionally out of scope)
AlicatTransientTransportError— alicat has not observed a cold-open or transient-retry race today, so this is deferred until evidence surfaces (spec §F).Device.expected_rate_hzproperty — dropped in favour ofRecording.rate_hz(spec §I), since the recorder owns the rate.