Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert enum to Literal type alias in Python typing

Is there a way to type annotate a function or variable in Python in such a way it allows both an enum or Literal formed form the attributes of the enum?

from enum import Enum
from typing import Literal


class State(str, Enum):
    ENABLED = "enabled"
    DISABLED = "disabled"

def is_enabled(state: State | Literal["enabled", "disabled"]) -> bool:
    if isinstance(state, str):
        state = State(state)
    return state == State.ENABLED

In other words, is there a way to obtain the alias for Literal["enabled", "disabled"] without having to rewrite all the keys of the enum?

like image 905
federicober Avatar asked Nov 22 '25 21:11

federicober


2 Answers

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.

like image 68
funnydman Avatar answered Nov 25 '25 09:11

funnydman


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

like image 39
Jim Avatar answered Nov 25 '25 10:11

Jim