String Enum in Python
Bare Python enum is not good enough: Int representation may be efficient, but it is not human readable, especially after it is serialized.
NOTE:
The color.py approach was sensible before Python 3.11, but should no longer be the preferred style:
Color(str, Enum)(line 9) is now superseded by StrEnum.- The custom
__repr__(line 40) changes interactive/container display; it is not what enables string serialization. Color._value2member_map_(line 38) accesses a private implementation detail.-
Accepting str Color exploits the surprising fact that string-enum members compare equal to raw strings. Prefer converting at an input boundary and using Color internally. - Modern enum members conventionally use uppercase names: Color.RED, not Color.red.
Wouldn’t it be nice to have a string enum? Multiple inheritance to the resque!
'''
Demo of the better enum implementation
'''
from enum import Enum
from typing import Union
class Color(str, Enum):
'''
Possible colors
see also https://fastapi-utils.davidmontague.xyz/user-guide/basics/enums/
'''
red = 'Red'
green = 'Green'
blue = 'Blue'
black = 'Black'
@classmethod
def is_dark(cls, st: Union[str, 'Color']) -> bool:
'''
Dark color recognizer
'''
return st in [cls.black]
@classmethod
def is_primary(cls, st: Union[str, 'Color']) -> bool:
'''
Primary color recognizer
'''
return st in [cls.red, cls.green, cls.black]
@classmethod
def is_valid(cls, st: Union[str, 'Color']) -> bool:
'''
Valid color recognizer
'''
return st in Color._value2member_map_
def __repr__(self):
'''
To enable Color serialization as a string...
'''
# default implementation
#type_ = type(self)
#module = type_.__module__
#qualname = type_.__qualname__
#return f"<{module}.{qualname} object at {hex(id(self))}>"
return repr(self.value)
Let’s see how this works for Color user…
>>> r = Color.red
>>> r == 'Red'
True
>>> r == 'red'
False
>>> r
'Red'
>>> Color.is_primary( r )
True
>>> Color.is_primary( 'Red' )
True
>>> j = { 'color': r }
>>> j
{'color': 'Red'}
>>> print( j )
{'color': 'Red'}
I like it much more than bare enums!
Better auto yet…
This subject gets traction! Checkout this FastAPI write-up!
Modern sources
For ordinary application enums:
from enum import StrEnum
class Color(StrEnum):
RED = 'Red'
GREEN = 'Green'
BLUE = 'Blue'
BLACK = 'Black'
@property
def is_dark(self) -> bool:
return self is Color.BLACK
Parse external input explicitly:
color = Color(raw_value) # raises ValueError for unknown input
The PersistedEnum (line 30) style is stronger when values are written to evidence, databases, or files:
values()(line 33) derives validation data from the members.decode()(line 39) rejects non-strings and unknown future values at the read boundary.- The injected failure function translates malformed persisted data into the subsystem’s stable domain error. That failure callback would be unnecessary abstraction for a simple color enum, but it is justified for agent-orchestra’s persistence contract.