How to construct a case insensitive enum?
enum have missing function which can be overridden to make enum case insensitive. As per documentation https://docs.python.org/3.11/howto/enum.html missing – a lookup function used when a value is not found; may be overridden
example
class Status(enum.Enum):
@classmethod
def _missing_(cls, value):
for member in cls:
if member.value == value.upper():
return member
SUCCESS = 'SUCCESS'
FAILURE = 'FAILURE'
print(Status('success'))
Output
Status.SUCCESS
Not 100% sure this will work in Python 2.7, but I came up with a simple way to make it work exactly as requested for Python 3.6+.
The idea is that lookup for names is done by the class using square brackets, which means that it uses __getitem__
from the metaclass. So you can make a simple metaclass that implements a case insensitive search. Since it will extend the existing EnumMeta
, it will be totally compatible with existing enums:
class CaseInsensitiveEnumMeta(EnumMeta):
def __getitem__(self, item):
if isinstance(item, str):
item = item.upper()
return super().__getitem__(item)
This presupposes that your enums are all uppercase. Using it is pretty straightforward:
class Label(Enum, metaclass=CaseInsensitiveEnumMeta):
REDAPPLE = 1
GREENAPPLE = 2
I'm not sure you even need Enum
in this case. The catch here is that your enums have to be all uppercase for this to work. If you want to have a truly insensitive search, you would have to make sure that all of the keys in __members__
are properly casefolded.
In the meantime, you can do
>>> Label['GreenApple']
Label.GREENAPPLE
In Python 3.6 and aenum 2.0
12 (which is compatible with 2.7 and 3.0+) a new method has been added: _missing_
2.
This method is called just before a ValueError
is raised to give custom code a chance to try and find the enum member by value. Unfortunately, this makes it unsuited for your use-case -- looking up by name.
Fortunately, aenum
has the extra option of defining a _missing_name_
method3 which will be called when name lookup fails.
Your code above should look like this:
from aenum import Enum
class Label(Enum):
RedApple = 1
GreenApple = 2
@classmethod
def _missing_name_(cls, name):
for member in cls:
if member.name.lower() == name.lower():
return member
And in use:
>>> Label['redapple']
<Label.RedApple: 1>
If stuck using the 3.6 stdlib (or want to stay compatible with it) you can (ab)use _missing_
but:
- you will have to do
Label('redapple')
instead (round parens, not square brackets), and - you will be working against the design of enum ('redapple' is the name, not the value)
1 Disclosure: I am the author of the Python stdlib Enum
, the enum34
backport, and the Advanced Enumeration (aenum
) library.
2enum34
does not have these improvements as it is only being maintained for bug fixes.
3_missing_value_
is preferred in aenum
as it is more explicit about what it is checking, but it falls back to _missing_
for compatibility with the stdlib.
4 aenum v2.0.2 has a bug where _missing_
is called for both values and names if _missing_name_
has not been overridden -- this is fixed in v2.0.3+.