First that comes to mind is matplotlib. Why are parameters strings? E.g. fig.legend(loc='topleft').
Wouldn't it be much more elegant for enum LegendPlacement.TOPLEFT to exist?
What was their reasoning when they decided "it'll be strings"?
EDIT: So many great answers already! Much to learn from this...
I'm afraid there's no such way. The first thing that comes to mind is iterating over enum's values to build a Literal type won't work, because Literal cannot contain arbitrary expressions. So, you cannot specify it explicitly:
# THIS DOES NOT WORK
def is_enabled(state: State | Literal[State.ENABLED.value, State.DISABLED.value]) -> bool:
...
There's an open issue on GitHub with the related discussion. Basically, you could have hardcoded literals for every Enum, but in that case, you need to update it in accordance with Enum updates, and one day it will be messy. So, I would either stick with State | str annotation or just State and expect your function to accept only enums.
Also, take into account that you do not need to explicitly create an Enum object to test its value, you can just write "enabled" == State.ENABLED as I mentioned in the comments.
It's very annoying that you can't easily create a Literal type hint for all the members of an enum, but one potential workaround if you have control of the enum (and can programmatically determine what the member name should be from the value) is to reverse the direction
Instead of creating a Literal type hint from the enum members, you can create an enum from the values in a Literal type hint using the functional API for Enum and typing.get_args
Your example would look like:
from enum import Enum
from typing import Literal, get_args
# variable naming might not be great here,
# but I figure it's good enough for the short example
StateValues = Literal["enabled", "disabled"]
State = Enum(
"State",
((state.upper(), state) for state in get_args(StateValues))
module="...", # fill in module and qualname to help with pickling
qualname="...",
)
def is_enabled(state: State | StateValues) -> bool:
return state is State.ENABLED or state == State.ENABLED.value
Huge downside to this approach is that now static analysis tools will flag State.ENABLED as an unknown attribute (and you lose the ability to put individual enum members in type hints or do exhaustiveness checking on enum members)
The functional API won't let you do everything that the class API does, but if all you need are the members to be defined, then this should work
Overall, this approach probably isn't worth it
Literal of enum values
Python, what's the Enum type good for? - Stack Overflow
Amend PEP 586 to make `enum` values subtypes of `Literal` - Typing - Discussions on Python.org
Which is better: string, or enum-like global constants in interface?
Videos
Contrived example. Suppose you are defining the API of a class FireExtinguisher that takes a constructor argument specifying whether it's type A, B or C. Which interface is better:
import firefighting as ff
extinguisher = ff.FireExtinguisher(ff.C)
# or
extinguisher = ff.FireExtinguisher("C")or something else?
It's easy to find instances of the first way: for example, the logging module defines logging.DEBUG, logging.INFO, etc. Similarly, the subprocess module defines several constants for Windows. But the string approach looks more concise.
In C, one would argue that the enum style is more efficient and provides a compile time check that you haven't specified an invalid name, while the string version would require a run-time check for validity. But in python, it's a runtime check, anyway, right?
What is the best way to do this in python and why?