jxramos
jxramos

Reputation: 8296

Is there a way to convert a string representing multiple flag members to a composite enum.Flag?

I've observed that I can convert certain integer values into their flag decomposed types, and that I can convert flag string names into flags, but I haven't yet figured out if there's a means to convert a string union of flag names to an enum.Flag.

import enum
flag = enum.Flag('flg' , ['a', 'b', 'c', 'd'] )

# Valid 1:1 conversions...
>>> flag["a"]
<flg.a: 1>
>>> flag["b"]
<flg.b: 2>
>>> flag(1)
<flg.a: 1>
>>> flag(2)
<flg.b: 2>

# Valid union int conversion
>>> flag(7)
<flg.c|b|a: 7>
>>> flag(15) # largest integer flag can represent (1+2+4+8)
<flg.d|c|b|a: 15>

# Out of bounds
>>> flag(16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Anaconda3\lib\enum.py", line 291, in __call__
    return cls.__new__(cls, value)
  File "C:\Anaconda3\lib\enum.py", line 533, in __new__
    return cls._missing_(value)
  File "C:\Anaconda3\lib\enum.py", line 673, in _missing_
    possible_member = cls._create_pseudo_member_(value)
  File "C:\Anaconda3\lib\enum.py", line 688, in _create_pseudo_member_
    raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 16 is not a valid flg

I was hoping I might be able to pull off something in the spirit of flag["d|c|b|a"], but being a dictionary this conversion only works for distinct flag member names, not any aggregation of them. In the mean time I'm doing the split, convert, union merge manually but I'm curious if there's a more direct route like the int conversion case was capable of.

EDIT: Looking at the source for enum.py, it appears the correct terminology is not union but composite

# enum.py
def _create_pseudo_member_(cls, value):
    """
    Create a composite member iff value contains only members.
    """

Upvotes: 3

Views: 667

Answers (1)

schesis
schesis

Reputation: 59238

You could subclass enum.Flag and intercept its _missing_() method:

from functools import reduce

import enum


class UnionFlag(enum.Flag):

    @classmethod
    def _missing_(cls, value):
        if isinstance(value, str):
            return reduce(cls.__or__, (cls[s] for s in value.split('|')))
        else:
            return super()._missing_(value)

>>> flag = UnionFlag('uflg', ['a', 'b', 'c', 'd'])
>>> flag.a
<uflg.a: 1>
>>> flag(3)
<uflg.b|a: 3>
>>> flag['c']
<uflg.c: 4>
>>> flag('a|b|d')
<uflg.d|b|a: 11>

Upvotes: 3

Related Questions