How to construct a case insensitive enum?

Question:

I have a simple Python 2.7 enum:

from enum import Enum

class Label(enum):
    RedApple = 1
    GreenApple = 2

I would like to be able create enum objects using case-insensitive keys:

fruitname = "redapple"
a = Label[fruitname]

I’ve tried to create an __init__ method:

def __init__(self, key):
    super(Label, self).__init__()
    pass # do comparison here

but I keep running into errors:

super(Label, self).__init__()
NameError: global name 'Label' is not defined

I’d like to do a comparison on key.lower().strip(). Is this even possible?

Asked By: Hephaestus

||

Answers:

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.

2 enum34 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+.

Answered By: Ethan Furman

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
Answered By: Mad Physicist

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
Answered By: codingclues

I don’t wanted to define this validation in the enum class, this is a general functionality I want to reuse. So I decided to do this in an MetaEnum:

from enum import Enum, auto, EnumMeta

class CaseInsensitveKeys(EnumMeta):
    def __getitem__(cls, item):
        try:
            return super().__getitem__(item)
        except:
            for key in cls._member_map_.keys():
                if key.casefold() == item.casefold():
                    return super().__getitem__(key)

class DatabaseType(Enum, metaclass=CaseInsensitveKeys):
    PostgreSQL = auto()
    Oracle = auto()
Answered By: Max
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.