Alex Lin Wang 王帅

Enum Magic

September 3, 2025 • 8 min read
Mastering Python's Enum, StrEnum, IntEnum, and Flags for type-safe constants and better code organization.

Imagine you are writing a function that takes in levels of log levels and wants to do something such as printing or notifying specific individuals based on the level. For the sake of simplicity, the levels are info, warning, error:

def log(level: str, message: str) -> None:
    ...

If you use str as arguments, you are susceptible to misspellings and potential unreal levels as someone might pass "errror" or "critical". To resolve this, you should use Enum, but preferably use StrEnum (Python 3.11+) with something like this:

from enum import StrEnum

class LogLevel(StrEnum):
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"

If, instead, you wanted to use integers to log different levels of warnings you could use IntEnum. This is especially useful if you wanted to do mathematical operations on these values, such as if you wanted to sum the values of total ExitCode's generated.

from enum import IntEnum

class LogLevel(IntEnum):
    INFO = 0
    WARNING = 1
    ERROR = 2
    BIGERROR = 3

def log(level: LogLevel, message: str) -> None:
    ...

Or even better, you could use auto() for both StrEnum and IntEnum, this way you can guarantee uniqueness and adding new values is easier. For StrEnum, it represents the value as a lowercase of the real member name.

from enum import StrEnum, auto

class LogLevel(StrEnum):
    INFO = auto()     # "info"
    WARNING = auto()  # "warning"
    ERROR = auto()    # "error"
    SUCCESS = auto()  # "success"

For IntEnum, it automatically creates order and generates uniqueness so no need to hardcode, and also makes adding new items easier.

from enum import IntEnum, auto

class LogLevel(IntEnum):
    INFO = auto()     # 1
    WARNING = auto()  # 2
    ERROR = auto()    # 3
    SUCCESS = auto()  # 4

Finally, you can also use Flags which are assigned bitwise combinations of values (1, 2, 4, 8, 16). They are useful when you want to represent multiple boolean options at once, and together. To combine these use OR (|) and AND (&) operators.

from enum import Flag, auto

class Permission(Flag):
    READ = auto()     # 1
    WRITE = auto()    # 2
    EXECUTE = auto()  # 4

# Give read and write perms to the user
user_perms = Permission.READ | Permission.WRITE

Useful Decorators

A useful decorator would be the @unique, which guarantees that you don't have duplicate values.

from enum import Enum, unique

@unique
class Status(Enum):
    OK = 1
    SUCCESS = 1   # ❌ ValueError: duplicate value 1

Another potentially useful decorator for ENUMs is @verify which helps you confirm that your values satisfy a certain set of conditions.

from enum import Enum, verify, UNIQUE, CONTINUOUS

@verify(UNIQUE, CONTINUOUS)
class ErrorCode(Enum):
    NOT_FOUND = 1
    TIMEOUT = 2
    UNKNOWN = 3   # ✅ values are unique + continuous (1, 2, 3)

Questions or feedback? Feel free to reach out!