Skip to content

attributes: replace the DataType family with python types and *Meta typed dicts - #418

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

attributes: replace the DataType family with python types and *Meta typed dicts#418
coretl wants to merge 2 commits into
refactorfrom
refactor-issue-413

Conversation

@coretl

@coretl coretl commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Closes #413

An attribute's datatype is now the python type it holds, and everything that used to hang off a DataType instance — precision, units, limits, array shape — travels separately as metadata on attr.meta. This completes the DataType drop split out of #392, and absorbs the naming pass (#396 / ADR 0017) as that issue directed.

self.temperature = AttrRW(float, precision=3, units="degC", setter=apply_temp)

Scope

  • DataType is gone. datatype.py, _numeric.py, bool.py, float.py, int.py, string.py, enum.py, waveform.py and table.py are deleted. fastcs.datatypes now holds:
  • attr.meta stores the resolved metadata; attr.dtype is the python type. attr.datatype is gone, and description/group are meta fields with properties reading them.
  • Statically checked per datatype: AttrR/AttrW/AttrRW.__init__ are overloaded once per datatype with **meta: Unpack[*Meta], so AttrRW(str, precision=3) does not type check. validate_meta is the runtime counterpart — the error names the field, datatype and attribute ('precision' is not valid metadata for str attribute device_id), which is the message ControllerFiller — declarative/procedural split #394/Example 4 — SCPI device: annotated attributes + per-attribute filler data #405 need from the filler.
  • Naming pass (ADR 0017): precprecision; the flat min/max/min_alarm/max_alarm become NumericLimits(control=…, display=…, alarm=…, warning=…), all optional, with the ADR's inheritance rules (control inherits display, warning inherits alarm, warning ⊆ alarm asserted). Only the control range rejects a value; the rest are served to clients.
  • WaveformArray1D: AttrR(Array1D[np.int32], shape=(10,)) — the element type rides on the subscript. Arrays of rank > 1 keep working, written as AttrR(np.ndarray, array_dtype=np.uint16, shape=(1024, 1024)), since Array1D is by name one dimensional and there is no ophyd-async spelling for higher ranks.
  • Transport repoint (the widest part): EPICS CA (util.py, ioc.py), PVA (types.py, _pv_handlers.py, gui.py), the shared EPICS GUI, Tango, REST and GraphQL all dispatch on attr.dtype and read what they serve from attr.meta. add_update_datatype_callbackadd_update_meta_callback. The cast helpers now take the Attribute rather than a datatype object, since validation lives on the attribute.
  • Demo controllers, all 15 docs/snippets/*.py, and the prose docs (explanations/datatypes.md rewritten, how-to/table-waveform-data.md, explanations/transports.md, and the rest) migrated.

Instructions to reviewer on how to test:

  1. uv run --locked tox -e pre-commit,type-checking — both green.
  2. uv run pytest tests/test_datatypes.py tests/test_attributes.py -v
  3. python -m fastcs.demo run src/fastcs/demo/fastcs.yaml against the sim (tickit all src/fastcs/demo/simulation/temp_controller.yaml)

Checks for reviewer

  • Would the PR title make sense to a user on a set of release notes
  • Limit mapping. The old min_alarm/max_alarm drove EPICS LOPR/HOPR, which are EPICS' display range, not its alarm limits. With the categories now named, this PR maps displayLOPR/HOPR and controlDRVL/DRVH (and PVA/Tango likewise), so a driver that previously set min_alarm to get a display range now sets display. Serving alarm/warning as CA LOLO/HIHI/LOW/HIGH is left out — it needs the severity fields set too, and is a behaviour addition rather than this rename.
  • Array vs table at runtime. Both are held as np.ndarray; what separates them is that a table's metadata names its columns, so transports check for structured_dtype in attr.meta. Declaring Table without columns, or structured_dtype without Table, fails fast at construction.

Notes

  • Overload resolution has one honest gap. Overloads are tried in order and bool matches int while int matches float, so AttrR(bool, units=...) resolves to the int overload rather than failing statically; the constructor's runtime check rejects it. A call whose metadata is valid always picks its own datatype's overload (AttrR(bool) is AttrR[bool], AttrR(int) is AttrR[int]). This is commented at the overload block rather than left for a reader to discover.
  • DataType.all_equal was unused outside its own test and is not carried over.
  • tests/transports/epics/ca/test_initial_value.py had attributes literally named int/float/bool/str; harmless when the datatype was Int(), but they shadow the builtins in the class body now, so they are renamed *_rw (PV names follow).
  • 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), the same known limitation noted on demo: use ControllerVector for temperature ramp sub-controllers #409/demo: cut-down Eiger REST sim + introspectable controller example #410/demo: convert temperature controller to getter/setter style #411/attributes: getter/setter IO rework, remove AttributeIORef/AttributeIO #412. Excluding those two paths, pytest src tests --ignore=tests/benchmarking passes 361/371, with only the same 10 pre-existing p4p/socket-family failures — I confirmed those 10 are identical on refactor itself by running that file in a worktree off the base commit. Real CI covers docs and PVA.

Generated by Claude Code

…yped dicts

An attribute's datatype is now the python type it holds - `float`, an Enum
subclass, `Array1D[np.int32]`, `Table` - and everything that used to hang off a
`DataType` instance travels separately as metadata on `attr.meta`.

- New `fastcs.datatypes`: `Array1D`/`Table` datatype spellings, the per-datatype
  `*Meta` typed dicts (plus the superset `Meta`), nested `NumericLimits`, and the
  validation the `DataType` classes used to do.
- `Attr*` constructors are overloaded per datatype, so `AttrRW(str, precision=3)`
  is a static type error; `validate_meta` is the runtime counterpart for metadata
  that arrives without a static check.
- Naming pass (ADR 0017): `prec` -> `precision`, and the flat
  `min`/`max`/`min_alarm`/`max_alarm` become nested control/display/alarm/warning
  limits with inheritance.
- Every transport repointed from `attr.datatype.*` to `attr.dtype` + `attr.meta`;
  `add_update_datatype_callback` becomes `add_update_meta_callback`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zpFjvbhUisfVUq3NH8M1G
@coderabbitai

coderabbitai Bot commented Aug 14, 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: f6fcc706-ca14-4e93-ad46-78daa5488b71

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

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.

The p4p enum test still read `attr.datatype.members`; it cannot run in the
sandbox (no PVA socket family), so CI was the first to see it. Three new
docstrings also referenced `ControllerFiller`, which does not exist until #394,
and an ambiguous `fastcs.datatypes.meta` - sphinx builds with warnings as
errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zpFjvbhUisfVUq3NH8M1G
@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.26016% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.40%. Comparing base (e73453b) to head (c1fce75).
⚠️ Report is 2 commits behind head on refactor.

Files with missing lines Patch % Lines
src/fastcs/datatypes/validation.py 95.45% 4 Missing ⚠️
src/fastcs/transports/epics/pva/types.py 94.11% 4 Missing ⚠️
src/fastcs/datatypes/limits.py 91.66% 3 Missing ⚠️
src/fastcs/datatypes/types.py 92.85% 3 Missing ⚠️
src/fastcs/transports/tango/util.py 91.89% 3 Missing ⚠️
src/fastcs/attributes/_infer_datatype.py 81.81% 2 Missing ⚠️
src/fastcs/transports/epics/gui.py 94.28% 2 Missing ⚠️
src/fastcs/transports/epics/ca/util.py 99.01% 1 Missing ⚠️
src/fastcs/transports/epics/pva/gui.py 95.83% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           refactor     #418      +/-   ##
============================================
+ Coverage     91.25%   91.40%   +0.15%     
============================================
  Files            72       67       -5     
  Lines          2892     3003     +111     
============================================
+ Hits           2639     2745     +106     
- Misses          253      258       +5     

☔ 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.

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