Cannot implement _missing_ on Flag enum

I’m writing a business application layer around SQLAlchemy. I would like to use python’s enum.Flag class to define a enumeration to pass as a function argument for declaring what columns are desired from a query.

from enum import Flag, auto

class RunsetColumns(Flag):
    name = auto()
    date = auto()
    manual_label= auto()
    submission_type = auto()
    purpose = auto()

That works

RunsetColumns.name | RunsetColumns.date will produce a member that I can iterate over for performing the query and it allows validating that the requested columns are spelled correctly.

I find how writing RunsetColumns.name | RunsetColumns.date to be verbose and I’m extremely certain that the business people are going to be confused by | and want to naturally use & which will produce undesired results.

So I wanted to implement _missing_ as a less verbose solution.

from enum import Flag, auto
from functools import reduce
from operator import or_

class RunsetColumns(Flag):
    name = auto()
    date = auto()
    manual_label= auto()
    submission_type = auto()
    purpose = auto()

    @classmethod
    def _missing_(cls, values):
       if isinstance(values, tuple):
           cols = (cls[val] for val in values)
           col_bits = reduce(or_, cols)
           return col_bits

RunsetColumns(("name", "date")) breaks. Even if this solution did work I don’t like having to wrap it into a tuple. I seriously don’t understand the design intent around EnumMeta.__call__ and overwriting the classes __new__ but that’s a different discussion.

Is there no way around this? Without writing a custom class method?

With _missing_ defined the previously working RunsetColumns.name | RunsetColumns.date fails for the same error message

ValueError: 3 is not a valid RunsetColumns

I’m not seeing anything in the enum docs indicating we aren’t supposed to be overriding _missing_ for Flag.

If anyone has a completely alternative solution I am all ears for that. What I am looking for is something that

  1. Will allow me iterate over the requested columns to funnel to SQLAlchemy Query. Additionally a containment test is useful for going the other way. I may have columns in a query and I want to check that they’re being requested.
  2. Validate the strings are valid column identifiers for the particular table used in the query.
  3. A nice to have, something that will work with a static type checker to validate the strings.

After constructing the bits in _missing_, call super()._missing_, that should work. Flag itself uses the _missing_ function to construct the special members.

2 Likes

Thank you Cornelius, that provided me enough to workout a solution.

I had to do

  1. The bit operations on the member values and not the members directly
  2. Account for different types being passed to _missing_.
    @classmethod
    def _missing_(cls, values):
       if isinstance(values, tuple):
           cols = (cls[val].value for val in values)
           col_bits = reduce(or_, cols)
           return super()._missing_(col_bits)
       elif isinstance(values, int):
           return super()._missing_(values)
       else:
           return None