Deltares / Deltares/imod-python
"User friendly" enums
@Manangka is already working on this.
Since Sep 3, 2024.
- Dominant language
- Python
- Stars
- 41
- Forks
- 12
- Avg merge
- 21h 8m
- Merged PRs (30d)
- 1
Description
Basically a follow up of #416:
We've briefly discussed this before: Enums are a great way to enumerate options, but a lot of our users aren't familiar with them. The issue with enums is also that you need to import the relevant enums from the right namespace. A pragmatic solution is to dynamically force inputs to enums, thereby checking them as well.
This gives decent errors:
from enum import Enum
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
color = Color("YELLOW")
ValueError: 'YELLOW' is not a valid Color
Ideally, it wouldn't tell you that it's wrong, but what the right entries are. This is especially helpful with typos.
from enum import Enum
from typing import Union, Type, TypeVar
E = TypeVar('E', bound='FlexibleEnum')
def _show_options(options: Enum) -> str:
return "\n * ".join(map(str, options.__members__))
class FlexibleEnum(Enum):
@classmethod
def from_value(cls: Type[E], value: Union[E, str]) -> E:
if isinstance(value, cls):
return value
elif isinstance(value, str):
try:
return cls.__members__[value]
except KeyError:
pass
raise ValueError(
# Use __repr__() so strings are shown with quotes.
f"{value.__repr__()} is not a valid {cls.__name__}. "
f"Valid options are:\n * {_show_options(cls)}"
)
class Color(FlexibleEnum):
RED = 1
GREEN = 2
BLUE = 3
Color.from_value("YELLOW")
Color.from_value("YELLOW")
ValueError: 'YELLOW' is not a valid Color. Valid options are:
* RED
* GREEN
* BLUE
Another advantage is that regular enums accept integer values.
E.g. one of the current enums:
class ALLOCATION_OPTION(Enum):
stage_to_riv_bot = 0
first_active_to_elevation = -1
stage_to_riv_bot_drn_above = 1
at_elevation = 2
at_first_active = 9 # Not an iMOD 5.6 option
In general, I don't think we want user facing functions to support something like .allocate(option=0). A default Enum will support ALLOCATION_OPTION(0).
But with the FlexibleEnum.from_value(), it won't (which is good):
Color.from_value(1)
ValueError: 1 is not a valid Color. Valid options are:
* RED
* GREEN
* BLUE
So my suggestion is to replace the Enums with these FlexibleEnums (or a better name), and preferable the same for all strings literals that we support. Then inside of the function:
def f(arg, option: str | ThisOptionEnum):
option = ThisOptionEnum.from_value(option)
...
This ensures the option is validated and that a clear error message listing the available valid options is printed.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.