Skip to content

Comments

Pass attribute instead of datatype into _get_read_widget#321

Merged
GDYendell merged 5 commits intomainfrom
pass_attribute_into_get_read_widget
Feb 20, 2026
Merged

Pass attribute instead of datatype into _get_read_widget#321
GDYendell merged 5 commits intomainfrom
pass_attribute_into_get_read_widget

Conversation

@jacob720
Copy link
Contributor

@jacob720 jacob720 commented Feb 13, 2026

Fixes #298

Summary by CodeRabbit

  • Bug Fixes

    • Waveform warnings now include the attribute name and its full waveform shape.
  • Improvements

    • Read/write widget selection for tables and enums is more accurate, producing better UI controls via attribute-aware handling.
  • Tests

    • Updated tests to match the revised widget-handling behavior using attribute wrappers.
  • Breaking Changes

    • Public widget-related APIs now expect attribute objects (with datatype) instead of raw datatype inputs.

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Refactor EPICS GUI handlers to accept full Attribute objects instead of raw datatypes; dispatch and widget creation now use attribute.datatype. PVA GUI imports Attribute, AttrR, AttrW and handles Table read/write via attribute.datatype.structured_dtype. Tests updated to pass Attributes.

Changes

Cohort / File(s) Summary
EPICS CA GUI
src/fastcs/transports/epics/gui.py
Changed _get_read_widget and _get_write_widget signatures to accept Attribute; dispatch now uses attribute.datatype; enum handling reads names from attribute.datatype; updated _get_attribute_component call sites to pass full Attribute; improved 1D waveform warning to include the attribute and waveform shape; removed DataType import.
EPICS PVA GUI
src/fastcs/transports/epics/pva/gui.py
Added imports Attribute, AttrR, AttrW; _get_read_widget/_get_write_widget now accept Attribute; Table read uses attribute.datatype.structured_dtype and wraps fields with AttrR(...) for widget creation; Table write iterates attribute.datatype.structured_dtype, maps numpy field types to fastcs datatypes, uses CheckBox for Bool fields and AttrW(...) wrappers for others, returning TableWrite with per-field widgets; non-table paths delegate with the full attribute.
Tests
tests/transports/epics/ca/test_gui.py
Updated tests to call _get_write_widget with AttrR(Waveform(...))/Attribute objects instead of bare datatypes to match the revised API and attribute-based signatures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through datatypes, now holding the whole,
Attributes cradled, each widget finds its role,
Tables and enums line up in a row,
Waveforms whisper shape when warnings must show,
A tiny rabbit dance — refactor in tow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: passing Attribute instead of DataType into _get_read_widget, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR fulfills all coding requirements from issue #298: replaces DataType with Attribute in _get_read_widget signatures, updates call sites to pass Attribute objects, and enhances error reporting with attribute context for 2D waveforms.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objective of passing Attribute instead of DataType into widget methods; changes to _get_write_widget are necessary refactoring to maintain consistency with the primary goal.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pass_attribute_into_get_read_widget

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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 and usage tips.

@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 95.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 90.83%. Comparing base (4992357) to head (e0b86cc).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/fastcs/transports/epics/pva/gui.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #321   +/-   ##
=======================================
  Coverage   90.83%   90.83%           
=======================================
  Files          70       70           
  Lines        2553     2554    +1     
=======================================
+ Hits         2319     2320    +1     
  Misses        234      234           

☔ View full report in Codecov by Sentry.
📢 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.

@GDYendell GDYendell force-pushed the pass_attribute_into_get_read_widget branch from 98ddf4f to 3eb02cc Compare February 17, 2026 15:52
Copy link
Contributor

@GDYendell GDYendell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Minor comments and as you suggested we should update the write equivalents to match.

@jacob720 jacob720 requested a review from GDYendell February 19, 2026 11:27
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/fastcs/transports/epics/pva/gui.py (2)

58-58: Semantic mismatch: AttrR (read-only) used to drive write-widget selection.

super()._get_write_widget(AttrR(fastcs_datatype)) wraps a column datatype in a read-only attribute solely so the parent's dispatch (attribute.datatype) resolves correctly. This works today because EpicsGUI._get_write_widget ignores the attribute kind, but it's misleading and fragile if that assumption changes.

A cleaner pattern would be to extract a small private helper that accepts a raw DataType directly (matching the pre-refactor contract), rather than constructing a semantically incorrect AttrR wrapper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fastcs/transports/epics/pva/gui.py` at line 58, The call that constructs
a read-only wrapper AttrR(fastcs_datatype) to feed into
super()._get_write_widget is semantically wrong; instead add a small private
helper (e.g., _select_write_widget_for_datatype) that accepts the raw DataType
and encapsulates the dispatch logic, then change the call site to call this
helper with fastcs_datatype and have it delegate to EpicsGUI._get_write_widget
or the superclass logic without fabricating an AttrR; update references to
AttrR(...) in this file to use the new helper so attribute kind is not faked.

11-12: Inconsistent import style: use package-level fastcs.attributes imports.

The parent module src/fastcs/transports/epics/gui.py imports via from fastcs.attributes import Attribute, AttrR, .... These two lines reach into private submodules directly, which is inconsistent and couples this file to the internal layout.

♻️ Proposed fix
-from fastcs.attributes.attr_r import AttrR
-from fastcs.attributes.attribute import Attribute
+from fastcs.attributes import AttrR, Attribute
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fastcs/transports/epics/pva/gui.py` around lines 11 - 12, Imports in
gui.py dig into submodules (using "from fastcs.attributes.attr_r import AttrR"
and "from fastcs.attributes.attribute import Attribute"), which is inconsistent
with package-level imports; change them to use the package-level API by
importing AttrR and Attribute from fastcs.attributes (e.g., replace those two
specific import statements with a single "from fastcs.attributes import
Attribute, AttrR"), keeping the same symbols so other code (references to
Attribute and AttrR) remains unchanged and preserving import ordering/style used
in the parent module.
tests/transports/epics/ca/test_gui.py (1)

96-96: Nitpick: use array_dtype= keyword argument for consistency.

Every other Waveform instantiation in this file uses the explicit keyword argument Waveform(array_dtype=np.int32) (Lines 43, 57), but this line omits the keyword.

♻️ Proposed fix
-    assert gui._get_write_widget(attribute=AttrR(Waveform(np.int32))) is None
+    assert gui._get_write_widget(attribute=AttrR(Waveform(array_dtype=np.int32))) is None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/transports/epics/ca/test_gui.py` at line 96, The assertion uses
Waveform(np.int32) without the explicit keyword; update the call to use the same
style as the rest of the file by changing Waveform(np.int32) to
Waveform(array_dtype=np.int32) in the assertion that calls
gui._get_write_widget(attribute=AttrR(Waveform(...))). This keeps instantiation
consistent with other lines using Waveform(array_dtype=...) and avoids
positional-argument ambiguity for the Waveform constructor.
src/fastcs/transports/epics/gui.py (1)

81-81: Optional: move long TypeError message into the exception class or a constant.

Ruff (TRY003) flags inline long messages on raise statements. Extracting the message to a constant or a dedicated exception subclass would satisfy this.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fastcs/transports/epics/gui.py` at line 81, The inline long TypeError
message in src/fastcs/transports/epics/gui.py should be extracted: either define
a module-level constant like UNHANDLED_DATATYPE_MSG = "Unsupported type {type}:
{value}" and format it at raise, or create a small subclass
UnsupportedDatatypeError(Exception) that accepts the datatype and builds the
message; then replace the current raise TypeError(f"...") with raise
UnsupportedDatatypeError(datatype) or raise
TypeError(UNHANDLED_DATATYPE_MSG.format(type=type(datatype), value=datatype));
reference the existing raise site (the TypeError raise in this module) when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/fastcs/transports/epics/gui.py`:
- Around line 73-76: The warning currently interpolates waveform.shape (a tuple)
which prints dimensions like "(10, 10)D"; change the interpolation to the
dimensionality count by using waveform.ndim (or len(waveform.shape)) in the
logger.warning call so the message becomes e.g. "... is a 2D waveform" — update
the logger.warning invocation that references attribute and waveform (in this
EPICS CA transport check) to use waveform.ndim instead of waveform.shape.

In `@src/fastcs/transports/epics/pva/gui.py`:
- Around line 47-52: Remove the dead binding `datatype = attribute.datatype` in
the `_get_write_widget` method: it's unused because `match attribute.datatype:`
and the loop `for _, datatype in attribute.datatype.structured_dtype:` shadow
it; delete that assignment so only `attribute.datatype` is referenced via the
match and loop variables (`attribute`, `attribute.datatype`, and loop
`datatype`) remain.

---

Nitpick comments:
In `@src/fastcs/transports/epics/gui.py`:
- Line 81: The inline long TypeError message in
src/fastcs/transports/epics/gui.py should be extracted: either define a
module-level constant like UNHANDLED_DATATYPE_MSG = "Unsupported type {type}:
{value}" and format it at raise, or create a small subclass
UnsupportedDatatypeError(Exception) that accepts the datatype and builds the
message; then replace the current raise TypeError(f"...") with raise
UnsupportedDatatypeError(datatype) or raise
TypeError(UNHANDLED_DATATYPE_MSG.format(type=type(datatype), value=datatype));
reference the existing raise site (the TypeError raise in this module) when
making the change.

In `@src/fastcs/transports/epics/pva/gui.py`:
- Line 58: The call that constructs a read-only wrapper AttrR(fastcs_datatype)
to feed into super()._get_write_widget is semantically wrong; instead add a
small private helper (e.g., _select_write_widget_for_datatype) that accepts the
raw DataType and encapsulates the dispatch logic, then change the call site to
call this helper with fastcs_datatype and have it delegate to
EpicsGUI._get_write_widget or the superclass logic without fabricating an AttrR;
update references to AttrR(...) in this file to use the new helper so attribute
kind is not faked.
- Around line 11-12: Imports in gui.py dig into submodules (using "from
fastcs.attributes.attr_r import AttrR" and "from fastcs.attributes.attribute
import Attribute"), which is inconsistent with package-level imports; change
them to use the package-level API by importing AttrR and Attribute from
fastcs.attributes (e.g., replace those two specific import statements with a
single "from fastcs.attributes import Attribute, AttrR"), keeping the same
symbols so other code (references to Attribute and AttrR) remains unchanged and
preserving import ordering/style used in the parent module.

In `@tests/transports/epics/ca/test_gui.py`:
- Line 96: The assertion uses Waveform(np.int32) without the explicit keyword;
update the call to use the same style as the rest of the file by changing
Waveform(np.int32) to Waveform(array_dtype=np.int32) in the assertion that calls
gui._get_write_widget(attribute=AttrR(Waveform(...))). This keeps instantiation
consistent with other lines using Waveform(array_dtype=...) and avoids
positional-argument ambiguity for the Waveform constructor.

@jacob720 jacob720 self-assigned this Feb 19, 2026
Copy link
Contributor

@GDYendell GDYendell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good except for the two comments made.

@jacob720 jacob720 requested a review from GDYendell February 19, 2026 13:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/fastcs/transports/epics/gui.py (1)

81-81: Ruff TRY003: long exception messages should live in the exception class.

Both _get_read_widget (line 81) and _get_write_widget (line 98) use the same inline-message pattern flagged by the linter. Extracting to a custom exception or shortening the inline string would silence this.

♻️ One approach: a shared custom exception
+class UnsupportedDataTypeError(TypeError):
+    def __init__(self, datatype: object) -> None:
+        super().__init__(f"Unsupported type {type(datatype)}: {datatype}")

Then in both match arms:

-            case datatype:
-                raise TypeError(f"Unsupported type {type(datatype)}: {datatype}")
+            case datatype:
+                raise UnsupportedDataTypeError(datatype)

Also applies to: 98-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fastcs/transports/epics/gui.py` at line 81, Replace the long inline
TypeError messages in _get_read_widget and _get_write_widget with a short raise
that uses a new custom exception class (e.g., UnsupportedDatatypeError) or a
very short message; add a new UnsupportedDatatypeError exception type
(subclassing TypeError) and move the detailed message construction into that
class (via its __init__ or __str__) so the raise sites simply do raise
UnsupportedDatatypeError(datatype) or raise UnsupportedDatatypeError; update the
two raise sites (currently raising TypeError with the long f-string) to use this
new exception to satisfy the linter.
src/fastcs/transports/epics/pva/gui.py (2)

57-57: Use AttrW instead of AttrR for semantic clarity in write-widget factory.

super()._get_write_widget(AttrR(fastcs_datatype)) is functionally correct because the parent method dispatches only on attribute.datatype. However, passing a read-only attribute type to a write-widget method is semantically misleading. Since both AttrR and AttrW accept the same datatype positional argument, the refactor is safe and improves code clarity.

♻️ Proposed refactor
-                        widget = super()._get_write_widget(AttrR(fastcs_datatype))
+                        widget = super()._get_write_widget(AttrW(fastcs_datatype))

Update the import:

-from fastcs.attributes.attr_r import AttrR
+from fastcs.attributes.attr_r import AttrR
+from fastcs.attributes.attr_w import AttrW
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fastcs/transports/epics/pva/gui.py` at line 57, The call to
super()._get_write_widget currently passes AttrR(fastcs_datatype), which is
semantically misleading for a write-widget factory; change the call to use
AttrW(fastcs_datatype) instead so the write-widget dispatch reflects a write
attribute type, i.e. replace AttrR with AttrW where
super()._get_write_widget(AttrR(fastcs_datatype)) is invoked; ensure AttrW is
imported if not already.

11-12: Consider importing from the top-level fastcs.attributes package for consistency.

The parent gui.py uses from fastcs.attributes import Attribute, AttrR, .... These submodule imports are functionally equivalent since the package re-exports both classes in __init__.py, but aligning with the convention used elsewhere keeps the import surface consistent.

♻️ Proposed refactor
-from fastcs.attributes.attr_r import AttrR
-from fastcs.attributes.attribute import Attribute
+from fastcs.attributes import AttrR, Attribute
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fastcs/transports/epics/pva/gui.py` around lines 11 - 12, The imports in
gui.py use submodule paths (from fastcs.attributes.attr_r import AttrR and from
fastcs.attributes.attribute import Attribute); update them to the top-level
re-exports used elsewhere by replacing those two imports with a single import
from fastcs.attributes that imports Attribute and AttrR (reference symbols:
Attribute, AttrR, file's gui.py) so the import surface matches the parent module
convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/fastcs/transports/epics/gui.py`:
- Line 81: Replace the long inline TypeError messages in _get_read_widget and
_get_write_widget with a short raise that uses a new custom exception class
(e.g., UnsupportedDatatypeError) or a very short message; add a new
UnsupportedDatatypeError exception type (subclassing TypeError) and move the
detailed message construction into that class (via its __init__ or __str__) so
the raise sites simply do raise UnsupportedDatatypeError(datatype) or raise
UnsupportedDatatypeError; update the two raise sites (currently raising
TypeError with the long f-string) to use this new exception to satisfy the
linter.

In `@src/fastcs/transports/epics/pva/gui.py`:
- Line 57: The call to super()._get_write_widget currently passes
AttrR(fastcs_datatype), which is semantically misleading for a write-widget
factory; change the call to use AttrW(fastcs_datatype) instead so the
write-widget dispatch reflects a write attribute type, i.e. replace AttrR with
AttrW where super()._get_write_widget(AttrR(fastcs_datatype)) is invoked; ensure
AttrW is imported if not already.
- Around line 11-12: The imports in gui.py use submodule paths (from
fastcs.attributes.attr_r import AttrR and from fastcs.attributes.attribute
import Attribute); update them to the top-level re-exports used elsewhere by
replacing those two imports with a single import from fastcs.attributes that
imports Attribute and AttrR (reference symbols: Attribute, AttrR, file's gui.py)
so the import surface matches the parent module convention.

@GDYendell GDYendell merged commit 02d634a into main Feb 20, 2026
10 of 11 checks passed
@GDYendell GDYendell deleted the pass_attribute_into_get_read_widget branch February 20, 2026 14:03
@GDYendell
Copy link
Contributor

Thanks @jacob720 !

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/transports/epics/ca/test_gui.py (1)

96-98: Consider using AttrW instead of AttrR in test_get_write_widget_none.

Passing a read-only AttrR to _get_write_widget works today because the parent only inspects attribute.datatype, but a writable attribute better reflects real call-site usage and makes the test's intent clearer. If the method ever branches on access mode, AttrR would silently test an unreachable path.

🔧 Suggested change
 def test_get_write_widget_none():
     gui = EpicsGUI(ControllerAPI(), "DEVICE")
     assert (
-        gui._get_write_widget(attribute=AttrR(Waveform(array_dtype=np.int32))) is None
+        gui._get_write_widget(attribute=AttrW(Waveform(array_dtype=np.int32))) is None
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/transports/epics/ca/test_gui.py` around lines 96 - 98, Test uses a
read-only AttrR when calling gui._get_write_widget in
test_get_write_widget_none; change the test to pass a writable attribute (use
AttrW with the same Waveform(array_dtype=np.int32) argument) so the test
reflects real call-site usage and avoids exercising a read-only path that may
become irrelevant if access-mode logic is added; update the call in the test
(the gui._get_write_widget(attribute=...)) to use AttrW instead of AttrR and
ensure any necessary imports or symbols remain available.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/transports/epics/ca/test_gui.py`:
- Around line 96-98: Test uses a read-only AttrR when calling
gui._get_write_widget in test_get_write_widget_none; change the test to pass a
writable attribute (use AttrW with the same Waveform(array_dtype=np.int32)
argument) so the test reflects real call-site usage and avoids exercising a
read-only path that may become irrelevant if access-mode logic is added; update
the call in the test (the gui._get_write_widget(attribute=...)) to use AttrW
instead of AttrR and ensure any necessary imports or symbols remain available.

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.

Pass Attribute to _get_read_widget instead of DataType

2 participants