Add AlphaMode enum for stringly-typed alpha_mode field#705
Conversation
- AlphaMode(str, Enum) with NO_ABSORPTION, NO_DISPERSION, STOKES - kWaveMedium.__post_init__ normalizes string inputs to enum - Backward compatible: AlphaMode == "no_dispersion" works via str inheritance - __str__ returns the value so f-strings render cleanly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| if self.alpha_mode is not None and not isinstance(self.alpha_mode, AlphaMode): | ||
| raise ValueError( | ||
| f"medium.alpha_mode must be an AlphaMode enum value or one of " | ||
| f"'no_absorption', 'no_dispersion', 'stokes', got {self.alpha_mode!r}" | ||
| ) |
There was a problem hiding this comment.
Post-construction string assignment silently breaks
check_fields
check_fields is called from kWaveSimulation after the object is fully constructed (line 610 in kWaveSimulation.py). Since kWaveMedium is a plain dataclass, its attributes are mutable — a caller can legally write medium.alpha_mode = "no_dispersion" after __post_init__ runs, bypassing the normalization step. Under the old code that assignment would still pass the in [...] check; under this PR it raises a ValueError even for a valid string value, silently breaking previously-working patterns.
The type hint Optional[Union[AlphaMode, str]] reinforces the expectation that plain strings remain valid inputs throughout the object's lifetime. Fix by normalising strings in check_fields as well, or by converting alpha_mode to a property setter so normalisation is always applied on write:
# check the absorption mode input is valid (already normalized to AlphaMode in __post_init__)
if self.alpha_mode is not None:
if isinstance(self.alpha_mode, str) and not isinstance(self.alpha_mode, AlphaMode):
self.alpha_mode = AlphaMode(self.alpha_mode) # late-normalise post-construction strings
elif not isinstance(self.alpha_mode, AlphaMode):
raise ValueError(
f"medium.alpha_mode must be an AlphaMode enum value or one of "
f"'no_absorption', 'no_dispersion', 'stokes', got {self.alpha_mode!r}"
)| if isinstance(self.alpha_mode, str) and not isinstance(self.alpha_mode, AlphaMode): | ||
| self.alpha_mode = AlphaMode(self.alpha_mode) |
There was a problem hiding this comment.
Invalid string raises unhelpful
ValueError from enum constructor
When an invalid string is passed at construction time (e.g. kWaveMedium(sound_speed=1500, alpha_mode="typo")), AlphaMode("typo") raises ValueError: 'typo' is not a valid AlphaMode — the more descriptive message in check_fields is never reached. Consider wrapping the conversion to re-raise with a clearer hint:
if isinstance(self.alpha_mode, str) and not isinstance(self.alpha_mode, AlphaMode):
try:
self.alpha_mode = AlphaMode(self.alpha_mode)
except ValueError:
valid = [m.value for m in AlphaMode]
raise ValueError(
f"medium.alpha_mode must be one of {valid}, got {self.alpha_mode!r}"
)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #705 +/- ##
==========================================
+ Coverage 74.40% 74.46% +0.05%
==========================================
Files 56 56
Lines 8026 8040 +14
Branches 1570 1570
==========================================
+ Hits 5972 5987 +15
Misses 1437 1437
+ Partials 617 616 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The type hint advertises Union[AlphaMode, str], so a plain string
assigned after __post_init__ runs (e.g. medium.alpha_mode = "no_dispersion")
must remain valid. check_fields previously rejected this. Extracts a
_to_alpha_mode helper used by both __post_init__ and check_fields, which
also gives a friendly error message for invalid strings instead of the
bare ValueError from AlphaMode("garbage").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@Greptile-app |
Original change (TullyMonster): replace incorrect `np.array` type hints (which is a callable, not a type) with proper `Optional[Union[float, int, np.ndarray]]` annotations across the `kWaveMedium` dataclass. This eliminates IDE warnings like "Expected type 'None', got 'float'" when passing scalars for `alpha_coeff`, `alpha_power`, etc. Also reconciled with the AlphaMode enum work from #705 (kept enum-aware `Optional[Union[AlphaMode, str]]` for `alpha_mode`). Greptile P2 fixes applied on top: - Drop unused `import kwave.utils.checks` (dead code after the `np.issubdtype(...)` migration replaced the `is_number(...)` call). - The new type hint allows `np.ndarray` for `alpha_power`, but `np.isscalar(np.array(1.5))` is `False`. Widen the scalar check in `_check_absorbing_without_stokes` so 0-d arrays are accepted as scalars. Regression test added. Test coverage rolls up existing AlphaMode normalization tests from master with the broader coverage in this PR. Co-authored-by: TullyMonster <47771282+TullyMonster@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile Summary
Adds
AlphaMode(str, Enum)to replace the stringly-typedalpha_modefield and introduces a_to_alpha_modehelper that normalises the value at construction time (__post_init__) and again insidecheck_fieldsto cover post-construction string reassignment. BecauseAlphaModeinherits fromstr, all existing equality/membership comparisons across the codebase (checks.py,create_absorption_variables.py,kWaveSimulation.py) continue to work without modification.Confidence Score: 5/5
Safe to merge — only P2 style findings, no logic or correctness issues.
All three changed files are correct. The str-Enum design preserves backward compatibility with every existing string comparison in the codebase. The two previous review concerns (post-construction bypass, unhelpful error message) are both addressed. Only minor style findings remain.
No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["caller sets alpha_mode\n(str, AlphaMode, or None)"] --> B{__post_init__ or\ncheck_fields} B --> C["_to_alpha_mode(value)"] C --> D{value is None or\nalready AlphaMode?} D -- Yes --> E["return as-is"] D -- No --> F["AlphaMode(value)"] F -- valid string --> G["AlphaMode enum member\n(NO_ABSORPTION / NO_DISPERSION / STOKES)"] F -- invalid --> H["ValueError with\nfriendly message"] G --> I["self.alpha_mode = AlphaMode member"] E --> I I --> J["Downstream comparisons\nalpha_mode == 'no_dispersion' ✓\nalpha_mode == 'stokes' ✓\nalpha_mode in list ✓"]Reviews (3): Last reviewed commit: "Accept post-construction string assignme..." | Re-trigger Greptile