You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 noio= object and noReadIO/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).
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_period → poll_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).
RemoveAttributeIORef, 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.
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.
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.
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.
Foundational — unblocks most of the refactor.
Scope
getter/settercallables on the constructors:AttrR(getter=…),AttrW(setter=…),AttrRW(getter=…, setter=…). Access mode is enforced by which parameters exist — there is noio=object and noReadIO/WriteIO/ReadWriteIOhierarchy (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 +AttrWsetpoint cache; the sanctioned replacement forfastcs-secop's private_call_sync_setpoint_callbacks).Update[T]=value: T,timestamp: float | None(None⇒ framework stamps receive-time),severity: Severity = OK.Update[T]→T; the per-datatypeUnpack[*Meta]static check keys off the inferred type. Not inferable (-> Any, unannotated) ⇒ positional datatype required (fail-fast at construction).update_periodis 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().AttrRW(float)self-wires setpoint→readback). Noio=Nonesentinel.get()→.readback/.setpoint(read-only properties; AttrR has readback, AttrW setpoint, AttrRW both); no-argupdate()→poll()(returns the value);update_period→poll_period(schedule only);update(value)stays as the pure cache-push (acceptsT | Update[T], no IO);put(value[, sync_setpoint])→set(value)(caches.setpointthen runs the setter; setter return feeds.readback;sync_setpointgone). Update every transport + the embedded connector call site (~5 each).AttributeIORef, ref-type dispatch,ios=,_validate_io,_connect_attribute_ios,_attribute_ref_io_map, theReadIO/WriteIO/ReadWriteIOclasses,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]).DataTypefamily. Replace with python types +*Metatyped dicts:AttrR(float, precision=3, units="deg", getter=...)andA[AttrR[float], FloatMeta(...)]. Absorbs the naming pass (closes DRAFT: Naming pass (precision, Limits alignment, Array1D/Table hints) #396):precision; nestedLimitswith inheritance (Display→Control, Alarm→Warning, assert Warning⊆Alarm; unset ⇒ unbounded);Array1D/Tableas the hint AND the runtime structure.@attrdecorator here — that is @attr decorator sugar over getter/setter constructors #397 (this issue provides thegetter=/setter=constructor params@attris sugar over).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,testsgreen; #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 reviewTypedDicts: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.MetaTypedDict (all fields optional) for generic extras (see Example 4 — SCPI device: annotated attributes + per-attribute filler data #405).Attributestores the resolved metadata (attr.meta) — noDataTypeobject. Repoint every transport/connector that readattr.datatype.precision/.units/.limits/.choicestoattr.meta(enumchoicescome from the python type). This repointing is the widest part of this issue.Annotatedpath, ControllerFiller — declarative/procedural split #394).*Metamodule location is provisional pending Define the top-level public API namespace (flat vs nested) #406.