Skip to content

getter/setter IO rework; remove AttributeIORef and DataType #392

Description

@coretl

Foundational — unblocks most of the refactor.

Scope

  • Per-attribute IO as getter/setter callables on the constructors: AttrR(getter=…), AttrW(setter=…), AttrRW(getter=…, setter=…). Access mode is enforced by which parameters exist — there is no io= object and no ReadIO/WriteIO/ReadWriteIO hierarchy (superseded, ADR 0014 review update 2026-07-22).
    • getter() -> T | Update[T]; setter(value) -> None | T | Update[T] (a returned value is the device's accepted/clamped readback → updates readback + AttrW setpoint cache; the sanctioned replacement for fastcs-secop's private _call_sync_setpoint_callbacks).
    • Update[T] = value: T, timestamp: float | None (None ⇒ framework stamps receive-time), severity: Severity = OK.
    • Datatype optional when a getter/setter is given — inferred from the getter's return annotation (or setter param), unwrapping Update[T]T; the per-datatype Unpack[*Meta] static check keys off the inferred type. Not inferable (-> Any, unannotated) ⇒ positional datatype required (fail-fast at construction).
    • update_period is a read-side kwarg: ONCE (default when a getter is supplied) / float (poll) / None (on-demand only). No getter ⇒ soft, value pushed via @scan/attr.update().
    • Soft = absence of getter/setter (AttrRW(float) self-wires setpoint→readback). No io=None sentinel.
  • Runtime surface rename (part of the repoint sweep, ADR 0014): get().readback / .setpoint (read-only properties; AttrR has readback, AttrW setpoint, AttrRW both); no-arg update()poll() (returns the value); update_periodpoll_period (schedule only); update(value) stays as the pure cache-push (accepts T | Update[T], no IO); put(value[, sync_setpoint])set(value) (caches .setpoint then runs the setter; setter return feeds .readback; sync_setpoint gone). Update every transport + the embedded connector call site (~5 each).
  • Remove AttributeIORef, ref-type dispatch, ios=, _validate_io, _connect_attribute_ios, _attribute_ref_io_map, the ReadIO/WriteIO/ReadWriteIO classes, set_update_callback/bind_update_callback/_call_sync_setpoint_callbacks (the getter/setter live on the attr now), and the second TypeVar (Attribute[DType_T, RefT]Attribute[DType_T]).
  • Remove the DataType family. Replace with python types + *Meta typed dicts: AttrR(float, precision=3, units="deg", getter=...) and A[AttrR[float], FloatMeta(...)]. Absorbs the naming pass (closes DRAFT: Naming pass (precision, Limits alignment, Array1D/Table hints) #396): precision; nested Limits with inheritance (Display→Control, Alarm→Warning, assert Warning⊆Alarm; unset ⇒ unbounded); Array1D/Table as the hint AND the runtime structure.
  • Do NOT add the @attr decorator here — that is @attr decorator sugar over getter/setter constructors #397 (this issue provides the getter=/setter= constructor params @attr is sugar over).
  • Migrate the composition example (Example — composition & methods (temperature controller) #390, temperature controller) to getter/setter in this PR.

Key files (§9)

src/fastcs/attributes/, controllers/base_controller.py, controllers/controller.py (update-period scheduling), datatypes/.

Acceptance

uv run --locked tox -e pre-commit,type-checking,tests green; #390 updated + green.

Note on size

Large. If it can't land green in one session, prefer implementing the getter/setter half first as a coherent green PR and leave a comment scoping the DataType-removal remainder — do not leave a broken tree or a half-migrated Closes.

ADR: 0014, 0017, 0012
Parent: #388
Blocked by: none

Metadata model (*Meta) — folded in from review

  • Per-datatype TypedDicts: FloatMeta, IntMeta, StrMeta, BoolMeta, EnumMeta, Array1DMeta, TableMeta (fields = precision:int, nested Limits + inheritance, units, …).
  • Attr* constructors overloaded per datatype so **kwargs: Unpack[FloatMeta] is statically checked: AttrRW(float, precision=3, getter=...) OK; AttrRW(str, precision=3) is a type error. The overload keys off the datatype — passed positionally, or inferred from the getter's return type when omitted.
  • A superset Meta TypedDict (all fields optional) for generic extras (see Example 4 — SCPI device: annotated attributes + per-attribute filler data #405).
  • The Attribute stores the resolved metadata (attr.meta) — no DataType object. Repoint every transport/connector that read attr.datatype.precision/.units/.limits/.choices to attr.meta (enum choices come from the python type). This repointing is the widest part of this issue.
  • Constructor runtime-validates meta vs datatype (belt-and-braces; the filler does the same for the Annotated path, ControllerFiller — declarative/procedural split #394).
  • *Meta module location is provisional pending Define the top-level public API namespace (flat vs nested) #406.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions