Skip to content

attributes: getter/setter IO rework, remove AttributeIORef/AttributeIO#412

Open
coretl wants to merge 2 commits into
refactorfrom
refactor-issue-392
Open

attributes: getter/setter IO rework, remove AttributeIORef/AttributeIO#412
coretl wants to merge 2 commits into
refactorfrom
refactor-issue-392

Conversation

@coretl

@coretl coretl commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closes #392

Implements the getter/setter half of #392 (ADR 0014's AttributeIO/AttributeIORef removal), scoped per the issue's own "Note on size": the DataType/*Meta TypedDict replacement and the precision/Limits naming pass (ADR 0017) are left for a follow-up PR rather than risking an incoherent halfway state in one session — see "Notes" below.

Scope

  • Per-attribute IO is now getter/setter callables passed straight to AttrR(getter=...)/AttrW(setter=...)/AttrRW(getter=..., setter=...). Access mode is enforced structurally by which of getter/setter are present - there's no more io=/AttributeIO class hierarchy or AttributeIORef-keyed dispatch registry.
  • Datatype is now optional on the constructors when it can be inferred from the getter's return annotation or the setter's value-parameter annotation (AttrR(getter=get_value) where get_value() -> float); not inferable ⇒ positional datatype still required, fails fast at construction.
  • New Update[T] dataclass (value, timestamp) a getter/setter may return/accept instead of a bare value - update()/poll() unwrap it. (Native persistence of the timestamp, and a severity field, are left to AttrW setpoint cache, native timestamps + severity, ControllerRunner #395, which explicitly owns that.)
  • Runtime surface rename (ADR 0014): get().readback/.setpoint read-only properties (AttrR has readback, AttrW has setpoint, AttrRW has both); no-arg update()poll() (does the getter read, caches, returns the value); update(value) stays as a pure cache-push (now also accepting Update[T]); put(value[, sync_setpoint])set(value) (caches .setpoint, runs the setter; a non-None setter return updates .readback too - the sanctioned replacement for the old private _call_sync_setpoint_callbacks/sync_setpoint= mechanism).
  • Removed: AttributeIO, AttributeIORef, ios=, _connect_attribute_ios, _validate_io, _attribute_ref_io_map, set_update_callback/bind_update_callback/set_on_put_callback/_call_sync_setpoint_callbacks/add_sync_setpoint_callback, and the second Attribute/AttrR/AttrW/AttrRW TypeVar (Attribute[DType_T, AttributeIORefT]Attribute[DType_T]).
  • Controller.create_api_and_tasks now schedules polling directly off getter-bearing attributes (poll_period, defaulting to ONCE when a getter is given) instead of pattern-matching an AttributeIORef.
  • Migrated: the demo composition example (src/fastcs/demo/controllers.py), every docs/snippets/*.py tutorial snippet that used io_ref=, and all the tests that constructed attributes the old way (tests/assertable_controller.py, tests/test_attributes.py, tests/test_control_system.py, tests/conftest.py, tests/example_p4p_ioc.py, and the transport test files that built attributes directly).
  • .datatype/DataType themselves are untouched in this PR (see Notes).

Notes

  • Deliberately out of scope, left for a follow-up: the DataType family → python type + *Meta TypedDict replacement and the precision/nested-Limits naming pass (ADR 0017). The issue's own "Note on size" calls out this split explicitly. attr.datatype/Float/Int/etc. are unchanged.
  • Verified locally with uv run --locked tox -e pre-commit,type-checking, both green in full. For the tests env, this sandbox can't run docs (needs outbound network to diamondlightsource.github.io) or the PVA/p4p-backed tests (RuntimeError: Address family not supported by protocol - no PVA-capable socket family here), the same known limitation noted on prior PRs (demo: use ControllerVector for temperature ramp sub-controllers #409/demo: cut-down Eiger REST sim + introspectable controller example #410/demo: getter/setter-in-init temperature attr example #411) in this epic. Excluding those two paths, pytest src tests --ignore=tests/benchmarking passes 314/324, with only the same 10 pre-existing PVA (p4p)/socket-family failures, unrelated to this change. Real CI covers docs and PVA.
  • Also spot-checked the demo end-to-end against the real tickit temperature-controller simulator (docs/snippets/*.py's test_docs_snippets.py already exercises this - all 16 snippets pass) and confirmed TemperatureController/TemperatureRampController construct and wire up correctly with the new getter/setter API.

Generated by Claude Code

Per-attribute IO moves from a shared, ref-dispatched AttributeIO/
AttributeIORef pair onto plain getter/setter callables passed straight to
AttrR/AttrW/AttrRW. Datatype is now optional on the constructors when it
can be inferred from the getter/setter annotation.

Runtime surface rename: get() -> .readback / .setpoint properties, no-arg
update() -> poll() (does the getter read + caches + returns), update(value)
stays as a pure cache-push (now also accepting Update[T]), put() -> set()
(caches .setpoint, runs the setter, a non-None return updates .readback -
the replacement for the old sync_setpoint-callback mechanism). Scheduling
in Controller.create_api_and_tasks now polls getter-bearing attrs directly
instead of going through an IO update-callback indirection.

Removed: AttributeIO, AttributeIORef, ios=, _connect_attribute_ios,
_validate_io, the second Attribute/AttrR/AttrW/AttrRW TypeVar. Migrates
the demo composition example and all docs snippets that used the old
io_ref= wiring.

Deliberately out of scope for this PR (left for a follow-up): the
DataType family / *Meta TypedDict replacement and the associated
precision/Limits naming pass - the issue's own sizing note allows
splitting the getter/setter half from the DataType-removal half.

Closes #392
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dd42bbd-3f8c-4206-bd7b-3119304a22ff

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-issue-392

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.15205% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.06%. Comparing base (ef0c3c6) to head (d38e78a).

Files with missing lines Patch % Lines
src/fastcs/attributes/_infer_datatype.py 76.47% 8 Missing ⚠️
src/fastcs/attributes/attr_rw.py 93.10% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           refactor     #412      +/-   ##
============================================
- Coverage     91.25%   91.06%   -0.19%     
============================================
  Files            72       72              
  Lines          2892     2900       +8     
============================================
+ Hits           2639     2641       +2     
- Misses          253      259       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The docs build failed CI (fail-on-warning) because docs/tutorials/static-drivers.md's
literalinclude emphasize-lines directives pointed at line numbers that no longer existed
after the snippet rewrite. Fixing that surfaced the deeper issue: several tutorial and
how-to pages narrated the removed AttributeIO/AttributeIORef pattern in prose, with code
examples that no longer import.

- Rewrite docs/tutorials/static-drivers.md and dynamic-drivers.md prose + literalinclude
  line references to match the getter/setter snippets.
- Give docs/snippets/static15.py's TemperatureProtocol a Tracer base and thread `topic`
  through send_query, so the tutorial's per-attribute tracing walkthrough (enable_tracing
  on one attribute, see only its queries) still holds - a plain logger.trace call
  wouldn't respect per-attribute enable_tracing() at all.
- Rewrite docs/how-to/update-attributes-from-device.md's four patterns (poll via getter,
  event-driven updates from a set, batched scan updates, scan-as-cache) for getter/setter.
- Fix remaining AttributeIO/.get()/.put()/update_period mentions in
  docs/explanations/{transports,controllers,what-is-fastcs,datatypes}.md and
  docs/how-to/{table-waveform-data,wait-methods}.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants