Spaces:
Sleeping
Sleeping
from __future__ import print_function | |
from ._common import * | |
from ._constant import NamedConstant | |
from ._tuple import NamedTuple | |
from collections import defaultdict | |
import sys as _sys | |
__all__ = [ | |
'bit_count', 'is_single_bit', 'bin', 'property', 'bits', | |
'AddValue', 'MagicValue', 'MultiValue', 'NoAlias', 'Unique', 'enum', 'auto', | |
'AddValueEnum', 'MultiValueEnum', 'NoAliasEnum', 'UniqueEnum', 'AutoNumberEnum', | |
'OrderedEnum', 'unique', 'no_arg', 'extend_enum', 'enum_property', | |
'EnumType', 'EnumMeta', 'EnumDict', 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', | |
'LowerStrEnum', 'UpperStrEnum', 'ReprEnum', 'SqliteEnum', 'sqlite3', | |
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', | |
'add_stdlib_integration', 'remove_stdlib_integration', | |
'export', 'cls2module', '_reduce_ex_by_name', 'show_flag_values', | |
] | |
_bltin_bin = bin | |
try: | |
import sqlite3 | |
except ImportError: | |
sqlite3 = None | |
__all__.remove('SqliteEnum') | |
try: | |
RecursionError | |
except NameError: | |
# python3.4 | |
RecursionError = RuntimeError | |
try: | |
any | |
except NameError: | |
def any(iterable): | |
for element in iterable: | |
if element: | |
return True | |
return False | |
# derive from stdlib enum if possible | |
stdlib_enums = () | |
try: | |
import enum | |
if hasattr(enum, 'version'): | |
raise ImportError('wrong version') | |
else: | |
from enum import EnumMeta as StdlibEnumMeta, Enum as StdlibEnum, IntEnum as StdlibIntEnum | |
StdlibFlag = StdlibIntFlag = StdlibStrEnum = StdlibReprEnum = None | |
stdlib_enums = StdlibEnum, StdlibIntEnum | |
except ImportError: | |
StdlibEnumMeta = StdlibEnum = StdlibIntEnum = StdlibIntFlag = StdlibFlag = StdlibStrEnum = StdlibStrEnum = StdlibReprEnum = None | |
if StdlibEnum: | |
try: | |
from enum import IntFlag as StdlibIntFlag, Flag as StdlibFlag | |
stdlib_enums += StdlibFlag, StdlibIntFlag | |
except ImportError: | |
pass | |
try: | |
from enum import StrEnum as StdlibStrEnum | |
stdlib_enums += (StdlibStrEnum, ) | |
except ImportError: | |
pass | |
try: | |
from enum import ReprEnum as StdlibReprEnum | |
stdlib_enums += (StdlibReprEnum, ) | |
except ImportError: | |
pass | |
# will be exported later | |
MagicValue = AddValue = MultiValue = NoAlias = Unique = None | |
def export(collection, namespace=None): | |
""" | |
export([collection,] namespace) -> Export members to target namespace. | |
If collection is not given, act as a decorator. | |
""" | |
if namespace is None: | |
namespace = collection | |
def export_decorator(collection): | |
return export(collection, namespace) | |
return export_decorator | |
elif issubclass(collection, NamedConstant): | |
for n, c in collection.__dict__.items(): | |
if isinstance(c, NamedConstant): | |
namespace[n] = c | |
elif issubclass(collection, Enum) or stdlib_enums and issubclass(collection, stdlib_enums): | |
data = collection.__members__.items() | |
for n, m in data: | |
namespace[n] = m | |
else: | |
raise TypeError('%r is not a supported collection' % (collection,) ) | |
return collection | |
def bit_count(num): | |
""" | |
return number of set bits | |
Counting bits set, Brian Kernighan's way* | |
unsigned int v; // count the number of bits set in v | |
unsigned int c; // c accumulates the total bits set in v | |
for (c = 0; v; c++) | |
{ v &= v - 1; } //clear the least significant bit set | |
This method goes through as many iterations as there are set bits. So if we | |
have a 32-bit word with only the high bit set, then it will only go once | |
through the loop. | |
* The C Programming Language 2nd Ed., Kernighan & Ritchie, 1988. | |
This works because each subtraction "borrows" from the lowest 1-bit. For | |
example: | |
loop pass 1 loop pass 2 | |
----------- ----------- | |
101000 100000 | |
- 1 - 1 | |
= 100111 = 011111 | |
& 101000 & 100000 | |
= 100000 = 0 | |
It is an excellent technique for Python, since the size of the integer need | |
not be determined beforehand. | |
(from https://wiki.python.org/moin/BitManipulation) | |
""" | |
count = 0 | |
while num: | |
num &= num - 1 | |
count += 1 | |
return count | |
def is_single_bit(value): | |
""" | |
True if only one bit set in value (should be an int) | |
""" | |
if value == 0: | |
return False | |
value &= value - 1 | |
return value == 0 | |
def _iter_bits_lsb(value): | |
""" | |
Return each bit value one at a time. | |
>>> list(_iter_bits_lsb(6)) | |
[2, 4] | |
""" | |
while value: | |
bit = value & (~value + 1) | |
yield bit | |
value ^= bit | |
def bin(value, max_bits=None): | |
""" | |
Like built-in bin(), except negative values are represented in | |
twos-compliment, and the leading bit always indicates sign | |
(0=positive, 1=negative). | |
>>> bin(10) | |
'0b0 1010' | |
>>> bin(~10) # ~10 is -11 | |
'0b1 0101' | |
""" | |
ceiling = 2 ** (value).bit_length() | |
if value >= 0: | |
s = _bltin_bin(value + ceiling).replace('1', '0', 1) | |
else: | |
s = _bltin_bin(~value ^ (ceiling - 1) + ceiling) | |
sign = s[:3] | |
digits = s[3:] | |
if not digits: | |
digits = '0' | |
if max_bits is not None: | |
if len(digits) < max_bits: | |
digits = (sign[-1] * max_bits + digits)[-max_bits:] | |
return "%s %s" % (sign, digits) | |
def show_flag_values(value): | |
return list(_iter_bits_lsb(value)) | |
try: | |
from types import DynamicClassAttribute | |
base = DynamicClassAttribute | |
except ImportError: | |
base = object | |
DynamicClassAttribute = None | |
class property(base): | |
""" | |
This is a descriptor, used to define attributes that act differently | |
when accessed through an enum member and through an enum class. | |
Instance access is the same as property(), but access to an attribute | |
through the enum class will look in the class' _member_map_. | |
""" | |
# inherit from DynamicClassAttribute if we can in order to get `inspect` | |
# support | |
def __init__(self, fget=None, fset=None, fdel=None, doc=None): | |
self.fget = fget | |
self.fset = fset | |
self.fdel = fdel | |
# next two lines make property act the same as bltin_property | |
self.__doc__ = doc or fget.__doc__ | |
self.overwrite_doc = doc is None | |
# support for abstract methods | |
self.__isabstractmethod__ = bool(getattr(fget, '__isabstractmethod__', False)) | |
# names, if possible | |
def getter(self, fget): | |
fdoc = fget.__doc__ if self.overwrite_doc else None | |
result = type(self)(fget, self.fset, self.fdel, fdoc or self.__doc__) | |
result.overwrite_doc = self.__doc__ is None | |
return result | |
def setter(self, fset): | |
fdoc = fget.__doc__ if self.overwrite_doc else None | |
result = type(self)(self.fget, fset, self.fdel, self.__doc__) | |
result.overwrite_doc = self.__doc__ is None | |
return result | |
def deleter(self, fdel): | |
fdoc = fget.__doc__ if self.overwrite_doc else None | |
result = type(self)(self.fget, self.fset, fdel, self.__doc__) | |
result.overwrite_doc = self.__doc__ is None | |
return result | |
def __repr__(self): | |
member = self.ownerclass._member_map_.get(self.name) | |
func = self.fget or self.fset or self.fdel | |
strings = [] | |
if member: | |
strings.append('%r' % member) | |
if func: | |
strings.append('function=%s' % func.__name__) | |
return 'property(%s)' % ', '.join(strings) | |
def __get__(self, instance, ownerclass=None): | |
if instance is None: | |
try: | |
return ownerclass._member_map_[self.name] | |
except KeyError: | |
raise AttributeError( | |
'%r has no attribute %r' % (ownerclass, self.name) | |
) | |
else: | |
if self.fget is not None: | |
return self.fget(instance) | |
else: | |
if self.fset is not None: | |
raise AttributeError( | |
'cannot read attribute %r on %r' % (self.name, ownerclass) | |
) | |
else: | |
try: | |
return instance.__dict__[self.name] | |
except KeyError: | |
raise AttributeError( | |
'%r member has no attribute %r' % (ownerclass, self.name) | |
) | |
def __set__(self, instance, value): | |
if self.fset is None: | |
if self.fget is not None: | |
raise AttributeError( | |
"cannot set attribute %r on <aenum %r>" % (self.name, self.clsname) | |
) | |
else: | |
instance.__dict__[self.name] = value | |
else: | |
return self.fset(instance, value) | |
def __delete__(self, instance): | |
if self.fdel is None: | |
if self.fget or self.fset: | |
raise AttributeError( | |
"cannot delete attribute %r on <aenum %r>" % (self.name, self.clsname) | |
) | |
elif self.name in instance.__dict__: | |
del instance.__dict__[self.name] | |
else: | |
raise AttributeError( | |
"no attribute %r on <aenum %r> member" % (self.name, self.clsname) | |
) | |
else: | |
return self.fdel(instance) | |
def __set_name__(self, ownerclass, name): | |
self.name = name | |
self.clsname = ownerclass.__name__ | |
self.ownerclass = ownerclass | |
_RouteClassAttributeToGetattr = property | |
if DynamicClassAttribute is None: | |
DynamicClassAttribute = property | |
# deprecated | |
enum_property = property | |
# more helpers | |
class SentinelType(type): | |
def __repr__(cls): | |
return '<%s>' % cls.__name__ | |
Sentinel = SentinelType('Sentinel', (object, ), {}) | |
def _power_of_two(value): | |
if value < 1: | |
return False | |
return value == 2 ** _high_bit(value) | |
def bits(num): | |
if num in (0, 1): | |
return str(num) | |
negative = False | |
if num < 0: | |
negative = True | |
num = ~num | |
result = bits(num>>1) + str(num&1) | |
if negative: | |
result = '1' + ''.join(['10'[d=='1'] for d in result]) | |
return result | |
def bit_count(num): | |
""" | |
return number of set bits | |
Counting bits set, Brian Kernighan's way* | |
unsigned int v; // count the number of bits set in v | |
unsigned int c; // c accumulates the total bits set in v | |
for (c = 0; v; c++) | |
{ v &= v - 1; } //clear the least significant bit set | |
This method goes through as many iterations as there are set bits. So if we | |
have a 32-bit word with only the high bit set, then it will only go once | |
through the loop. | |
* The C Programming Language 2nd Ed., Kernighan & Ritchie, 1988. | |
This works because each subtraction "borrows" from the lowest 1-bit. For example: | |
loop pass 1 loop pass 2 | |
----------- ----------- | |
101000 100000 | |
- 1 - 1 | |
= 100111 = 011111 | |
& 101000 & 100000 | |
= 100000 = 0 | |
It is an excellent technique for Python, since the size of the integer need not | |
be determined beforehand. | |
""" | |
count = 0 | |
while(num): | |
num &= num - 1 | |
count += 1 | |
return(count) | |
def bit_len(num): | |
length = 0 | |
while num: | |
length += 1 | |
num >>= 1 | |
return length | |
def is_single_bit(num): | |
""" | |
True if only one bit set in num (should be an int) | |
""" | |
return (num != 0) and (num & (num - 1)) == 0 | |
def _check_auto_args(method): | |
"""check if new generate method supports *args and **kwds""" | |
if isinstance(method, staticmethod): | |
method = method.__get__(type) | |
method = getattr(method, 'im_func', method) | |
args, varargs, keywords, defaults = getargspec(method) | |
return varargs is not None and keywords is not None | |
def enumsort(things): | |
""" | |
sorts things by value if all same type; otherwise by name | |
""" | |
if not things: | |
return things | |
sort_type = type(things[0]) | |
if not issubclass(sort_type, tuple): | |
# direct sort or type error | |
if not all((type(v) is sort_type) for v in things[1:]): | |
raise TypeError('cannot sort items of different types') | |
return sorted(things) | |
else: | |
# expecting list of (name, value) tuples | |
sort_type = type(things[0][1]) | |
try: | |
if all((type(v[1]) is sort_type) for v in things[1:]): | |
return sorted(things, key=lambda i: i[1]) | |
else: | |
raise TypeError('try name sort instead') | |
except TypeError: | |
return sorted(things, key=lambda i: i[0]) | |
# Enum | |
# _init_ and value and AddValue | |
# ----------------------------- | |
# by default, when defining a member everything after the = is "the value", everything is | |
# passed to __new__, everything is passed to __init__ | |
# | |
# if _init_ is present then | |
# if `value` is not in _init_, everything is "the value", defaults apply | |
# if `value` is in _init_, only the first thing after the = is the value, and the rest will | |
# be passed to __init__ | |
# if fewer values are present for member assignment than _init_ calls for, _generate_next_value_ | |
# will be called in an attempt to generate them | |
# | |
# if AddValue is present then | |
# _generate_next_value_ is always called, and any generated values are prepended to provided | |
# values (custom _gnv_s can change that) | |
# default _init_ rules apply | |
# Constants used in Enum | |
class EnumConstants(NamedConstant): | |
AddValue = constant('addvalue', 'prepends value(s) from _generate_next_value_ to each member') | |
MagicValue = constant('magicvalue', 'calls _generate_next_value_ when no arguments are given') | |
MultiValue = constant('multivalue', 'each member can have several values') | |
NoAlias = constant('noalias', 'duplicate valued members are distinct, not aliased') | |
Unique = constant('unique', 'duplicate valued members are not allowed') | |
def __repr__(self): | |
return self._name_ | |
# Dummy value for Enum as EnumType explicity checks for it, but of course until | |
# EnumType finishes running the first time the Enum class doesn't exist. This | |
# is also why there are checks in EnumType like `if Enum is not None`. | |
# | |
# Ditto for Flag. | |
Enum = ReprEnum = IntEnum = StrEnum = Flag = IntFlag = EJECT = KEEP = None | |
class enum(object): | |
""" | |
Helper class to track args, kwds. | |
""" | |
def __init__(self, *args, **kwds): | |
self._args = args | |
self._kwds = dict(kwds.items()) | |
self._hash = hash(args) | |
self.name = None | |
def args(self): | |
return self._args | |
def kwds(self): | |
return self._kwds.copy() | |
def __hash__(self): | |
return self._hash | |
def __eq__(self, other): | |
if not isinstance(other, self.__class__): | |
return NotImplemented | |
return self.args == other.args and self.kwds == other.kwds | |
def __ne__(self, other): | |
if not isinstance(other, self.__class__): | |
return NotImplemented | |
return self.args != other.args or self.kwds != other.kwds | |
def __repr__(self): | |
final = [] | |
args = ', '.join(['%r' % (a, ) for a in self.args]) | |
if args: | |
final.append(args) | |
kwds = ', '.join([('%s=%r') % (k, v) for k, v in enumsort(list(self.kwds.items()))]) | |
if kwds: | |
final.append(kwds) | |
return '%s(%s)' % (self.__class__.__name__, ', '.join(final)) | |
_auto_null = SentinelType('no_value', (object, ), {}) | |
class auto(enum): | |
""" | |
Instances are replaced with an appropriate value in Enum class suites. | |
""" | |
enum_member = _auto_null | |
_value = _auto_null | |
_operations = [] | |
def __and__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_and_, (self, other))) | |
return new_auto | |
def __rand__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_and_, (other, self))) | |
return new_auto | |
def __invert__(self): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_inv_, (self,))) | |
return new_auto | |
def __or__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_or_, (self, other))) | |
return new_auto | |
def __ror__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_or_, (other, self))) | |
return new_auto | |
def __xor__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_xor_, (self, other))) | |
return new_auto | |
def __rxor__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_xor_, (other, self))) | |
return new_auto | |
def __abs__(self): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_abs_, (self, ))) | |
return new_auto | |
def __add__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_add_, (self, other))) | |
return new_auto | |
def __radd__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_add_, (other, self))) | |
return new_auto | |
def __neg__(self): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_neg_, (self, ))) | |
return new_auto | |
def __pos__(self): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_pos_, (self, ))) | |
return new_auto | |
if PY2: | |
def __div__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_div_, (self, other))) | |
return new_auto | |
def __rdiv__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_div_, (other, self))) | |
return new_auto | |
def __floordiv__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_floordiv_, (self, other))) | |
return new_auto | |
def __rfloordiv__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_floordiv_, (other, self))) | |
return new_auto | |
def __truediv__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_truediv_, (self, other))) | |
return new_auto | |
def __rtruediv__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_truediv_, (other, self))) | |
return new_auto | |
def __lshift__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_lshift_, (self, other))) | |
return new_auto | |
def __rlshift__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_lshift_, (other, self))) | |
return new_auto | |
def __rshift__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_rshift_, (self, other))) | |
return new_auto | |
def __rrshift__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_rshift_, (other, self))) | |
return new_auto | |
def __mod__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_mod_, (self, other))) | |
return new_auto | |
def __rmod__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_mod_, (other, self))) | |
return new_auto | |
def __mul__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_mul_, (self, other))) | |
return new_auto | |
def __rmul__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_mul_, (other, self))) | |
return new_auto | |
def __pow__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_pow_, (self, other))) | |
return new_auto | |
def __rpow__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_pow_, (other, self))) | |
return new_auto | |
def __sub__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_sub_, (self, other))) | |
return new_auto | |
def __rsub__(self, other): | |
new_auto = self.__class__() | |
new_auto._operations = self._operations[:] | |
new_auto._operations.append((_sub_, (other, self))) | |
return new_auto | |
def __repr__(self): | |
if self._operations: | |
return 'auto(...)' | |
else: | |
return 'auto(%r, *%r, **%r)' % (self._value, self._args, self._kwds) | |
def value(self): | |
if self._value is not _auto_null and self._operations: | |
raise TypeError('auto() object out of sync') | |
elif self._value is _auto_null and not self._operations: | |
return self._value | |
elif self._value is not _auto_null: | |
return self._value | |
else: | |
return self._resolve() | |
def value(self, value): | |
if self._operations: | |
value = self._resolve(value) | |
self._value = value | |
def _resolve(self, base_value=None): | |
cls = self.__class__ | |
for op, params in self._operations: | |
values = [] | |
for param in params: | |
if isinstance(param, cls): | |
if param.value is _auto_null: | |
if base_value is None: | |
return _auto_null | |
else: | |
values.append(base_value) | |
else: | |
values.append(param.value) | |
else: | |
values.append(param) | |
value = op(*values) | |
self._operations[:] = [] | |
self._value = value | |
return value | |
from . import _common | |
_common.property = property | |
_common.auto = auto | |
del _common | |
class _EnumArgSpec(NamedTuple): | |
args = 0, 'all args except *args and **kwds' | |
varargs = 1, 'the name of the *args variable' | |
keywords = 2, 'the name of the **kwds variable' | |
defaults = 3, 'any default values' | |
required = 4, 'number of required values (no default available)' | |
def __new__(cls, _new_func): | |
argspec = getargspec(_new_func) | |
args, varargs, keywords, defaults = argspec | |
if defaults: | |
reqs = args[1:-len(defaults)] | |
else: | |
reqs = args[1:] | |
return tuple.__new__(_EnumArgSpec, (args, varargs, keywords, defaults, reqs)) | |
class _proto_member: | |
""" | |
intermediate step for enum members between class execution and final creation | |
""" | |
def __init__(self, value): | |
self.value = value | |
def __set_name__(self, enum_class, member_name): | |
""" | |
convert each quasi-member into an instance of the new enum class | |
""" | |
# first step: remove ourself from enum_class | |
delattr(enum_class, member_name) | |
# second step: create member based on enum_class | |
value = self.value | |
kwds = {} | |
args = () | |
init_args = () | |
extra_mv_args = () | |
multivalue = None | |
if isinstance(value, tuple) and value and isinstance(value[0], auto): | |
multivalue = value | |
value = value[0] | |
if isinstance(value, auto) and value.value is _auto_null: | |
args = value.args | |
kwds = value.kwds | |
elif isinstance(value, auto): | |
kwds = value.kwds | |
args = (value.value, ) + value.args | |
value = value.value | |
elif isinstance(value, enum): | |
args = value.args | |
kwds = value.kwds | |
elif isinstance(value, Member): | |
value = value.value | |
args = (value, ) | |
elif not isinstance(value, tuple): | |
args = (value, ) | |
else: | |
args = value | |
if multivalue is not None: | |
value = (value, ) + multivalue[1:] | |
kwds = {} | |
args = value | |
del multivalue | |
# possibilities | |
# | |
# - no init, multivalue -> __new__[0], __init__(*[:]), extra=[1:] | |
# - init w/o value, multivalue -> __new__[0], __init__(*[:]), extra=[1:] | |
# | |
# - init w/value, multivalue -> __new__[0], __init__(*[1:]), extra=[1:] | |
# | |
# - init w/value, no multivalue -> __new__[0], __init__(*[1:]), extra=[] | |
# | |
# - init w/o value, no multivalue -> __new__[:], __init__(*[:]), extra=[] | |
# - no init, no multivalue -> __new__[:], __init__(*[:]), extra=[] | |
if enum_class._multivalue_ or 'value' in enum_class._creating_init_: | |
if enum_class._multivalue_: | |
# when multivalue is True, creating_init can be anything | |
mv_arg = args[0] | |
extra_mv_args = args[1:] | |
if 'value' in enum_class._creating_init_: | |
init_args = args[1:] | |
else: | |
init_args = args | |
args = args[0:1] | |
value = args[0] | |
else: | |
# 'value' is definitely in creating_init | |
if enum_class._auto_init_ and enum_class._new_args_: | |
# we have a custom __new__ and an auto __init__ | |
# divvy up according to number of params in each | |
init_args = args[-len(enum_class._creating_init_)+1:] | |
if not enum_class._auto_args_: | |
args = args[:len(enum_class._new_args_.args)] | |
value = args[0] | |
elif enum_class._auto_init_: | |
# don't pass in value | |
init_args = args[1:] | |
args = args[0:1] | |
value = args[0] | |
elif enum_class._new_args_: | |
# do not modify args | |
value = args[0] | |
else: | |
# keep all args for user-defined __init__ | |
# keep value as-is | |
init_args = args | |
else: | |
# either no creating_init, or it doesn't have 'value' | |
init_args = args | |
if enum_class._member_type_ is tuple: # special case for tuple enums | |
args = (args, ) # wrap it one more time | |
if not enum_class._use_args_: | |
enum_member = enum_class._new_member_(enum_class) | |
else: | |
enum_member = enum_class._new_member_(enum_class, *args, **kwds) | |
if not hasattr(enum_member, '_value_'): | |
if enum_class._member_type_ is object: | |
enum_member._value_ = value | |
else: | |
try: | |
enum_member._value_ = enum_class._member_type_(*args, **kwds) | |
except Exception as exc: | |
te = TypeError('_value_ not set in __new__, unable to create it') | |
te.__cause__ = exc | |
raise te | |
value = enum_member._value_ | |
enum_member._name_ = member_name | |
enum_member.__objclass__ = enum_class | |
enum_member.__init__(*init_args, **kwds) | |
enum_member._sort_order_ = len(enum_class._member_names_) | |
if Flag is not None and issubclass(enum_class, Flag): | |
enum_class._flag_mask_ |= value | |
if is_single_bit(value): | |
enum_class._singles_mask_ |= value | |
enum_class._all_bits_ = 2 ** ((enum_class._flag_mask_).bit_length()) - 1 | |
# If another member with the same value was already defined, the | |
# new member becomes an alias to the existing one. | |
if enum_class._noalias_: | |
# unless NoAlias was specified | |
enum_class._member_names_.append(member_name) | |
else: | |
nonunique = defaultdict(list) | |
try: | |
try: | |
# try to do a fast lookup to avoid the quadratic loop | |
enum_member = enum_class._value2member_map_[value] | |
if enum_class._unique_: | |
nonunique[enum_member.name].append(member_name) | |
except TypeError: | |
# unhashable members are stored elsewhere | |
for unhashable_value, canonical_member in enum_class._value2member_seq_: | |
name = canonical_member.name | |
if unhashable_value == enum_member._value_: | |
if enum_class._unique_: | |
nonunique[name].append(member_name) | |
enum_member = canonical_member | |
break | |
else: | |
raise KeyError | |
except KeyError: | |
# this could still be an alias if the value is multi-bit and the | |
# class is a flag class | |
if ( | |
Flag is None | |
or not issubclass(enum_class, Flag) | |
): | |
# no other instances found, record this member in _member_names_ | |
enum_class._member_names_.append(member_name) | |
elif ( | |
Flag is not None | |
and issubclass(enum_class, Flag) | |
and is_single_bit(value) | |
): | |
# no other instances found, record this member in _member_names_ | |
enum_class._member_names_.append(member_name) | |
if nonunique: | |
# duplicates not allowed if Unique specified | |
message = [] | |
for name, aliases in nonunique.items(): | |
bad_aliases = ','.join(aliases) | |
message.append('%s --> %s [%r]' % (name, bad_aliases, enum_class[name].value)) | |
raise ValueError( | |
'%s: duplicate names found: %s' % | |
(enum_class.__name__, '; '.join(message)) | |
) | |
# if self.value is an `auto()`, replace the value attribute with the new enum member | |
if isinstance(self.value, auto): | |
self.value.enum_member = enum_member | |
# if necessary, get redirect in place and then add it to _member_map_ | |
found_descriptor = None | |
descriptor_type = None | |
class_type = None | |
for base in enum_class.__mro__[1:]: | |
attr = base.__dict__.get(member_name) | |
if attr is not None: | |
if isinstance(attr, (property, DynamicClassAttribute)): | |
found_descriptor = attr | |
class_type = base | |
descriptor_type = 'enum' | |
break | |
elif is_descriptor(attr): | |
found_descriptor = attr | |
descriptor_type = descriptor_type or 'desc' | |
class_type = class_type or base | |
continue | |
else: | |
descriptor_type = 'attr' | |
class_type = base | |
if found_descriptor: | |
redirect = property() | |
redirect.member = enum_member | |
redirect.__set_name__(enum_class, member_name) | |
if descriptor_type in ('enum','desc'): | |
# earlier descriptor found; copy fget, fset, fdel to this one. | |
redirect.fget = getattr(found_descriptor, 'fget', None) | |
redirect._get = getattr(found_descriptor, '__get__', None) | |
redirect.fset = getattr(found_descriptor, 'fset', None) | |
redirect._set = getattr(found_descriptor, '__set__', None) | |
redirect.fdel = getattr(found_descriptor, 'fdel', None) | |
redirect._del = getattr(found_descriptor, '__delete__', None) | |
redirect._attr_type = descriptor_type | |
redirect._cls_type = class_type | |
setattr(enum_class, member_name, redirect) | |
else: | |
setattr(enum_class, member_name, enum_member) | |
# now add to _member_map_ (even aliases) | |
enum_class._member_map_[member_name] = enum_member | |
# | |
# process (possible) MultiValues | |
values = (value, ) + extra_mv_args | |
if enum_class._multivalue_ and mv_arg not in values: | |
values += (mv_arg, ) | |
enum_member._values_ = values | |
for value in values: | |
# first check if value has already been used | |
if enum_class._multivalue_ and ( | |
value in enum_class._value2member_map_ | |
or any(v == value for (v, m) in enum_class._value2member_seq_) | |
): | |
raise ValueError('%r has already been used' % (value, )) | |
try: | |
# This may fail if value is not hashable. We can't add the value | |
# to the map, and by-value lookups for this value will be | |
# linear. | |
if enum_class._noalias_: | |
raise TypeError('cannot use dict to store value') | |
enum_class._value2member_map_[value] = enum_member | |
except TypeError: | |
enum_class._value2member_seq_ += ((value, enum_member), ) | |
class EnumDict(dict): | |
"""Track enum member order and ensure member names are not reused. | |
EnumType will use the names found in self._member_names as the | |
enumeration member names. | |
""" | |
def __init__(self, cls_name, settings, start, constructor_init, constructor_start, constructor_boundary): | |
super(EnumDict, self).__init__() | |
self._cls_name = cls_name | |
self._constructor_init = constructor_init | |
self._constructor_start = constructor_start | |
self._constructor_boundary = constructor_boundary | |
self._generate_next_value = None | |
self._member_names = [] | |
self._member_names_set = set() | |
self._settings = settings | |
self._addvalue = addvalue = AddValue in settings | |
self._magicvalue = MagicValue in settings | |
self._multivalue = MultiValue in settings | |
if self._addvalue and self._magicvalue: | |
raise TypeError('%r: AddValue and MagicValue are mutually exclusive' % cls_name) | |
if self._multivalue and self._magicvalue: | |
raise TypeError('%r: MultiValue and MagicValue are mutually exclusive' % cls_name) | |
self._start = start | |
self._addvalue_value = start | |
self._new_args = () | |
self._auto_args = False | |
# when the magic turns off | |
self._locked = MagicValue not in settings | |
# if init fields are specified | |
self._init = [] | |
# list of temporary names | |
self._ignore = [] | |
if self._magicvalue: | |
self._ignore = ['property', 'staticmethod', 'classmethod'] | |
self._ignore_init_done = False | |
# if _sunder_ values can be changed via the class body | |
self._allow_init = True | |
self._last_values = [] | |
self._auto_called = False | |
def __getitem__(self, key): | |
if key == self._cls_name and self._cls_name not in self: | |
return enum | |
elif ( | |
self._locked | |
or key in self | |
or key in self._ignore | |
or is_sunder(key) | |
or is_dunder(key) | |
): | |
return super(EnumDict, self).__getitem__(key) | |
elif self._magicvalue: | |
value = self._generate_next_value(key, self._start, len(self._member_names), self._last_values[:]) | |
self.__setitem__(key, value) | |
return value | |
else: | |
raise Exception('Magic is not set -- why am I here?') | |
def __setitem__(self, key, value): | |
"""Changes anything not sundured, dundered, nor a descriptor. | |
If an enum member name is used twice, an error is raised; duplicate | |
values are not checked for. | |
Single underscore (sunder) names are reserved. | |
""" | |
# Flag classes that have MagicValue and __new__ will get a generated _gnv_ | |
# | |
# if auto() is used in a tuple, auto_store becomes False | |
auto_store = True | |
if is_internal_class(self._cls_name, value): | |
pass | |
elif is_private_name(self._cls_name, key): | |
pass | |
elif is_sunder(key): | |
if key not in ( | |
'_init_', '_settings_', '_order_', '_ignore_', '_start_', | |
'_create_pseudo_member_', '_create_pseudo_member_values_', | |
'_generate_next_value_', '_boundary_', '_numeric_repr_', | |
'_missing_', '_missing_value_', '_missing_name_', | |
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', | |
): | |
raise ValueError('%r: _sunder_ names, such as %r, are reserved for future Enum use' | |
% (self._cls_name, key) | |
) | |
elif not self._allow_init and key not in ( | |
'create_pseudo_member_', '_missing_', '_missing_value_', '_missing_name_', | |
): | |
# sunder is used during creation, must be specified first | |
raise ValueError('%r: cannot set %r after init phase' % (self._cls_name, key)) | |
elif key == '_ignore_': | |
if self._ignore_init_done: | |
raise TypeError('%r: ignore can only be specified once' % self._cls_name) | |
if isinstance(value, basestring): | |
value = value.split() | |
else: | |
value = list(value) | |
self._ignore = value | |
already = set(value) & self._member_names_set | |
if already: | |
raise ValueError('%r: _ignore_ cannot specify already set names %s' % ( | |
self._cls_name, | |
', '.join(repr(a) for a in already) | |
)) | |
self._ignore_init_done = True | |
elif key == '_boundary_': | |
if self._constructor_boundary: | |
raise TypeError('%r: boundary specified in constructor and class body' % self._cls_name) | |
elif key == '_start_': | |
if self._constructor_start: | |
raise TypeError('%r: start specified in constructor and class body' % self._cls_name) | |
self._start = value | |
elif key == '_settings_': | |
if not isinstance(value, (set, tuple)): | |
value = (value, ) | |
if not isinstance(value, set): | |
value = set(value) | |
self._settings |= value | |
if NoAlias in value and Unique in value: | |
raise TypeError('%r: NoAlias and Unique are mutually exclusive' % self._cls_name) | |
elif MultiValue in value and NoAlias in value: | |
raise TypeError('cannot specify both MultiValue and NoAlias' % self._cls_name) | |
allowed_settings = dict.fromkeys(['addvalue', 'magicvalue', 'noalias', 'unique', 'multivalue']) | |
for arg in self._settings: | |
if arg not in allowed_settings: | |
raise TypeError('%r: unknown qualifier %r (from %r)' % (self._cls_name, arg, value)) | |
allowed_settings[arg] = True | |
self._multivalue = allowed_settings['multivalue'] | |
self._addvalue = allowed_settings['addvalue'] | |
self._magicvalue = allowed_settings['magicvalue'] | |
self._locked = not self._magicvalue | |
if self._magicvalue and not self._ignore_init_done: | |
self._ignore = ['property', 'classmethod', 'staticmethod'] | |
if self._addvalue and self._init and 'value' not in self._init: | |
self._init.insert(0, 'value') | |
value = tuple(self._settings) | |
elif key == '_init_': | |
if self._constructor_init: | |
raise TypeError('%r: init specified in constructor and in class body' % self._cls_name) | |
_init_ = value | |
if isinstance(_init_, basestring): | |
_init_ = _init_.replace(',',' ').split() | |
if self._addvalue and 'value' not in self._init: | |
self._init.insert(0, 'value') | |
if self._magicvalue: | |
raise TypeError("%r: _init_ and MagicValue are mutually exclusive" % self._cls_name) | |
self._init = _init_ | |
value = _init_ | |
elif key == '_generate_next_value_': | |
# check if members already defined as auto() | |
if self._auto_called: | |
raise TypeError("_generate_next_value_ must be defined before members") | |
gnv = value | |
if value is not None: | |
if isinstance(value, staticmethod): | |
gnv = value.__func__ | |
elif isinstance(value, classmethod): | |
raise TypeError('%r: _generate_next_value must be a staticmethod, not a classmethod' % self._cls_name) | |
else: | |
gnv = value | |
value = staticmethod(value) | |
self._auto_args = _check_auto_args(value) | |
setattr(self, '_generate_next_value', gnv) | |
elif is_dunder(key): | |
if key == '__order__': | |
key = '_order_' | |
if not self._allow_init: | |
# _order_ is used during creation, must be specified first | |
raise ValueError('%r: cannot set %r after init phase' % (self._cls_name, key)) | |
elif key == '__new__': # and self._new_to_init: | |
if isinstance(value, staticmethod): | |
value = value.__func__ | |
self._new_args = _EnumArgSpec(value) | |
elif key == '__init_subclass__': | |
if not isinstance(value, classmethod): | |
value = classmethod(value) | |
if is_descriptor(value): | |
self._locked = True | |
elif key in self._member_names_set: | |
# descriptor overwriting an enum? | |
raise TypeError('%r: attempt to reuse name: %r' % (self._cls_name, key)) | |
elif key in self._ignore: | |
pass | |
elif not is_descriptor(value): | |
self._allow_init = False | |
if key in self: | |
# enum overwriting a descriptor? | |
raise TypeError('%r: %s already defined as %r' % (self._cls_name, key, self[key])) | |
if type(value) is enum: | |
value.name = key | |
if self._addvalue: | |
raise TypeError('%r: enum() and AddValue are incompatible' % self._cls_name) | |
elif self._addvalue and not self._multivalue: | |
# generate a value | |
value = self._gnv(key, value) | |
elif self._multivalue: | |
# make sure it's a tuple | |
if not type(value) is tuple: | |
value = (value, ) | |
if isinstance(value[0], auto): | |
value = (self._convert_auto(key, value[0]), ) + value[1:] | |
if self._addvalue: | |
value = self._gnv(key, value) | |
elif isinstance(value, auto): | |
value = self._convert_auto(key, value) | |
elif type(value) is tuple and any(isinstance(v, auto) for v in value): | |
# insist on an actual tuple, no subclasses, in keeping with only supporting | |
# top-level auto() usage (not contained in any other data structure) | |
auto_valued = [] | |
for v in value: | |
if isinstance(v, auto): | |
auto_store = False | |
v = self._convert_auto(key, v) | |
v = v.value | |
self._last_values.append(v) | |
auto_valued.append(v) | |
value = tuple(auto_valued) | |
elif not isinstance(value, auto): | |
# call generate maybe if | |
# - init is specified; or | |
# - __new__ is specified; | |
# and either of them call for more values than are present | |
new_args = () or self._new_args and self._new_args.required | |
target_len = len(self._init or new_args) | |
if isinstance(value, tuple): | |
source_len = len(value) | |
else: | |
source_len = 1 | |
multi_args = len(self._init) > 1 or new_args | |
if source_len < target_len : | |
value = self._gnv(key, value) | |
else: | |
pass | |
if self._init: | |
if isinstance(value, auto): | |
test_value = value.args | |
elif not isinstance(value, tuple): | |
test_value = (value, ) | |
else: | |
test_value = value | |
if len(self._init) != len(test_value): | |
raise TypeError( | |
'%s.%s: number of fields provided do not match init [%r != %r]' | |
% (self._cls_name, key, self._init, test_value) | |
) | |
self._member_names.append(key) | |
self._member_names_set.add(key) | |
else: | |
# not a new member, turn off the autoassign magic | |
self._locked = True | |
self._allow_init = False | |
if not (is_sunder(key) or is_dunder(key) or is_private_name(self._cls_name, key) or is_descriptor(value)): | |
if not auto_store: | |
# reset for next pass | |
auto_store = True | |
elif isinstance(value, auto): | |
self._last_values.append(value.value) | |
elif type(value) is tuple: | |
if value: | |
if isinstance(value[0], auto): | |
self._last_values.append(value[0].value) | |
else: | |
self._last_values.append(value[0]) | |
else: | |
self._last_values.append(value) | |
super(EnumDict, self).__setitem__(key, value) | |
def _convert_auto(self, key, value): | |
# if auto.args or auto.kwds, compare to _init_ and __new__ -- if lacking, call gnv | |
# if not auto.args|kwds but auto.value is _auto_null -- call gnv | |
if value.args or value.kwds or value.value is _auto_null: | |
if value.args or value.kwds: | |
values = value.args | |
else: | |
values = () | |
new_args = () or self._new_args and self._new_args.required | |
target_len = len(self._init or new_args) or 1 | |
if type(values) is tuple: | |
source_len = len(values) | |
else: | |
source_len = 1 | |
multi_args = len(self._init) > 1 or new_args | |
if source_len < target_len : | |
values = self._gnv(key, values) | |
self._auto_called = True | |
if value.args: | |
value._args = values | |
else: | |
value.value = values | |
return value | |
def _gnv(self, key, value): | |
# generate a value | |
if self._auto_args: | |
if not isinstance(value, tuple): | |
value = (value, ) | |
value = self._generate_next_value(key, self._start, len(self._member_names), self._last_values[:], *value) | |
else: | |
value = self._generate_next_value(key, self._start, len(self._member_names), self._last_values[:]) | |
if isinstance(value, tuple) and len(value) == 1: | |
value = value[0] | |
return value | |
no_arg = SentinelType('no_arg', (type, ), {}) | |
class EnumType(type): | |
"""Metaclass for Enum""" | |
def __prepare__(metacls, cls, bases, init=None, start=None, settings=(), boundary=None, **kwds): | |
metacls._check_for_existing_members_(cls, bases) | |
if Flag is None and cls == 'Flag': | |
initial_flag = True | |
else: | |
initial_flag = False | |
# settings are a combination of current and all past settings | |
constructor_init = init is not None | |
constructor_start = start is not None | |
constructor_boundary = boundary is not None | |
if not isinstance(settings, tuple): | |
settings = settings, | |
settings = set(settings) | |
generate = None | |
order = None | |
# inherit previous flags | |
member_type, first_enum = metacls._get_mixins_(cls, bases) | |
if first_enum is not None: | |
generate = getattr(first_enum, '_generate_next_value_', None) | |
generate = getattr(generate, 'im_func', generate) | |
settings |= metacls._get_settings_(bases) | |
init = init or first_enum._auto_init_[:] | |
order = first_enum._order_function_ | |
if start is None: | |
start = first_enum._start_ | |
else: | |
# first time through -- creating Enum itself | |
start = 1 | |
# check for custom settings | |
if AddValue in settings and init and 'value' not in init: | |
if isinstance(init, list): | |
init.insert(0, 'value') | |
else: | |
init = 'value ' + init | |
if NoAlias in settings and Unique in settings: | |
raise TypeError('%r: NoAlias and Unique are mutually exclusive' % cls) | |
if MultiValue in settings and NoAlias in settings: | |
raise TypeError('%r: MultiValue and NoAlias are mutually exclusive' % cls) | |
allowed_settings = dict.fromkeys(['addvalue', 'magicvalue', 'noalias', 'unique', 'multivalue']) | |
for arg in settings: | |
if arg not in allowed_settings: | |
raise TypeError('%r: unknown qualifier %r' % (cls, arg)) | |
enum_dict = EnumDict(cls_name=cls, settings=settings, start=start, constructor_init=constructor_init, constructor_start=constructor_start, constructor_boundary=constructor_boundary) | |
enum_dict._member_type = member_type | |
enum_dict._base_type = ('enum', 'flag')[ | |
Flag is None and cls == 'Flag' | |
or | |
Flag is not None and any(issubclass(b, Flag) for b in bases) | |
] | |
if Flag is not None and any(b is Flag for b in bases) and member_type not in (baseinteger + (object, )): | |
if Flag in bases: | |
# when a non-int data type is mixed in with Flag, we end up | |
# needing two values for two `__new__`s: | |
# - the integer value for the Flag itself; and | |
# - the mix-in value for the mix-in | |
# | |
# we provide a default `_generate_next_value_` to supply the int | |
# argument, and a default `__new__` to keep the two straight | |
def _generate_next_value_(name, start, count, values, *args, **kwds): | |
return (2 ** count, ) + args | |
enum_dict['_generate_next_value_'] = staticmethod(_generate_next_value_) | |
def __new__(cls, flag_value, type_value): | |
obj = member_type.__new__(cls, type_value) | |
obj._value_ = flag_value | |
return obj | |
enum_dict['__new__'] = __new__ | |
else: | |
try: | |
enum_dict._new_args = _EnumArgSpec(first_enum.__new_member__) | |
except TypeError: | |
pass | |
elif not initial_flag: | |
if hasattr(first_enum, '__new_member__'): | |
enum_dict._new_args = _EnumArgSpec(first_enum.__new_member__) | |
if generate: | |
enum_dict['_generate_next_value_'] = generate | |
enum_dict._inherited_gnv = True | |
if init is not None: | |
if isinstance(init, basestring): | |
init = init.replace(',',' ').split() | |
enum_dict._init = init | |
elif hasattr(first_enum, '__new_member__'): | |
enum_dict._new_args = _EnumArgSpec(first_enum.__new_member__) | |
if order is not None: | |
enum_dict['_order_'] = staticmethod(order) | |
return enum_dict | |
def __init__(cls, *args , **kwds): | |
pass | |
def __new__(metacls, cls, bases, clsdict, init=None, start=None, settings=(), boundary=None, **kwds): | |
# handle py2 case first | |
if type(clsdict) is not EnumDict: | |
# py2 and/or functional API gyrations | |
init = clsdict.pop('_init_', None) | |
start = clsdict.pop('_start_', None) | |
settings = clsdict.pop('_settings_', ()) | |
_order_ = clsdict.pop('_order_', clsdict.pop('__order__', None)) | |
_ignore_ = clsdict.pop('_ignore_', None) | |
_create_pseudo_member_ = clsdict.pop('_create_pseudo_member_', None) | |
_create_pseudo_member_values_ = clsdict.pop('_create_pseudo_member_values_', None) | |
_generate_next_value_ = clsdict.pop('_generate_next_value_', None) | |
_missing_ = clsdict.pop('_missing_', None) | |
_missing_value_ = clsdict.pop('_missing_value_', None) | |
_missing_name_ = clsdict.pop('_missing_name_', None) | |
_boundary_ = clsdict.pop('_boundary_', None) | |
_iter_member_ = clsdict.pop('_iter_member_', None) | |
_iter_member_by_value_ = clsdict.pop('_iter_member_by_value_', None) | |
_iter_member_by_def_ = clsdict.pop('_iter_member_by_def_', None) | |
__new__ = clsdict.pop('__new__', None) | |
__new__ = getattr(__new__, 'im_func', __new__) | |
__new__ = getattr(__new__, '__func__', __new__) | |
enum_members = dict([ | |
(k, v) for (k, v) in clsdict.items() | |
if not (is_sunder(k) or is_dunder(k) or is_private_name(cls, k) or is_descriptor(v)) | |
]) | |
original_dict = clsdict | |
clsdict = metacls.__prepare__(cls, bases, init=init, start=start) | |
if settings: | |
clsdict['_settings_'] = settings | |
init = init or clsdict._init | |
if _order_ is None: | |
_order_ = clsdict.get('_order_') | |
if _order_ is not None: | |
_order_ = _order_.__get__(cls) | |
if isinstance(original_dict, OrderedDict): | |
calced_order = original_dict | |
elif _order_ is None: | |
calced_order = [name for (name, value) in enumsort(list(enum_members.items()))] | |
elif isinstance(_order_, basestring): | |
calced_order = _order_ = _order_.replace(',', ' ').split() | |
elif callable(_order_): | |
if init: | |
if not isinstance(init, basestring): | |
init = ' '.join(init) | |
member = NamedTuple('member', init and 'name ' + init or ['name', 'value']) | |
calced_order = [] | |
for name, value in enum_members.items(): | |
if init: | |
if not isinstance(value, tuple): | |
value = (value, ) | |
name_value = (name, ) + value | |
else: | |
name_value = tuple((name, value)) | |
if member._defined_len_ != len(name_value): | |
raise TypeError('%d values expected (%s), %d received (%s)' % ( | |
member._defined_len_, | |
', '.join(member._fields_), | |
len(name_value), | |
', '.join([repr(v) for v in name_value]), | |
)) | |
calced_order.append(member(*name_value)) | |
calced_order = [m.name for m in sorted(calced_order, key=_order_)] | |
else: | |
calced_order = _order_ | |
for name in ( | |
'_missing_', '_missing_value_', '_missing_name_', | |
'_ignore_', '_create_pseudo_member_', '_create_pseudo_member_values_', | |
'_generate_next_value_', '_order_', '__new__', | |
'_missing_', '_missing_value_', '_missing_name_', | |
'_boundary_', | |
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', | |
): | |
attr = locals()[name] | |
if attr is not None: | |
clsdict[name] = attr | |
# now add members | |
for k in calced_order: | |
try: | |
clsdict[k] = original_dict[k] | |
except KeyError: | |
# this error will be handled when _order_ is checked | |
pass | |
for k, v in original_dict.items(): | |
if k not in calced_order: | |
clsdict[k] = v | |
del _order_, _ignore_, _create_pseudo_member_, _create_pseudo_member_values_, | |
del _generate_next_value_, _missing_, _missing_value_, _missing_name_ | |
# | |
# resume normal path | |
clsdict._locked = True | |
# | |
# check for illegal enum names (any others?) | |
member_names = clsdict._member_names | |
invalid_names = set(member_names) & set(['mro', '']) | |
if invalid_names: | |
raise ValueError('invalid enum member name(s): %s' % ( | |
', '.join(invalid_names), )) | |
_order_ = clsdict.pop('_order_', None) | |
if isinstance(_order_, basestring): | |
_order_ = _order_.replace(',',' ').split() | |
init = clsdict._init | |
start = clsdict._start | |
settings = clsdict._settings | |
creating_init = [] | |
new_args = clsdict._new_args | |
auto_args = clsdict._auto_args | |
auto_init = False | |
if init is not None: | |
auto_init = True | |
creating_init = init[:] | |
if 'value' in creating_init and creating_init[0] != 'value': | |
raise TypeError("'value', if specified, must be the first item in 'init'") | |
magicvalue = MagicValue in settings | |
multivalue = MultiValue in settings | |
noalias = NoAlias in settings | |
unique = Unique in settings | |
# an Enum class cannot be mixed with other types (int, float, etc.) if | |
# it has an inherited __new__ unless a new __new__ is defined (or | |
# the resulting class will fail). | |
# an Enum class is final once enumeration items have been defined; | |
# | |
# remove any keys listed in _ignore_ | |
clsdict.setdefault('_ignore_', []).append('_ignore_') | |
ignore = clsdict['_ignore_'] | |
for key in ignore: | |
clsdict.pop(key, None) | |
# | |
boundary = boundary or clsdict.pop('_boundary_', None) | |
_gnv = clsdict.get('_generate_next_value_') | |
if _gnv is not None and type(_gnv) is not staticmethod: | |
_gnv = staticmethod(_gnv) | |
# convert to regular dict | |
clsdict = dict(clsdict.items()) | |
if _gnv is not None: | |
clsdict['_generate_next_value_'] = _gnv | |
member_type, first_enum = metacls._get_mixins_(cls, bases) | |
# get the method to create enum members | |
__new__, save_new, new_uses_args = metacls._find_new_( | |
clsdict, | |
member_type, | |
first_enum, | |
) | |
clsdict['_new_member_'] = staticmethod(__new__) | |
clsdict['_use_args_'] = new_uses_args | |
# | |
# convert future enum members into temporary _proto_members | |
for name in member_names: | |
value = clsdict[name] | |
clsdict[name] = _proto_member(value) | |
# | |
# temp stuff | |
clsdict['_creating_init_'] = creating_init | |
clsdict['_multivalue_'] = multivalue | |
clsdict['_magicvalue_'] = magicvalue | |
clsdict['_noalias_'] = noalias | |
clsdict['_unique_'] = unique | |
# | |
# house-keeping structures | |
clsdict['_member_names_'] = [] | |
clsdict['_member_map_'] = OrderedDict() | |
clsdict['_member_type_'] = member_type | |
clsdict['_value2member_map_'] = {} | |
clsdict['_value2member_seq_'] = () | |
clsdict['_settings_'] = settings | |
clsdict['_start_'] = start | |
clsdict['_auto_init_'] = init | |
clsdict['_new_args_'] = new_args | |
clsdict['_auto_args_'] = auto_args | |
clsdict['_order_function_'] = None | |
# now set the __repr__ for the value | |
clsdict['_value_repr_'] = metacls._find_data_repr_(cls, bases) | |
# | |
# Flag structures (will be removed if final class is not a Flag | |
clsdict['_boundary_'] = ( | |
boundary | |
or getattr(first_enum, '_boundary_', None) | |
) | |
clsdict['_flag_mask_'] = 0 | |
clsdict['_singles_mask_'] = 0 | |
clsdict['_all_bits_'] = 0 | |
clsdict['_inverted_'] = None | |
# | |
# move skipped values out of the descriptor | |
for name, obj in clsdict.items(): | |
if isinstance(obj, nonmember): | |
clsdict[name] = obj.value | |
# | |
# If a custom type is mixed into the Enum, and it does not know how | |
# to pickle itself, pickle.dumps will succeed but pickle.loads will | |
# fail. Rather than have the error show up later and possibly far | |
# from the source, sabotage the pickle protocol for this class so | |
# that pickle.dumps also fails. | |
# | |
# However, if the new class implements its own __reduce_ex__, do not | |
# sabotage -- it's on them to make sure it works correctly. We use | |
# __reduce_ex__ instead of any of the others as it is preferred by | |
# pickle over __reduce__, and it handles all pickle protocols. | |
unpicklable = False | |
if '__reduce_ex__' not in clsdict: | |
if member_type is not object: | |
methods = ('__getnewargs_ex__', '__getnewargs__', | |
'__reduce_ex__', '__reduce__') | |
if not any(m in member_type.__dict__ for m in methods): | |
make_class_unpicklable(clsdict) | |
unpicklable = True | |
# | |
# create a default docstring if one has not been provided | |
if '__doc__' not in clsdict: | |
clsdict['__doc__'] = 'An enumeration.' | |
# | |
# create our new Enum type | |
try: | |
exc = None | |
enum_class = type.__new__(metacls, cls, bases, clsdict) | |
except RuntimeError as e: | |
# any exceptions raised by _proto_member (aka member.__new__) will get converted to | |
# a RuntimeError, so get that original exception back and raise | |
# it instead | |
exc = e.__cause__ or e | |
if exc is not None: | |
raise exc | |
# | |
# if Python 3.5 or ealier, implement the __set_name__ and | |
# __init_subclass__ protocols | |
if pyver < PY3_6: | |
for name in member_names: | |
enum_class.__dict__[name].__set_name__(enum_class, name) | |
for name, obj in enum_class.__dict__.items(): | |
if name in member_names: | |
continue | |
if hasattr(obj, '__set_name__'): | |
obj.__set_name__(enum_class, name) | |
if Enum is not None and hasattr(enum_class, '__init_subclass__'): | |
super(enum_class, enum_class).__init_subclass__() | |
# | |
# double check that repr and friends are not the mixin's or various | |
# things break (such as pickle) | |
# | |
# Also, special handling for ReprEnum | |
if ReprEnum is not None and ReprEnum in bases: | |
if member_type is object: | |
raise TypeError( | |
'ReprEnum subclasses must be mixed with a data type (i.e.' | |
' int, str, float, etc.)' | |
) | |
if '__format__' not in clsdict: | |
enum_class.__format__ = member_type.__format__ | |
clsdict['__format__'] = enum_class.__format__ | |
if '__str__' not in clsdict: | |
method = member_type.__str__ | |
if method is object.__str__: | |
# if member_type does not define __str__, object.__str__ will use | |
# its __repr__ instead, so we'll also use its __repr__ | |
method = member_type.__repr__ | |
enum_class.__str__ = method | |
clsdict['__str__'] = enum_class.__str__ | |
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): | |
if name in clsdict: | |
# class has defined/imported/copied the method | |
continue | |
class_method = getattr(enum_class, name) | |
obj_method = getattr(member_type, name, None) | |
enum_method = getattr(first_enum, name, None) | |
if obj_method is not None and obj_method == class_method: | |
if name == '__reduce_ex__' and unpicklable: | |
continue | |
setattr(enum_class, name, enum_method) | |
clsdict[name] = enum_method | |
# | |
# for Flag, add __or__, __and__, __xor__, and __invert__ | |
if Flag is not None and issubclass(enum_class, Flag): | |
for name in ( | |
'__or__', '__and__', '__xor__', | |
'__ror__', '__rand__', '__rxor__', | |
'__invert__' | |
): | |
if name not in clsdict: | |
setattr(enum_class, name, getattr(Flag, name)) | |
clsdict[name] = enum_method | |
# | |
# method resolution and int's are not playing nice | |
# Python's less than 2.6 use __cmp__ | |
if pyver < PY2_6: | |
# | |
if issubclass(enum_class, int): | |
setattr(enum_class, '__cmp__', getattr(int, '__cmp__')) | |
# | |
elif PY2: | |
# | |
if issubclass(enum_class, int): | |
for method in ( | |
'__le__', | |
'__lt__', | |
'__gt__', | |
'__ge__', | |
'__eq__', | |
'__ne__', | |
'__hash__', | |
): | |
setattr(enum_class, method, getattr(int, method)) | |
# | |
# replace any other __new__ with our own (as long as Enum is not None, | |
# anyway) -- again, this is to support pickle | |
if Enum is not None: | |
# if the user defined their own __new__, save it before it gets | |
# clobbered in case they subclass later | |
if save_new: | |
setattr(enum_class, '__new_member__', enum_class.__dict__['__new__']) | |
setattr(enum_class, '__new__', Enum.__dict__['__new__']) | |
# | |
# _order_ checking is spread out into three/four steps | |
# - ensure _order_ is a list, not a string nor a function | |
# - if enum_class is a Flag: | |
# - remove any non-single-bit flags from _order_ | |
# - remove any aliases from _order_ | |
# - check that _order_ and _member_names_ match | |
# | |
# _order_ step 1: ensure _order_ is a list | |
if _order_: | |
if isinstance(_order_, staticmethod): | |
_order_ = _order_.__func__ | |
if callable(_order_): | |
# save order for future subclasses | |
enum_class._order_function_ = staticmethod(_order_) | |
# create ordered list for comparison | |
_order_ = [m.name for m in sorted(enum_class, key=_order_)] | |
# | |
# remove Flag structures if final class is not a Flag | |
if ( | |
Flag is None and cls != 'Flag' | |
or Flag is not None and not issubclass(enum_class, Flag) | |
): | |
delattr(enum_class, '_boundary_') | |
delattr(enum_class, '_flag_mask_') | |
delattr(enum_class, '_singles_mask_') | |
delattr(enum_class, '_all_bits_') | |
delattr(enum_class, '_inverted_') | |
elif Flag is not None and issubclass(enum_class, Flag): | |
# set correct __iter__ | |
if [m._value_ for m in enum_class] != sorted([m._value_ for m in enum_class]): | |
enum_class._iter_member_ = enum_class._iter_member_by_def_ | |
if _order_: | |
# _order_ step 2: remove any items from _order_ that are not single-bit | |
_order_ = [ | |
o | |
for o in _order_ | |
if o not in enum_class._member_map_ or is_single_bit(enum_class[o]._value_) | |
] | |
# | |
# check for constants with auto() values | |
for k, v in enum_class.__dict__.items(): | |
if isinstance(v, constant) and isinstance(v.value, auto): | |
v.value = enum_class(v.value.value) | |
# | |
if _order_: | |
# _order_ step 3: remove aliases from _order_ | |
_order_ = [ | |
o | |
for o in _order_ | |
if ( | |
o not in enum_class._member_map_ | |
or | |
(o in enum_class._member_map_ and o in enum_class._member_names_) | |
)] | |
# _order_ step 4: verify that _order_ and _member_names_ match | |
if _order_ != enum_class._member_names_: | |
raise TypeError( | |
'member order does not match _order_:\n%r\n%r' | |
% (enum_class._member_names_, _order_) | |
) | |
return enum_class | |
def __bool__(cls): | |
""" | |
classes/types should always be True. | |
""" | |
return True | |
def __call__(cls, value=no_arg, names=None, module=None, qualname=None, type=None, start=1, boundary=None): | |
"""Either returns an existing member, or creates a new enum class. | |
This method is used both when an enum class is given a value to match | |
to an enumeration member (i.e. Color(3)) and for the functional API | |
(i.e. Color = Enum('Color', names='red green blue')). | |
When used for the functional API: `module`, if set, will be stored in | |
the new class' __module__ attribute; `type`, if set, will be mixed in | |
as the first base class. | |
Note: if `module` is not set this routine will attempt to discover the | |
calling module by walking the frame stack; if this is unsuccessful | |
the resulting class will not be pickleable. | |
""" | |
if names is None: # simple value lookup | |
return cls.__new__(cls, value) | |
# otherwise, functional API: we're creating a new Enum type | |
return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start, boundary=boundary) | |
def __contains__(cls, value): | |
"""Return True if `value` is in `cls`. | |
`value` is in `cls` if: | |
1) `value` is a member of `cls`, or | |
2) `value` is the value of one of the `cls`'s members. | |
""" | |
if isinstance(value, cls): | |
return True | |
try: | |
return value in cls._value2member_map_ | |
except TypeError: | |
return value in [v for v,m in cls._value2member_seq_] | |
def __delattr__(cls, attr): | |
# nicer error message when someone tries to delete an attribute | |
# (see issue19025). | |
if attr in cls._member_map_: | |
raise AttributeError( | |
"%s: cannot delete Enum member %r." % (cls.__name__, attr), | |
) | |
found_attr = get_attr_from_chain(cls, attr) | |
if isinstance(found_attr, constant): | |
raise AttributeError( | |
"%s: cannot delete constant %r" % (cls.__name__, attr), | |
) | |
elif isinstance(found_attr, property): | |
raise AttributeError( | |
"%s: cannot delete property %r" % (cls.__name__, attr), | |
) | |
type.__delattr__(cls, attr) | |
def __dir__(cls): | |
interesting = set(cls._member_names_ + [ | |
'__class__', '__contains__', '__doc__', '__getitem__', | |
'__iter__', '__len__', '__members__', '__module__', | |
'__name__', | |
]) | |
if cls._new_member_ is not object.__new__: | |
interesting.add('__new__') | |
if cls.__init_subclass__ is not Enum.__init_subclass__: | |
interesting.add('__init_subclass__') | |
if hasattr(object, '__qualname__'): | |
interesting.add('__qualname__') | |
for method in ('__init__', '__format__', '__repr__', '__str__'): | |
if getattr(cls, method) not in (getattr(Enum, method), getattr(Flag, method)): | |
interesting.add(method) | |
if cls._member_type_ is object: | |
return sorted(interesting) | |
else: | |
# return whatever mixed-in data type has | |
return sorted(set(dir(cls._member_type_)) | interesting) | |
def __members__(cls): | |
"""Returns a mapping of member name->value. | |
This mapping lists all enum members, including aliases. Note that this | |
is a copy of the internal mapping. | |
""" | |
return cls._member_map_.copy() | |
def __getitem__(cls, name): | |
try: | |
return cls._member_map_[name] | |
except KeyError: | |
exc = _sys.exc_info()[1] | |
if Flag is not None and issubclass(cls, Flag) and '|' in name: | |
try: | |
# may be an __or__ed name | |
result = cls(0) | |
for n in name.split('|'): | |
result |= cls[n] | |
return result | |
except KeyError: | |
raise exc | |
result = cls._missing_name_(name) | |
if isinstance(result, cls): | |
return result | |
else: | |
raise exc | |
def __iter__(cls): | |
return (cls._member_map_[name] for name in cls._member_names_) | |
def __reversed__(cls): | |
return (cls._member_map_[name] for name in reversed(cls._member_names_)) | |
def __len__(cls): | |
return len(cls._member_names_) | |
__nonzero__ = __bool__ | |
def __repr__(cls): | |
return "<aenum %r>" % (cls.__name__, ) | |
def __setattr__(cls, name, value): | |
"""Block attempts to reassign Enum members/constants. | |
A simple assignment to the class namespace only changes one of the | |
several possible ways to get an Enum member from the Enum class, | |
resulting in an inconsistent Enumeration. | |
""" | |
member_map = cls.__dict__.get('_member_map_', {}) | |
if name in member_map: | |
raise AttributeError( | |
'%s: cannot rebind member %r.' % (cls.__name__, name), | |
) | |
found_attr = get_attr_from_chain(cls, name) | |
if isinstance(found_attr, constant): | |
raise AttributeError( | |
"%s: cannot rebind constant %r" % (cls.__name__, name), | |
) | |
elif isinstance(found_attr, (bltin_property, property)) and not isinstance(value, property): | |
raise AttributeError( | |
"%s: cannot rebind property %r" % (cls.__name__, name), | |
) | |
type.__setattr__(cls, name, value) | |
def _convert(cls, *args, **kwds): | |
import warnings | |
warnings.warn("_convert is deprecated and will be removed, use" | |
" _convert_ instead.", DeprecationWarning, stacklevel=2) | |
return cls._convert_(*args, **kwds) | |
def _convert_(cls, name, module, filter, source=None, boundary=None, as_global=False): | |
""" | |
Create a new Enum subclass that replaces a collection of global constants | |
""" | |
# convert all constants from source (or module) that pass filter() to | |
# a new Enum called name, and export the enum and its members back to | |
# module; | |
# also, replace the __reduce_ex__ method so unpickling works in | |
# previous Python versions | |
module_globals = vars(_sys.modules[module]) | |
if source: | |
source = vars(source) | |
else: | |
source = module_globals | |
members = [(key, source[key]) for key in source.keys() if filter(key)] | |
try: | |
# sort by value, name | |
members.sort(key=lambda t: (t[1], t[0])) | |
except TypeError: | |
# unless some values aren't comparable, in which case sort by just name | |
members.sort(key=lambda t: t[0]) | |
cls = cls(name, members, module=module, boundary=boundary or KEEP) | |
cls.__reduce_ex__ = _reduce_ex_by_name | |
if as_global: | |
global_enum(cls) | |
else: | |
module_globals.update(cls.__members__) | |
module_globals[name] = cls | |
return cls | |
def _create_(cls, class_name, names, module=None, qualname=None, type=None, start=1, boundary=None): | |
"""Convenience method to create a new Enum class. | |
`names` can be: | |
* A string containing member names, separated either with spaces or | |
commas. Values are auto-numbered from 1. | |
* An iterable of member names. Values are auto-numbered from 1. | |
* An iterable of (member name, value) pairs. | |
* A mapping of member name -> value. | |
""" | |
if PY2: | |
# if class_name is unicode, attempt a conversion to ASCII | |
if isinstance(class_name, unicode): | |
try: | |
class_name = class_name.encode('ascii') | |
except UnicodeEncodeError: | |
raise TypeError('%r is not representable in ASCII' % (class_name, )) | |
metacls = cls.__class__ | |
if type is None: | |
bases = (cls, ) | |
else: | |
bases = (type, cls) | |
_, first_enum = cls._get_mixins_(class_name, bases) | |
generate = getattr(first_enum, '_generate_next_value_', None) | |
generate = getattr(generate, 'im_func', generate) | |
# special processing needed for names? | |
if isinstance(names, basestring): | |
names = names.replace(',', ' ').split() | |
if isinstance(names, (tuple, list)) and names and isinstance(names[0], basestring): | |
original_names, names = names, [] | |
last_values = [] | |
for count, name in enumerate(original_names): | |
value = generate(name, start, count, last_values[:]) | |
last_values.append(value) | |
names.append((name, value)) | |
# Here, names is either an iterable of (name, value) or a mapping. | |
item = None # in case names is empty | |
clsdict = None | |
for item in names: | |
if clsdict is None: | |
# first time initialization | |
if isinstance(item, basestring): | |
clsdict = {} | |
else: | |
# remember the order | |
clsdict = metacls.__prepare__(class_name, bases) | |
if isinstance(item, basestring): | |
member_name, member_value = item, names[item] | |
else: | |
member_name, member_value = item | |
clsdict[member_name] = member_value | |
if clsdict is None: | |
# in case names was empty | |
clsdict = metacls.__prepare__(class_name, bases) | |
enum_class = metacls.__new__(metacls, class_name, bases, clsdict, boundary=boundary) | |
# TODO: replace the frame hack if a blessed way to know the calling | |
# module is ever developed | |
if module is None: | |
try: | |
module = _sys._getframe(2).f_globals['__name__'] | |
except (AttributeError, KeyError): | |
pass | |
if module is None: | |
make_class_unpicklable(enum_class) | |
else: | |
enum_class.__module__ = module | |
if qualname is not None: | |
enum_class.__qualname__ = qualname | |
return enum_class | |
def _check_for_existing_members_(mcls, class_name, bases): | |
if Enum is None: | |
return | |
for chain in bases: | |
for base in chain.__mro__: | |
if issubclass(base, Enum) and base._member_names_: | |
raise TypeError( | |
"<aenum %r> cannot extend %r" | |
% (class_name, base) | |
) | |
def _get_mixins_(mcls, class_name, bases): | |
"""Returns the type for creating enum members, and the first inherited | |
enum class. | |
bases: the tuple of bases that was given to __new__ | |
""" | |
if not bases or Enum is None: | |
return object, Enum | |
mcls._check_for_existing_members_(class_name, bases) | |
# ensure final parent class is an Enum derivative, find any concrete | |
# data type, and check that Enum has no members | |
first_enum = bases[-1] | |
if first_enum in stdlib_enums: | |
first_enum = bases[-2] | |
if not issubclass(first_enum, Enum): | |
raise TypeError("new enumerations should be created as " | |
"`EnumName([mixin_type, ...] [data_type,] enum_type)`") | |
member_type = mcls._find_data_type_(class_name, bases) or object | |
if first_enum._member_names_: | |
raise TypeError("cannot extend enumerations via subclassing") | |
# | |
return member_type, first_enum | |
def _find_data_repr_(mcls, class_name, bases): | |
for chain in bases: | |
for base in chain.__mro__: | |
if base in ((object, ) + stdlib_enums): | |
continue | |
elif isinstance(base, EnumType): | |
# if we hit an Enum, use it's _value_repr_ | |
return base._value_repr_ | |
elif '__repr__' in base.__dict__: | |
# this is our data repr | |
# double-check if a dataclass with a default __repr__ | |
if ( | |
'__dataclass_fields__' in base.__dict__ | |
and '__dataclass_params__' in base.__dict__ | |
and base.__dict__['__dataclass_params__'].repr | |
): | |
return _dataclass_repr | |
else: | |
return base.__dict__['__repr__'] | |
return None | |
def _find_data_type_(mcls, class_name, bases): | |
data_types = set() | |
for chain in bases: | |
candidate = None | |
for base in chain.__mro__: | |
if base in ((object, ) + stdlib_enums): | |
continue | |
elif isinstance(base, EnumType): | |
if base._member_type_ is not object: | |
data_types.add(base._member_type_) | |
break | |
elif '__new__' in base.__dict__ or '__dataclass_fields__' in base.__dict__: | |
if isinstance(base, EnumType): | |
continue | |
elif StdlibFlag is not None and issubclass(base, StdlibFlag): | |
continue | |
data_types.add(candidate or base) | |
break | |
else: | |
candidate = candidate or base | |
if len(data_types) > 1: | |
raise TypeError('%r: too many data types: %r' % (class_name, data_types)) | |
elif data_types: | |
return data_types.pop() | |
else: | |
return None | |
def _get_settings_(bases): | |
"""Returns the combined _settings_ of all Enum base classes | |
bases: the tuple of bases given to __new__ | |
""" | |
settings = set() | |
for chain in bases: | |
for base in chain.__mro__: | |
if issubclass(base, Enum): | |
for s in base._settings_: | |
settings.add(s) | |
return settings | |
def _find_new_(mcls, clsdict, member_type, first_enum): | |
"""Returns the __new__ to be used for creating the enum members. | |
clsdict: the class dictionary given to __new__ | |
member_type: the data type whose __new__ will be used by default | |
first_enum: enumeration to check for an overriding __new__ | |
""" | |
# now find the correct __new__, checking to see of one was defined | |
# by the user; also check earlier enum classes in case a __new__ was | |
# saved as __new_member__ | |
__new__ = clsdict.get('__new__', None) | |
# | |
# should __new__ be saved as __new_member__ later? | |
save_new = first_enum is not None and __new__ is not None | |
# | |
if __new__ is None: | |
# check all possibles for __new_member__ before falling back to | |
# __new__ | |
for method in ('__new_member__', '__new__'): | |
for possible in (member_type, first_enum): | |
target = getattr(possible, method, None) | |
if target not in ( | |
None, | |
None.__new__, | |
object.__new__, | |
Enum.__new__, | |
StdlibEnum.__new__, | |
): | |
__new__ = target | |
break | |
if __new__ is not None: | |
break | |
else: | |
__new__ = object.__new__ | |
# if a non-object.__new__ is used then whatever value/tuple was | |
# assigned to the enum member name will be passed to __new__ and to the | |
# new enum member's __init__ | |
if first_enum is None or __new__ in (Enum.__new__, object.__new__): | |
new_uses_args = False | |
else: | |
new_uses_args = True | |
# | |
return __new__, save_new, new_uses_args | |
# In order to support Python 2 and 3 with a single | |
# codebase we have to create the Enum methods separately | |
# and then use the `type(name, bases, dict)` method to | |
# create the class. | |
if StdlibEnumMeta: | |
class EnumType(EnumType, StdlibEnumMeta): | |
pass | |
EnumMeta = EnumType | |
enum_dict = _Addendum( | |
dict=EnumType.__prepare__('Enum', (object, )), | |
doc="Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n", | |
ns=globals(), | |
) | |
def __signature__(cls): | |
if cls._member_names_: | |
return '(*values)' | |
else: | |
return '(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)' | |
def __init__(self, *args, **kwds): | |
# auto-init method | |
_auto_init_ = self._auto_init_ | |
if _auto_init_ is None: | |
return | |
if 'value' in _auto_init_: | |
# remove 'value' from _auto_init_ as it has already been handled | |
_auto_init_ = _auto_init_[1:] | |
if _auto_init_: | |
if len(_auto_init_) < len(args): | |
raise TypeError('%d arguments expected (%s), %d received (%s)' | |
% (len(_auto_init_), _auto_init_, len(args), args)) | |
for name, arg in zip(_auto_init_, args): | |
setattr(self, name, arg) | |
if len(args) < len(_auto_init_): | |
remaining_args = _auto_init_[len(args):] | |
for name in remaining_args: | |
value = kwds.pop(name, undefined) | |
if value is undefined: | |
raise TypeError('missing value for: %r' % (name, )) | |
setattr(self, name, value) | |
if kwds: | |
# too many keyword arguments | |
raise TypeError('invalid keyword(s): %s' % ', '.join(kwds.keys())) | |
def __new__(cls, value): | |
# all enum instances are actually created during class construction | |
# without calling this method; this method is called by the metaclass' | |
# __call__ (i.e. Color(3) ), and by pickle | |
if NoAlias in cls._settings_: | |
raise TypeError('NoAlias enumerations cannot be looked up by value') | |
if type(value) is cls: | |
# For lookups like Color(Color.red) | |
# value = value.value | |
return value | |
# by-value search for a matching enum member | |
# see if it's in the reverse mapping (for hashable values) | |
try: | |
return cls._value2member_map_[value] | |
except KeyError: | |
# Not found, no need to do long O(n) search | |
pass | |
except TypeError: | |
# not there, now do long search -- O(n) behavior | |
for member_value, member in cls._value2member_seq_: | |
if member_value == value: | |
return member | |
# still not found -- try _missing_ hook | |
result = cls._missing_value_(value) | |
if isinstance(result, cls): | |
return result | |
elif result is not None and getattr(cls, '_boundary_', None) is EJECT: | |
return result | |
else: | |
if result is None: | |
if value is no_arg: | |
raise ValueError('%s() should be called with a value' % (cls.__name__, )) | |
else: | |
raise ValueError("%r is not a valid %s" % (value, cls.__name__)) | |
else: | |
raise TypeError( | |
'error in %s._missing_: returned %r instead of None or a valid member' | |
% (cls.__name__, result) | |
) | |
def __init_subclass__(cls, **kwds): | |
if pyver < PY3_6: | |
# end of the line | |
if kwds: | |
raise TypeError('unconsumed keyword arguments: %r' % (kwds, )) | |
else: | |
super(Enum, cls).__init_subclass__(**kwds) | |
def _generate_next_value_(name, start, count, last_values, *args, **kwds): | |
for last_value in reversed(last_values): | |
try: | |
new_value = last_value + 1 | |
break | |
except TypeError: | |
pass | |
else: | |
new_value = start | |
if args: | |
return (new_value, ) + args | |
else: | |
return new_value | |
def _missing_(cls, value): | |
"deprecated, use _missing_value_ instead" | |
return None | |
def _missing_value_(cls, value): | |
"used for failed value access" | |
return cls._missing_(value) | |
def _missing_name_(cls, name): | |
"used for failed item access" | |
return None | |
def __repr__(self): | |
v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__ | |
return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_)) | |
def __str__(self): | |
return "%s.%s" % (self.__class__.__name__, self._name_) | |
if PY3: | |
def __dir__(self): | |
""" | |
Returns all members and all public methods | |
""" | |
if self.__class__._member_type_ is object: | |
interesting = set(['__class__', '__doc__', '__eq__', '__hash__', '__module__']) | |
else: | |
interesting = set(n for n in object.__dir__(self) if n not in self._member_map_) | |
for name in getattr(self, '__dict__', []): | |
if name[0] != '_': | |
interesting.add(name) | |
for cls in self.__class__.mro(): | |
for name, obj in cls.__dict__.items(): | |
if name[0] == '_': | |
continue | |
if isinstance(obj, property): | |
# that's an enum.property | |
if obj.fget is not None or name not in self._member_map_: | |
interesting.add(name) | |
else: | |
# in case it was added by `dir(self)` | |
interesting.discard(name) | |
elif not isinstance(obj, self.__class__): | |
interesting.add(name) | |
return sorted(interesting) | |
def __format__(self, format_spec): | |
return str.__format__(str(self), format_spec) | |
def __hash__(self): | |
return hash(self._name_) | |
def __reduce_ex__(self, proto): | |
return self.__class__, (self._value_, ) | |
def __le__(self, other): | |
raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__)) | |
def __lt__(self, other): | |
raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__)) | |
def __ge__(self, other): | |
raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__)) | |
def __gt__(self, other): | |
raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__)) | |
def __eq__(self, other): | |
if isinstance(other, self.__class__): | |
return self is other | |
return NotImplemented | |
def __ne__(self, other): | |
if isinstance(other, self.__class__): | |
return self is not other | |
return NotImplemented | |
# enum.property is used to provide access to the `name`, `value', etc., | |
# properties of enum members while keeping some measure of protection | |
# from modification, while still allowing for an enumeration to have | |
# members named `name`, `value`, etc.. This works because enumeration | |
# members are not set directly on the enum class -- enum.property will | |
# look them up in _member_map_. | |
def name(self): | |
return self._name_ | |
def value(self): | |
return self._value_ | |
def values(self): | |
return self._values_ | |
_enum_base = StdlibEnum or object | |
Enum = EnumType('Enum', (_enum_base, ), enum_dict.resolve()) | |
del enum_dict | |
# Enum has now been created | |
def pickle_by_global_name(self, proto): | |
# should not be used with Flag-type enums | |
return self.name | |
_reduce_ex_by_name = pickle_by_global_name | |
def pickle_by_enum_name(self, proto): | |
# should not be used with Flag-type enums | |
return getattr, (self.__class__, self._name_) | |
def _dataclass_repr(self): | |
dcf = self.__dataclass_fields__ | |
return ', '.join( | |
'%s=%r' % (k, getattr(self, k)) | |
for k in dcf.keys() | |
if dcf[k].repr | |
) | |
# ReprEnum | |
if StdlibReprEnum: | |
_repr_bases = Enum, StdlibReprEnum | |
else: | |
_repr_bases = (Enum, ) | |
ReprEnum = EnumType('ReprEnum', _repr_bases, { | |
'__doc__': "Only changes the repr(), leaving str() and format() to the mixed-in type." | |
}) | |
# IntEnum | |
class IntEnum(int, ReprEnum): | |
""" | |
Enum where members are also (and must be) ints | |
""" | |
# StrEnums | |
class StrEnum(str, ReprEnum): | |
""" | |
Enum where members are also (and must already be) strings | |
default value is member name, lower-cased | |
""" | |
def __new__(cls, *values, **kwds): | |
if kwds: | |
raise TypeError('%r: keyword arguments not supported' % (cls.__name__)) | |
if values: | |
if not isinstance(values[0], str): | |
raise TypeError('%s: values must be str [%r is a %r]' % (cls.__name__, values[0], type(values[0]))) | |
value = str(*values) | |
member = str.__new__(cls, value) | |
member._value_ = value | |
return member | |
__str__ = str.__str__ | |
def _generate_next_value_(name, start, count, last_values): | |
""" | |
Return the lower-cased version of the member name. | |
""" | |
return name.lower() | |
class LowerStrEnum(StrEnum): | |
""" | |
Enum where members are also (and must already be) lower-case strings | |
default value is member name, lower-cased | |
""" | |
def __new__(cls, value, *args, **kwds): | |
obj = StrEnum.__new_member__(cls, value, *args, **kwds) | |
if value != value.lower(): | |
raise ValueError('%r is not lower-case' % value) | |
return obj | |
class UpperStrEnum(StrEnum): | |
""" | |
Enum where members are also (and must already be) upper-case strings | |
default value is member name, upper-cased | |
""" | |
def __new__(cls, value, *args, **kwds): | |
obj = StrEnum.__new_member__(cls, value, *args, **kwds) | |
if value != value.upper(): | |
raise ValueError('%r is not upper-case' % value) | |
return obj | |
def _generate_next_value_(name, start, count, last_values, *args, **kwds): | |
return name.upper() | |
# Specialty Enums | |
if PY3: | |
class AutoEnum(Enum): | |
""" | |
automatically use _generate_next_value_ when values are missing (Python 3 only) | |
""" | |
_settings_ = MagicValue | |
__all__.append('AutoEnum') | |
class AutoNumberEnum(Enum): | |
""" | |
Automatically assign increasing values to members. | |
Py3: numbers match creation order | |
Py2: numbers are assigned alphabetically by member name | |
(unless `_order_` is specified) | |
""" | |
def __new__(cls, *args, **kwds): | |
value = len(cls.__members__) + 1 | |
if cls._member_type_ is int: | |
obj = int.__new__(cls, value) | |
elif cls._member_type_ is long: | |
obj = long.__new__(cls, value) | |
else: | |
obj = object.__new__(cls) | |
obj._value_ = value | |
return obj | |
class AddValueEnum(Enum): | |
_settings_ = AddValue | |
class MultiValueEnum(Enum): | |
""" | |
Multiple values can map to each member. | |
""" | |
_settings_ = MultiValue | |
class NoAliasEnum(Enum): | |
""" | |
Duplicate value members are distinct, but cannot be looked up by value. | |
""" | |
_settings_ = NoAlias | |
class OrderedEnum(Enum): | |
""" | |
Add ordering based on values of Enum members. | |
""" | |
def __ge__(self, other): | |
if self.__class__ is other.__class__: | |
return self._value_ >= other._value_ | |
return NotImplemented | |
def __gt__(self, other): | |
if self.__class__ is other.__class__: | |
return self._value_ > other._value_ | |
return NotImplemented | |
def __le__(self, other): | |
if self.__class__ is other.__class__: | |
return self._value_ <= other._value_ | |
return NotImplemented | |
def __lt__(self, other): | |
if self.__class__ is other.__class__: | |
return self._value_ < other._value_ | |
return NotImplemented | |
if sqlite3: | |
class SqliteEnum(Enum): | |
def __conform__(self, protocol): | |
if protocol is sqlite3.PrepareProtocol: | |
return self.name | |
class UniqueEnum(Enum): | |
""" | |
Ensure no duplicate values exist. | |
""" | |
_settings_ = Unique | |
def convert(enum, name, module, filter, source=None): | |
""" | |
Create a new Enum subclass that replaces a collection of global constants | |
enum: Enum, IntEnum, ... | |
name: name of new Enum | |
module: name of module (__name__ in global context) | |
filter: function that returns True if name should be converted to Enum member | |
source: namespace to check (defaults to 'module') | |
""" | |
# convert all constants from source (or module) that pass filter() to | |
# a new Enum called name, and export the enum and its members back to | |
# module; | |
# also, replace the __reduce_ex__ method so unpickling works in | |
# previous Python versions | |
module_globals = vars(_sys.modules[module]) | |
if source: | |
source = vars(source) | |
else: | |
source = module_globals | |
members = dict((name, value) for name, value in source.items() if filter(name)) | |
enum = enum(name, members, module=module) | |
enum.__reduce_ex__ = _reduce_ex_by_name | |
module_globals.update(enum.__members__) | |
module_globals[name] = enum | |
def extend_enum(enumeration, name, *args, **kwds): | |
""" | |
Add a new member to an existing Enum. | |
""" | |
# there are four possibilities: | |
# - extending an aenum Enum or 3.11+ enum Enum | |
# - extending an aenum Flag or 3.11+ enum Flag | |
# - extending a pre-3.11 stdlib Enum Flag | |
# - extending a 3.11+ stdlib Flag | |
# | |
# fail early if name is already in the enumeration | |
if ( | |
name in enumeration.__dict__ | |
or name in enumeration._member_map_ | |
or name in [t[1] for t in getattr(enumeration, '_value2member_seq_', ())] | |
): | |
raise TypeError('%r already in use as %r' % (name, enumeration.__dict__.get(name, enumeration[name]))) | |
# and check for other instances in parent classes | |
descriptor = None | |
for base in enumeration.__mro__[1:]: | |
descriptor = base.__dict__.get(name) | |
if descriptor is not None: | |
if isinstance(descriptor, (property, DynamicClassAttribute)): | |
break | |
else: | |
raise TypeError('%r already in use in superclass %r' % (name, base.__name__)) | |
try: | |
_member_map_ = enumeration._member_map_ | |
_member_names_ = enumeration._member_names_ | |
_member_type_ = enumeration._member_type_ | |
_value2member_map_ = enumeration._value2member_map_ | |
base_attributes = set([a for b in enumeration.mro() for a in b.__dict__]) | |
except AttributeError: | |
raise TypeError('%r is not a supported Enum' % (enumeration, )) | |
try: | |
_value2member_seq_ = enumeration._value2member_seq_ | |
_multi_value_ = MultiValue in enumeration._settings_ | |
_no_alias_ = NoAlias in enumeration._settings_ | |
_unique_ = Unique in enumeration._settings_ | |
_auto_init_ = enumeration._auto_init_ or [] | |
except AttributeError: | |
# standard Enum | |
_value2member_seq_ = [] | |
_multi_value_ = False | |
_no_alias_ = False | |
_unique_ = False | |
_auto_init_ = [] | |
if _multi_value_ and not args: | |
# must specify values for multivalue enums | |
raise ValueError('no values specified for MultiValue enum %r' % enumeration.__name__) | |
mt_new = _member_type_.__new__ | |
_new = getattr(enumeration, '_new_member_', None) or getattr(enumeration, '__new_member__', None) or mt_new | |
if not args: | |
last_values = [m.value for m in enumeration] | |
count = len(enumeration) | |
start = getattr(enumeration, '_start_', None) | |
if start is None: | |
start = last_values and (last_values[-1] + 1) or 1 | |
_gnv = getattr(enumeration, '_generate_next_value_', None) | |
if _gnv is not None: | |
args = ( _gnv(name, start, count, last_values), ) | |
else: | |
# must be a 3.4 or 3.5 Enum | |
args = (start, ) | |
if _new is object.__new__: | |
new_uses_args = False | |
else: | |
new_uses_args = True | |
if len(args) == 1: | |
[value] = args | |
else: | |
value = args | |
more_values = () | |
kwds = {} | |
if isinstance(value, enum): | |
args = value.args | |
kwds = value.kwds | |
if not isinstance(value, tuple): | |
args = (value, ) | |
else: | |
args = value | |
# tease value out of auto-init if specified | |
if 'value' in _auto_init_: | |
if 'value' in kwds: | |
value = kwds.pop('value') | |
else: | |
value, args = args[0], args[1:] | |
elif _multi_value_: | |
value, more_values, args = args[0], args[1:], () | |
if new_uses_args: | |
args = (value, ) | |
if _member_type_ is tuple: | |
args = (args, ) | |
if not new_uses_args: | |
new_member = _new(enumeration) | |
if not hasattr(new_member, '_value_'): | |
new_member._value_ = value | |
else: | |
new_member = _new(enumeration, *args, **kwds) | |
if not hasattr(new_member, '_value_'): | |
new_member._value_ = _member_type_(*args) | |
value = new_member._value_ | |
if _multi_value_: | |
if 'value' in _auto_init_: | |
args = more_values | |
else: | |
# put all the values back into args for the init call | |
args = (value, ) + more_values | |
new_member._name_ = name | |
new_member.__objclass__ = enumeration.__class__ | |
new_member.__init__(*args) | |
new_member._values_ = (value, ) + more_values | |
new_member._sort_order_ = len(enumeration._member_names_) | |
# do final checks before modifying enum structures: | |
# - is new member a flag? | |
# - does the new member fit in the enum's declared _boundary_? | |
# - is new member an alias? | |
# | |
_all_bits_ = _flag_mask_ = None | |
if hasattr(enumeration, '_all_bits_'): | |
_all_bits_ = enumeration._all_bits_ | value | |
_flag_mask_ = enumeration._flag_mask_ | value | |
if enumeration._boundary_ != 'keep': | |
missed = list(_iter_bits_lsb(_flag_mask_ & ~_all_bits_)) | |
if missed: | |
raise TypeError( | |
'invalid Flag %r -- missing values: %s' | |
% (cls, ', '.join((str(i) for i in missed))) | |
) | |
# If another member with the same value was already defined, the | |
# new member becomes an alias to the existing one. | |
if _no_alias_: | |
# unless NoAlias was specified | |
return _finalize_extend_enum(enumeration, new_member, bits=_all_bits_, mask=_flag_mask_) | |
else: | |
# handle "normal" aliases | |
new_values = new_member._values_ | |
for canonical_member in _member_map_.values(): | |
canonical_values_ = getattr(canonical_member, '_values_', [canonical_member._value_]) | |
for canonical_value in canonical_values_: | |
for new_value in new_values: | |
if canonical_value == new_value: | |
# name is an alias | |
if _unique_ or _multi_value_: | |
# aliases not allowed in Unique and MultiValue enums | |
raise ValueError('%r is a duplicate of %r' % (new_member, canonical_member)) | |
else: | |
# aliased name can be added, remaining checks irrelevant | |
# aliases don't appear in member names (only in __members__ and _member_map_). | |
return _finalize_extend_enum(enumeration, canonical_member, name=name, bits=_all_bits_, mask=_flag_mask_, is_alias=True) | |
# not a standard alias, but maybe a flag alias | |
if pyver < PY3_6: | |
flag_bases = Flag, | |
else: | |
flag_bases = Flag, StdlibFlag | |
if issubclass(enumeration, flag_bases) and hasattr(enumeration, '_all_bits_'): | |
# handle the new flag type | |
if is_single_bit(value): | |
# a new member! (an aliase would have been discovered in the previous loop) | |
return _finalize_extend_enum(enumeration, new_member, bits=_all_bits_, mask=_flag_mask_) | |
else: | |
# might be an 3.11 Flag alias | |
if value & enumeration._flag_mask_ == value and _value2member_map_.get(value) is not None: | |
# yup, it's an alias to existing members... and its an alias of an alias | |
canonical = _value2member_map_.get(value) | |
return _finalize_extend_enum(enumeration, canonical, name=name, bits=_all_bits_, mask=_flag_mask_, is_alias=True) | |
else: | |
return _finalize_extend_enum(enumeration, new_member, bits=_all_bits_, mask=_flag_mask_, is_alias=True) | |
else: | |
# if we get here, we have a brand new member | |
return _finalize_extend_enum(enumeration, new_member) | |
def _finalize_extend_enum(enumeration, new_member, name=None, bits=None, mask=None, is_alias=False): | |
name = name or new_member.name | |
descriptor = None | |
for base in enumeration.__mro__[1:]: | |
descriptor = base.__dict__.get(name) | |
if descriptor is not None: | |
if isinstance(descriptor, (property, DynamicClassAttribute)): | |
break | |
else: | |
raise TypeError('%r already in use in superclass %r' % (name, base.__name__)) | |
if not descriptor: | |
# get redirect in place before adding to _member_map_ | |
redirect = property() | |
redirect.__set_name__(enumeration, name) | |
setattr(enumeration, name, redirect) | |
if not is_alias: | |
enumeration._member_names_.append(name) | |
enumeration._member_map_[name] = new_member | |
for v in getattr(new_member, '_values_', [new_member._value_]): | |
try: | |
enumeration._value2member_map_[v] = new_member | |
except TypeError: | |
enumeration._value2member_seq_ += ((v, new_member), ) | |
if bits: | |
enumeration._all_bits_ = bits | |
enumeration._flag_mask_ = mask | |
if is_single_bit(new_member._value_): | |
enumeration._singles_mask_ |= new_member._value_ | |
return new_member | |
def unique(enumeration): | |
""" | |
Class decorator that ensures only unique members exist in an enumeration. | |
""" | |
duplicates = [] | |
for name, member in enumeration.__members__.items(): | |
if name != member.name: | |
duplicates.append((name, member.name)) | |
if duplicates: | |
duplicate_names = ', '.join( | |
["%s -> %s" % (alias, name) for (alias, name) in duplicates] | |
) | |
raise ValueError('duplicate names found in %r: %s' % | |
(enumeration, duplicate_names) | |
) | |
return enumeration | |
# Flag | |
try: | |
from enum import FlagBoundary | |
except ImportError: | |
class FlagBoundary(StrEnum): | |
""" | |
control how out of range values are handled | |
"strict" -> error is raised [default] | |
"conform" -> extra bits are discarded | |
"eject" -> lose flag status (becomes a normal integer) | |
""" | |
STRICT = auto() | |
CONFORM = auto() | |
EJECT = auto() | |
KEEP = auto() | |
export(FlagBoundary, globals()) | |
if StdlibFlag: | |
_flag_bases = Enum, StdlibFlag | |
else: | |
_flag_bases = (Enum, ) | |
flag_dict = _Addendum( | |
dict=EnumType.__prepare__('Flag', _flag_bases), | |
doc="Generic flag enumeration.\n\nDerive from this class to define new flag enumerations.", | |
ns=globals(), | |
) | |
flag_dict['_boundary_'] = STRICT | |
flag_dict['_numeric_repr_'] = repr | |
def _generate_next_value_(name, start, count, last_values, *args, **kwds): | |
""" | |
Generate the next value when not given. | |
name: the name of the member | |
start: the initital start value or None | |
count: the number of existing members | |
last_value: the last value assigned or None | |
""" | |
if not count: | |
if args: | |
return ((1, start)[start is not None], ) + args | |
else: | |
return (1, start)[start is not None] | |
else: | |
last_value = max(last_values) | |
try: | |
high_bit = _high_bit(last_value) | |
result = 2 ** (high_bit+1) | |
if args: | |
return (result,) + args | |
else: | |
return result | |
except Exception: | |
pass | |
raise TypeError('invalid Flag value: %r' % last_value) | |
def _iter_member_by_value_(cls, value): | |
""" | |
Extract all members from the value in definition (i.e. increasing value) order. | |
""" | |
for val in _iter_bits_lsb(value & cls._singles_mask_): | |
yield cls._value2member_map_.get(val) | |
flag_dict['_iter_member_'] = _iter_member_by_value_ | |
def _iter_member_by_def_(cls, value): | |
""" | |
Extract all members from the value in definition order. | |
""" | |
members = list(cls._iter_member_by_value_(value)) | |
members.sort(key=lambda m: m._sort_order_) | |
for member in members: | |
yield member | |
def _missing_(cls, value): | |
""" | |
return a member matching the given value, or None | |
""" | |
return cls._create_pseudo_member_(value) | |
def _create_pseudo_member_(cls, *values): | |
""" | |
Create a composite member. | |
""" | |
# if we get here, no exact match was found | |
# STRICT - must be composed of single-bit flags | |
value = error_value = values[0] | |
if not isinstance(value, baseinteger): | |
raise ValueError( | |
"%r is not a valid %s" % (error_value, cls.__name__) | |
) | |
# check boundaries | |
# - value must be in range (e.g. -16 <-> +15, i.e. ~15 <-> 15) | |
# - value must not include any skipped flags (e.g. if bit 2 is not | |
# defined, then 0d10 is invalid) | |
flag_mask = cls._flag_mask_ | |
singles_mask = cls._singles_mask_ | |
all_bits = cls._all_bits_ | |
unknown_bits = all_bits ^ flag_mask | |
unnamed_bits = all_bits & ~flag_mask | |
boundary = cls._boundary_ | |
neg_value = (None, value)[value < 0] | |
# | |
if neg_value: | |
if neg_value <= ~all_bits: | |
if boundary is EJECT: | |
return error_value | |
elif boundary is KEEP: | |
value = 2**(neg_value.bit_length()) + neg_value | |
elif boundary is CONFORM: | |
value = (2**(neg_value.bit_length()) + neg_value) & flag_mask | |
else: | |
raise ValueError( | |
"%r is not a valid %s" % (error_value, cls.__name__) | |
) | |
else: | |
value = all_bits + 1 + neg_value | |
# | |
member_value = value & singles_mask # all named single-bit values | |
unnamed_value = value & flag_mask & ~singles_mask # all unnamed single-bit values | |
unknown_value = value & ~flag_mask | |
# | |
if boundary is EJECT: | |
if unknown_value: | |
return error_value | |
elif boundary is CONFORM: | |
unknown_value = 0 | |
elif boundary is STRICT: | |
if unknown_value: | |
raise ValueError( | |
"%r is not a valid %s" % (error_value, cls.__name__) | |
) | |
# | |
members = list(cls._iter_member_(member_value)) | |
final_value = member_value | |
still_unknown = 0 | |
if unnamed_value: | |
found = 0 | |
for n, pm in cls._member_map_.items(): | |
if ( | |
pm not in members | |
and pm._value_ | |
and pm._value_ & final_value == pm._value_ | |
and pm._value_ & unnamed_value | |
): | |
members.append(pm) | |
final_value |= pm._value_ | |
found |= pm._value_ | |
# anything still unnamed becomes unknown | |
still_unknown = (found & ~unnamed_value) ^ unnamed_value | |
if still_unknown: | |
if boundary is KEEP: | |
pass | |
elif boundary is CONFORM: | |
still_unknown = 0 | |
elif boundary is EJECT: | |
return error_value | |
else: # strict | |
raise ValueError( | |
"%r is not a valid %s" % (error_value, cls.__name__) | |
) | |
unknown_value |= still_unknown | |
final_value |= unknown_value | |
values = (final_value, ) + values[1:] | |
members.sort(key=lambda m: m._sort_order_) | |
# let class adjust values | |
values = cls._create_pseudo_member_values_(members, *values) | |
__new__ = getattr(cls, '__new_member__', None) | |
if cls._member_type_ is object and not __new__: | |
# construct a singleton enum pseudo-member | |
pseudo_member = object.__new__(cls) | |
else: | |
pseudo_member = (__new__ or cls._member_type_.__new__)(cls, *values) | |
if not hasattr(pseudo_member, '_value_'): | |
pseudo_member._value_ = final_value | |
if members: | |
pseudo_member._name_ = '|'.join([m._name_ for m in members]) | |
if unknown_value: | |
pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown_value) | |
else: | |
pseudo_member._name_ = None | |
# use setdefault in case another thread already created a composite | |
# with this value, but only if all members are known | |
# note: zero is a special case -- always add it | |
pseudo_member = cls._value2member_map_.setdefault(final_value, pseudo_member) | |
if neg_value is not None: | |
cls._value2member_map_[neg_value] = pseudo_member | |
return pseudo_member | |
def _create_pseudo_member_values_(cls, members, *values): | |
""" | |
Return values to be fed to __new__ to create new member. | |
""" | |
if cls._member_type_ in (baseinteger + (object, )): | |
return values | |
elif len(values) < 2: | |
return values + (cls._member_type_(), ) | |
else: | |
return values | |
def __contains__(self, other): | |
""" | |
Returns True if self has at least the same flags set as other. | |
""" | |
if not isinstance(other, self.__class__): | |
raise TypeError( | |
"unsupported operand type(s) for 'in': '%s' and '%s'" % ( | |
type(other).__name__, self.__class__.__name__)) | |
if other._value_ == 0 or self._value_ == 0: | |
return False | |
return other._value_ & self._value_ == other._value_ | |
def __iter__(self): | |
""" | |
Returns flags in definition order. | |
""" | |
for member in self._iter_member_(self._value_): | |
yield member | |
def __len__(self): | |
return bit_count(self._value_) | |
def __repr__(self): | |
cls = self.__class__ | |
if self._name_ is None: | |
# only zero is unnamed by default | |
return '<%s: %r>' % (cls.__name__, self._value_) | |
else: | |
return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_) | |
def __str__(self): | |
cls = self.__class__ | |
if self._name_ is None: | |
return '%s(%s)' % (cls.__name__, self._value_) | |
else: | |
return '%s.%s' % (cls.__name__, self._name_) | |
if PY2: | |
def __nonzero__(self): | |
return bool(self._value_) | |
else: | |
def __bool__(self): | |
return bool(self._value_) | |
def __or__(self, other): | |
if isinstance(other, self.__class__): | |
other_value = other._value_ | |
elif self._member_type_ is not object and isinstance(other, self._member_type_): | |
other_value = other | |
else: | |
return NotImplemented | |
return self.__class__(self._value_ | other_value) | |
def __and__(self, other): | |
if isinstance(other, self.__class__): | |
other_value = other._value_ | |
elif self._member_type_ is not object and isinstance(other, self._member_type_): | |
other_value = other | |
else: | |
return NotImplemented | |
return self.__class__(self._value_ & other_value) | |
def __xor__(self, other): | |
if isinstance(other, self.__class__): | |
other_value = other._value_ | |
elif self._member_type_ is not object and isinstance(other, self._member_type_): | |
other_value = other | |
else: | |
return NotImplemented | |
return self.__class__(self._value_ ^ other_value) | |
def __invert__(self): | |
if self._inverted_ is None: | |
self._inverted_ = self.__class__(self._singles_mask_ & ~self._value_) | |
if isinstance(self._inverted_, self.__class__): | |
self._inverted_._inverted_ = self | |
return self._inverted_ | |
flag_dict['__ror__'] = __or__ | |
flag_dict['__rand__'] = __and__ | |
flag_dict['__rxor__'] = __xor__ | |
Flag = EnumType('Flag', _flag_bases, flag_dict.resolve()) | |
del(flag_dict) | |
# IntFlag | |
class IntFlag(int, ReprEnum, Flag): | |
"Support for integer-based Flags" | |
_boundary_ = KEEP | |
def __contains__(self, other): | |
""" | |
Returns True if self has at least the same flags set as other. | |
""" | |
if isinstance(other, int): | |
other = self.__class__(other) | |
elif not isinstance(other, self.__class__): | |
raise TypeError( | |
"unsupported operand type(s) for 'in': '%s' and '%s'" % ( | |
type(other).__name__, self.__class__.__name__)) | |
if other._value_ == 0 or self._value_ == 0: | |
return False | |
return other._value_ & self._value_ == other._value_ | |
# helpers | |
def _high_bit(value): | |
"""returns index of highest bit, or -1 if value is zero or negative""" | |
return value.bit_length() - 1 | |
def global_enum_repr(self): | |
""" | |
use module.enum_name instead of class.enum_name | |
the module is the last module in case of a multi-module name | |
""" | |
module = self.__class__.__module__.split('.')[-1] | |
return '%s.%s' % (module, self._name_) | |
def global_flag_repr(self): | |
""" | |
use module.flag_name instead of class.flag_name | |
the module is the last module in case of a multi-module name | |
""" | |
module = self.__class__.__module__.split('.')[-1] | |
cls_name = self.__class__.__name__ | |
if self._name_ is None: | |
return "%s.%s(%r)" % (module, cls_name, self._value_) | |
if is_single_bit(self): | |
return '%s.%s' % (module, self._name_) | |
if self._boundary_ is not FlagBoundary.KEEP: | |
return '|'.join(['%s.%s' % (module, name) for name in self.name.split('|')]) | |
else: | |
name = [] | |
for n in self._name_.split('|'): | |
if n[0].isdigit(): | |
name.append(n) | |
else: | |
name.append('%s.%s' % (module, n)) | |
return '|'.join(name) | |
def global_str(self): | |
""" | |
use enum_name instead of class.enum_name | |
""" | |
if self._name_ is None: | |
cls_name = self.__class__.__name__ | |
return "%s(%r)" % (cls_name, self._value_) | |
else: | |
return self._name_ | |
def global_enum(cls, update_str=False): | |
""" | |
decorator that makes the repr() of an enum member reference its module | |
instead of its class; also exports all members to the enum's module's | |
global namespace | |
""" | |
if issubclass(cls, Flag): | |
cls.__repr__ = global_flag_repr | |
else: | |
cls.__repr__ = global_enum_repr | |
if not issubclass(cls, ReprEnum) or update_str: | |
cls.__str__ = global_str | |
_sys.modules[cls.__module__].__dict__.update(cls.__members__) | |
return cls | |
if StdlibEnumMeta: | |
from _weakrefset import WeakSet | |
def __subclasscheck__(cls, subclass): | |
""" | |
Override for issubclass(subclass, cls). | |
""" | |
if not isinstance(subclass, type): | |
raise TypeError('issubclass() arg 1 must be a class (got %r)' % (subclass, )) | |
# Check cache | |
try: | |
cls.__dict__['_subclass_cache_'] | |
except KeyError: | |
cls._subclass_cache_ = WeakSet() | |
cls._subclass_negative_cache_ = WeakSet() | |
except RecursionError: | |
import sys | |
exc, cls, tb = sys.exc_info() | |
exc = RecursionError('possible causes for endless recursion:\n - __getattribute__ is not ignoring __dunder__ attibutes\n - __instancecheck__ and/or __subclasscheck_ are (mutually) recursive\n see `aenum.remove_stdlib_integration` for temporary work-around') | |
raise_from_none(exc) | |
if subclass in cls._subclass_cache_: | |
return True | |
# Check negative cache | |
elif subclass in cls._subclass_negative_cache_: | |
return False | |
if cls is subclass: | |
cls._subclass_cache_.add(subclass) | |
return True | |
# Check if it's a direct subclass | |
if cls in getattr(subclass, '__mro__', ()): | |
cls._subclass_cache_.add(subclass) | |
return True | |
# Check if it's an aenum.Enum|IntEnum|IntFlag|Flag subclass | |
if cls is StdlibIntFlag and issubclass(subclass, IntFlag): | |
cls._subclass_cache_.add(subclass) | |
return True | |
elif cls is StdlibFlag and issubclass(subclass, Flag): | |
cls._subclass_cache_.add(subclass) | |
return True | |
elif cls is StdlibIntEnum and issubclass(subclass, IntEnum): | |
cls._subclass_cache_.add(subclass) | |
return True | |
if cls is StdlibEnum and issubclass(subclass, Enum): | |
cls._subclass_cache_.add(subclass) | |
return True | |
# No dice; update negative cache | |
cls._subclass_negative_cache_.add(subclass) | |
return False | |
def __instancecheck__(cls, instance): | |
subclass = instance.__class__ | |
try: | |
return cls.__subclasscheck__(subclass) | |
except RecursionError: | |
import sys | |
exc, cls, tb = sys.exc_info() | |
exc = RecursionError('possible causes for endless recursion:\n - __getattribute__ is not ignoring __dunder__ attibutes\n - __instancecheck__ and/or __subclasscheck_ are (mutually) recursive\n see `aenum.remove_stdlib_integration` for temporary work-around') | |
raise_from_none(exc) | |
def add_stdlib_integration(): | |
if StdlibEnum: | |
StdlibEnumMeta.__subclasscheck__ = __subclasscheck__ | |
StdlibEnumMeta.__instancecheck__ = __instancecheck__ | |
def remove_stdlib_integration(): | |
""" | |
Remove the __instancecheck__ and __subclasscheck__ overrides from the stdlib Enum. | |
Those overrides are in place so that code detecting stdlib enums will also detect | |
aenum enums. If a buggy __getattribute__, __instancecheck__, or __subclasscheck__ | |
is defined on a custom EnumMeta then RecursionErrors can result; using this | |
function after importing aenum will solve that problem, but the better solution is | |
to fix the buggy method. | |
""" | |
if StdlibEnum: | |
del StdlibEnumMeta.__instancecheck__ | |
del StdlibEnumMeta.__subclasscheck__ | |
class cls2module(object): | |
def __init__(self, cls, *args): | |
self.__name__ = cls.__name__ | |
self._parent_module = cls.__module__ | |
self.__all__ = [] | |
all_objects = cls.__dict__ | |
if not args: | |
args = [k for k, v in all_objects.items() if isinstance(v, (NamedConstant, Enum))] | |
for name in args: | |
self.__dict__[name] = all_objects[name] | |
self.__all__.append(name) | |
def register(self): | |
_sys.modules["%s.%s" % (self._parent_module, self.__name__)] = self | |