1

I am wondering if I can use python enums to enforce specific string inputs from a user into my functions and classes, and avoid having to code the explicit checks against acceptable values.

So instead of this:

e.g.

utilities_supplied = {
    'electricity': 'Yes',
    'gas': 'No',
    'water': 'Yes',
}

def find_utility(utility):
    try:
        print(utilities_supplied[utility])
    except KeyError:
        raise KeyError(f'Please choose one of {utilities_supplied.keys()}, \'{utility}\' provided')

I would like to do this:


from enum import Enum
class Utility(Enum):
    electricity = 1
    gas = 2
    water = 3

Utility['electric']  
# not a member, I want this to raise an error which lists the acceptable options.

Is it possible for enums to raise an error listing the possible enumeration members?

ac24
  • 5,325
  • 1
  • 16
  • 31
  • 1
    Can you clarify what you need? ``enum``s already "enforce specific string inputs". Your example code does raise an error. – MisterMiyagi Feb 24 '20 at 11:36
  • I would like the error raised to also list the *acceptable* string inputs, so the user can adjust their string input to match one of the enum members. – ac24 Feb 24 '20 at 11:42

2 Answers2

2

You can use EnumMeta to overwrite __getitem__, which fetches the values from the enum:

import enum


class MyEnum(enum.EnumMeta):
    def __getitem__(cls, name):
        try:
            return super().__getitem__(name)
        except (KeyError) as error:
            options = ', '.join(cls._member_map_.keys())
            msg = f'Please choose one of {options}, \'{name}\' provided'
            raise ValueError(msg) from None


class Utility(enum.Enum, metaclass=MyEnum):
    electricity = 1
    gas = 2
    water = 3


fire = Utility['fire']
print(fire)

Output:

ValueError: Please choose one of electricity, gas, water, 'fire' provided
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
  • Thanks. I had tried overriding __getattr__ on the base Enum class but didn't get the desired result. This looks like a good solution. – ac24 Feb 24 '20 at 12:53
  • 1
    @MauriceMeyer, could you also show a solution using `__missing__`, and place it first? Subclassing `EnumMeta` as you have done works, but it's easy to get wrong. See [this question](https://stackoverflow.com/q/43730305/208880) for details. – Ethan Furman Feb 24 '20 at 15:34
  • Would be interested to see an implementation that avoids subclassing if possible. – ac24 Feb 26 '20 at 11:00
0

EDIT: This doesn't really work when I subclass the ValidatedEnum class. Any help gratefully received.

The solution using _missing_ was fairly straightforward it turns out! Thanks for the idea @EthanFurman.

from enum import Enum
class ValidatedEnum(Enum):
    electricity = 1
    gas = 2
    water = 3

    @classmethod
    def _missing_(cls, value):
        choices = list(cls.__members__.keys())
        raise ValueError("%r is not a valid %s, please choose from %s" % (value, cls.__name__, choices))

<ipython-input-1-8b49d805ac2d> in _missing_(cls, value)
      8     def _missing_(cls, value):
      9         choices = list(cls.__members__.keys())
---> 10         raise ValueError("%r is not a valid %s, please choose from %s" % (value, cls.__name__, choices))
     11 

ValueError: 'electric' is not a valid ValidatedEnum, please choose from ['electricity', 'gas', 'water']
ac24
  • 5,325
  • 1
  • 16
  • 31