Source code for boxsdk.util.enum
# coding: utf-8
# pylint:disable=no-value-for-parameter
from __future__ import absolute_import, unicode_literals
from collections import OrderedDict
from itertools import chain
import sys
from enum import EnumMeta
from six import reraise
from six.moves import map # pylint:disable=redefined-builtin
__all__ = list(map(str, ['ExtendableEnumMeta']))
[docs]class ExtendableEnumMeta(EnumMeta):
"""A metaclass for enum hierarchies.
This allows you to define hierarchies such as this:
from box.util.compat import with_metaclass
class EnumBase(with_metaclass(ExtendableEnumMeta, Enum)): pass
class Enum1(EnumBase):
A = 'A'
class Enum2(EnumBase): pass
class Enum2_1(Enum2):
B = 'B'
class Enum2_2(Enum2):
C = 'C'
and have all members be accessible on EnumBase (as well as have all members
of Enum2_1 and Enum2_2 be available on Enum2) as if they had been defined
there.
Non-leaf classes still may not have members directly defined on them, as
with standard enums.
Most of the usual enum magic methods are extended: __contains__, __dir__,
__getitem__, __getattr__, __iter__, __len__, and __reversed__. Only __new__
is not extended; instead, a new method `lookup` is provided. The
__members__ property is also extended.
"""
[docs] def lookup(cls, value):
"""Custom value lookup, which does recursive lookups on subclasses.
If this is a leaf enum class with defined members, this acts the same
as __new__().
But if this is a base class with no defined members of its own, it
tries doing a value lookup on all its subclasses until it finds the
value.
NOTE: Because of the implementation details of Enum, this must be a new
classmethod, and can't be implemented as __new__() [1].
[1] <https://docs.python.org/3.5/library/enum.html#finer-points>
:param value:
The value to look up. Can be a value, or an enum instance.
:type value:
`varies`
:raises:
:class:`ValueError` if the value isn't found anywhere.
"""
try:
return cls(value)
except ValueError:
exc_info = sys.exc_info()
for subclass in cls.__subclasses__():
try:
return subclass.lookup(value)
except ValueError:
pass
# This needs to be `reraise()`, and not just `raise`. Otherwise,
# the inner exception from the previous line is re-raised, which
# isn't desired.
reraise(*exc_info)
@property
def __members__(cls):
members = OrderedDict(super(ExtendableEnumMeta, cls).__members__)
for subclass in cls.__subclasses__():
members.update(subclass.__members__)
return members
def __contains__(cls, member):
if super(ExtendableEnumMeta, cls).__contains__(member):
return True
def in_(subclass):
return member in subclass
return any(map(in_, cls.__subclasses__()))
def __dir__(cls):
return list(set(super(ExtendableEnumMeta, cls).__dir__()).union(*map(dir, cls.__subclasses__())))
def __getitem__(cls, name):
try:
return super(ExtendableEnumMeta, cls).__getitem__(name)
except KeyError:
exc_info = sys.exc_info()
for subclass in cls.__subclasses__():
try:
return subclass[name]
except KeyError:
pass
# This needs to be `reraise()`, and not just `raise`. Otherwise,
# the inner exception from the previous line is re-raised, which
# isn't desired.
reraise(*exc_info)
def __getattr__(cls, name):
try:
return super(ExtendableEnumMeta, cls).__getattr__(name)
except AttributeError:
exc_info = sys.exc_info()
try:
# If the super() call fails, don't call getattr() on all of the
# subclasses. Instead, use __getitem__ to do this. This is
# because we don't want to grab arbitrary attributes from
# subclasses, only enum members. For enum members, __getattr__
# and __getitem__ have the same behavior. And __getitem__ has
# the advantage of never grabbing anything other than enum
# members.
return cls[name] # pylint:disable=unsubscriptable-object
except KeyError:
pass
# This needs to be `reraise()`, and not just `raise`. Otherwise,
# the inner exception from the previous line is re-raised, which
# isn't desired.
reraise(*exc_info)
def __iter__(cls):
return chain(super(ExtendableEnumMeta, cls).__iter__(), chain.from_iterable(map(iter, cls.__subclasses__())))
def __len__(cls):
return super(ExtendableEnumMeta, cls).__len__() + sum(map(len, cls.__subclasses__()))
def __reversed__(cls):
return reversed(list(cls))