feat(daq)!: make driver channel/timing state private and read-only#56
Merged
Conversation
The driver's channel and timing containers are now private (_ai_channels, _relay_channels, _*_hw_timing_config, ...) and the only sanctioned way to change them is the configure_* / define_* path. State is exposed through read-only @Property accessors on both DAQDriverBase and InstroDAQ that hand back frozen snapshots (MappingProxyType over a shallow copy for the dicts, a tuple for the aggregate channels) captured at call time. The channel/timing dataclasses are frozen, so a snapshot a user holds cannot be mutated and will not change when later configure_* calls run. BREAKING CHANGE: mutating channel/timing state through InstroDAQ or the driver (e.g. daq.ai_channels["x"] = ..., daq.ai_channels.clear(), channel.alias = ...) now raises instead of silently desyncing in-memory state from the hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
hoehner
approved these changes
Jun 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #37.
What
Makes DAQ driver channel/timing state mutable only through the public configuration API, and read-only everywhere else.
DAQDriverBaseholds channel/timing state in private attributes (_ai_channels,_ao_channels,_di_channels,_do_channels,_relay_channels, the four_*_hw_timing_configslots).configure_*/define_*are the only things that mutate them, and they program the device then record the channel.DAQDriverBaseandInstroDAQexpose state through read-only@propertyaccessors. Dict-valued props returnMappingProxyType(dict(...)); the aggregatechannelsreturns atuple. Each access is a snapshot captured at call time — not a live view, so a value a user holds does not change when laterconfigure_*calls run.AnalogChannel,DigitalChannel,DigitalPortChannel,DigitalLineChannel,RelayChannel, andHWTimingConfigarefrozen=True, so attribute writes (channel.alias = ...) are rejected. Frozen values + snapshot dicts mean a shallow copy is a genuinely immutable snapshot.points_in_bufferstays public — it's a derived telemetry counter the driver overwrites each fetch, not channel/timing config.Changes
instro/daq/types.py— froze the 7 channel/timing dataclasses.instro/daq/daq.py— privatized base storage, added read-only accessors,InstroDAQproperties delegate to them.tests/daq/test_daq_drivers.py— replaced theMock()helper with a concrete_RecordingDriver(DAQDriverBase)(real frozen channels on the privates, action methods as Mocks); added contract tests for read-only mappings, frozen channels, and non-live-view snapshots.AGENTS.md+docs/guides/instrumentation/daq.mdx— updated the driver-owned-state contract.Breaking change
Code that mutates channel/timing state through
InstroDAQor the driver (daq.ai_channels["x"] = ...,.clear(),channel.alias = ...) now raises instead of silently desyncing in-memory state from the hardware.Testing
just check— ruff format, mypy (no issues), ruff lint all pass.just test— 432 passed.