Add AlphaMode enum for stringly-typed alpha_mode field#705
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.42% +0.01%
==========================================
Files 56 56
Lines 8026 8035 +9
Branches 1570 1571 +1
==========================================
+ Hits 5972 5980 +8
- Misses 1437 1438 +1
Partials 617 617
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:
|
Greptile Summary
This PR introduces
AlphaMode(str, Enum)to replace the stringly-typedalpha_modefield onkWaveMedium, providing IDE discoverability and a cleaner API while preserving backward compatibility throughstrinheritance.Key changes:
AlphaModeenum (NO_ABSORPTION,NO_DISPERSION,STOKES) with__str__returning the raw value, so all existing== "no_dispersion"comparisons throughout the codebase (kWaveSimulation.py,create_absorption_variables.py,checks.py) continue to work correctly.kWaveMedium.__post_init__normalises plain-string inputs toAlphaModeat construction time.check_fieldsupdated to useisinstance(AlphaMode)instead of anin [...]list check.Issues found:
check_fieldsguard (isinstance(AlphaMode)) will incorrectly reject a valid plain string that was assigned toalpha_modeafter construction (e.g.medium.alpha_mode = "no_dispersion"), because__post_init__does not run again. This is a regression from the previous behaviour since the type hint explicitly advertisesUnion[AlphaMode, str].ValueErrorfrom theAlphaMode()constructor rather than the more informative message describing valid values.Confidence Score: 4/5
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["kWaveMedium(alpha_mode=value)"] --> B{type of value?} B -->|"AlphaMode enum"| C[Store as-is] B -->|"str (plain)"| D["__post_init__: AlphaMode(value)"] D -->|valid string| E[Stored as AlphaMode] D -->|invalid string| F["ValueError from enum constructor\n(less descriptive message)"] B -->|None| G[Store None] B -->|post-construction assignment\nmedium.alpha_mode = 'str'| H["Bypasses __post_init__\n(plain str stored)"] H --> I{check_fields called?} I -->|Yes| J["isinstance check fails →\nValueError raised\n⚠️ REGRESSION"] I -->|No| K["Works: str comparisons pass\nvia str inheritance"] E --> L{check_fields called?} G --> L L -->|isinstance OK| M[Validation passes]Reviews (1): Last reviewed commit: "Merge branch 'master' into cleanup-alpha..." | Re-trigger Greptile