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.012 (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+.