Bump 'enum' module from CPython 3.8.7

This commit is contained in:
Moreal
2021-08-22 18:58:23 +09:00
parent 4b3220b888
commit a5e7d25dee

233
Lib/enum.py vendored
View File

@@ -10,31 +10,41 @@ __all__ = [
def _is_descriptor(obj):
"""Returns True if obj is a descriptor, False otherwise."""
"""
Returns True if obj is a descriptor, False otherwise.
"""
return (
hasattr(obj, '__get__') or
hasattr(obj, '__set__') or
hasattr(obj, '__delete__'))
hasattr(obj, '__delete__')
)
def _is_dunder(name):
"""Returns True if a __dunder__ name, False otherwise."""
return (len(name) > 4 and
"""
Returns True if a __dunder__ name, False otherwise.
"""
return (
len(name) > 4 and
name[:2] == name[-2:] == '__' and
name[2] != '_' and
name[-3] != '_')
name[-3] != '_'
)
def _is_sunder(name):
"""Returns True if a _sunder_ name, False otherwise."""
return (len(name) > 2 and
"""
Returns True if a _sunder_ name, False otherwise.
"""
return (
len(name) > 2 and
name[0] == name[-1] == '_' and
name[1:2] != '_' and
name[-2:-1] != '_')
name[-2:-1] != '_'
)
def _make_class_unpicklable(cls):
"""Make the given class un-picklable."""
"""
Make the given class un-picklable.
"""
def _break_on_call_reduce(self, proto):
raise TypeError('%r cannot be pickled' % self)
cls.__reduce_ex__ = _break_on_call_reduce
@@ -49,26 +59,27 @@ class auto:
class _EnumDict(dict):
"""Track enum member order and ensure member names are not reused.
"""
Track enum member order and ensure member names are not reused.
EnumMeta will use the names found in self._member_names as the
enumeration member names.
"""
def __init__(self):
super().__init__()
self._member_names = []
self._last_values = []
self._ignore = []
self._auto_called = False
def __setitem__(self, key, value):
"""Changes anything not dundered or not a descriptor.
"""
Changes anything not dundered or not 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.
"""
if _is_sunder(key):
if key not in (
@@ -77,6 +88,9 @@ class _EnumDict(dict):
):
raise ValueError('_names_ are reserved for future Enum use')
if 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")
setattr(self, '_generate_next_value', value)
elif key == '_ignore_':
if isinstance(value, str):
@@ -86,7 +100,10 @@ class _EnumDict(dict):
self._ignore = value
already = set(value) & set(self._member_names)
if already:
raise ValueError('_ignore_ cannot specify already set names: %r' % (already, ))
raise ValueError(
'_ignore_ cannot specify already set names: %r'
% (already, )
)
elif _is_dunder(key):
if key == '__order__':
key = '_order_'
@@ -101,7 +118,13 @@ class _EnumDict(dict):
raise TypeError('%r already defined as: %r' % (key, self[key]))
if isinstance(value, auto):
if value.value == _auto_null:
value.value = self._generate_next_value(key, 1, len(self._member_names), self._last_values[:])
value.value = self._generate_next_value(
key,
1,
len(self._member_names),
self._last_values[:],
)
self._auto_called = True
value = value.value
self._member_names.append(key)
self._last_values.append(value)
@@ -113,17 +136,22 @@ class _EnumDict(dict):
# This is also why there are checks in EnumMeta like `if Enum is not None`
Enum = None
class EnumMeta(type):
"""Metaclass for Enum"""
"""
Metaclass for Enum
"""
@classmethod
def __prepare__(metacls, cls, bases):
# check that previous enum members do not exist
metacls._check_for_existing_members(cls, bases)
# create the namespace dict
enum_dict = _EnumDict()
# inherit previous flags and _generate_next_value_ function
member_type, first_enum = metacls._get_mixins_(bases)
member_type, first_enum = metacls._get_mixins_(cls, bases)
if first_enum is not None:
enum_dict['_generate_next_value_'] = getattr(first_enum, '_generate_next_value_', None)
enum_dict['_generate_next_value_'] = getattr(
first_enum, '_generate_next_value_', None,
)
return enum_dict
def __new__(metacls, cls, bases, classdict):
@@ -137,9 +165,10 @@ class EnumMeta(type):
ignore = classdict['_ignore_']
for key in ignore:
classdict.pop(key, None)
member_type, first_enum = metacls._get_mixins_(bases)
__new__, save_new, use_args = metacls._find_new_(classdict, member_type,
first_enum)
member_type, first_enum = metacls._get_mixins_(cls, bases)
__new__, save_new, use_args = metacls._find_new_(
classdict, member_type, first_enum,
)
# save enum items into separate mapping so they don't get baked into
# the new class
@@ -168,9 +197,11 @@ class EnumMeta(type):
# save DynamicClassAttribute attributes from super classes so we know
# if we can take the shortcut of storing members in the class dict
dynamic_attributes = {k for c in enum_class.mro()
for k, v in c.__dict__.items()
if isinstance(v, DynamicClassAttribute)}
dynamic_attributes = {
k for c in enum_class.mro()
for k, v in c.__dict__.items()
if isinstance(v, DynamicClassAttribute)
}
# Reverse value->name map for hashable values.
enum_class._value2member_map_ = {}
@@ -244,7 +275,11 @@ class EnumMeta(type):
# double check that repr and friends are not the mixin's or various
# things break (such as pickle)
# however, if the method is defined in the Enum itself, don't replace
# it
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
if name in classdict:
continue
class_method = getattr(enum_class, name)
obj_method = getattr(member_type, name, None)
enum_method = getattr(first_enum, name, None)
@@ -276,7 +311,8 @@ class EnumMeta(type):
return True
def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1):
"""Either returns an existing member, or creates a new enum class.
"""
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
@@ -298,12 +334,18 @@ class EnumMeta(type):
not correct, unpickling will fail in some circumstances.
`type`, if set, will be mixed in as the first base class.
"""
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)
return cls._create_(
value,
names,
module=module,
qualname=qualname,
type=type,
start=start,
)
def __contains__(cls, member):
if not isinstance(member, Enum):
@@ -316,22 +358,23 @@ class EnumMeta(type):
# 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." % cls.__name__)
raise AttributeError("%s: cannot delete Enum member." % cls.__name__)
super().__delattr__(attr)
def __dir__(self):
return (['__class__', '__doc__', '__members__', '__module__'] +
self._member_names_)
return (
['__class__', '__doc__', '__members__', '__module__']
+ self._member_names_
)
def __getattr__(cls, name):
"""Return the enum member matching `name`
"""
Return the enum member matching `name`
We use __getattr__ instead of descriptors or inserting into the enum
class' __dict__ in order to support `name` and `value` being both
properties for enum members (which live in the class' __dict__) and
enum members themselves.
"""
if _is_dunder(name):
raise AttributeError(name)
@@ -344,6 +387,9 @@ class EnumMeta(type):
return cls._member_map_[name]
def __iter__(cls):
"""
Returns members in definition order.
"""
return (cls._member_map_[name] for name in cls._member_names_)
def __len__(cls):
@@ -351,11 +397,11 @@ class EnumMeta(type):
@property
def __members__(cls):
"""Returns a mapping of member name->value.
"""
Returns a mapping of member name->value.
This mapping lists all enum members, including aliases. Note that this
is a read-only view of the internal mapping.
"""
return MappingProxyType(cls._member_map_)
@@ -363,15 +409,18 @@ class EnumMeta(type):
return "<enum %r>" % cls.__name__
def __reversed__(cls):
"""
Returns members in reverse definition order.
"""
return (cls._member_map_[name] for name in reversed(cls._member_names_))
def __setattr__(cls, name, value):
"""Block attempts to reassign Enum members.
"""
Block attempts to reassign Enum members.
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:
@@ -379,7 +428,8 @@ class EnumMeta(type):
super().__setattr__(name, value)
def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1):
"""Convenience method to create a new Enum class.
"""
Convenience method to create a new Enum class.
`names` can be:
@@ -388,11 +438,10 @@ class EnumMeta(type):
* An iterable of member names. Values are incremented by 1 from `start`.
* An iterable of (member name, value) pairs.
* A mapping of member name -> value pairs.
"""
metacls = cls.__class__
bases = (cls, ) if type is None else (type, cls)
_, first_enum = cls._get_mixins_(bases)
_, first_enum = cls._get_mixins_(cls, bases)
classdict = metacls.__prepare__(class_name, bases)
# special processing needed for names?
@@ -471,25 +520,50 @@ class EnumMeta(type):
return cls._convert_(*args, **kwargs)
@staticmethod
def _get_mixins_(bases):
"""Returns the type for creating enum members, and the first inherited
def _check_for_existing_members(class_name, bases):
for chain in bases:
for base in chain.__mro__:
if issubclass(base, Enum) and base._member_names_:
raise TypeError(
"%s: cannot extend enumeration %r"
% (class_name, base.__name__)
)
@staticmethod
def _get_mixins_(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:
return object, Enum
def _find_data_type(bases):
data_types = []
for chain in bases:
candidate = None
for base in chain.__mro__:
if base is object:
continue
elif issubclass(base, Enum):
if base._member_type_ is not object:
data_types.append(base._member_type_)
break
elif '__new__' in base.__dict__:
if issubclass(base, Enum):
continue
return base
data_types.append(candidate or base)
break
else:
candidate = base
if len(data_types) > 1:
raise TypeError('%r: too many data types: %r' % (class_name, data_types))
elif data_types:
return data_types[0]
else:
return None
# ensure final parent class is an Enum derivative, find any concrete
# data type, and check that Enum has no members
@@ -504,12 +578,12 @@ class EnumMeta(type):
@staticmethod
def _find_new_(classdict, member_type, first_enum):
"""Returns the __new__ to be used for creating the enum members.
"""
Returns the __new__ to be used for creating the enum members.
classdict: 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
@@ -549,10 +623,10 @@ class EnumMeta(type):
class Enum(metaclass=EnumMeta):
"""Generic enumeration.
"""
Generic enumeration.
Derive from this class to define new enumerations.
"""
def __new__(cls, value):
# all enum instances are actually created during class construction
@@ -595,6 +669,14 @@ class Enum(metaclass=EnumMeta):
raise exc
def _generate_next_value_(name, start, count, last_values):
"""
Generate the next value when not given.
name: the name of the member
start: the initial start value or None
count: the number of existing members
last_value: the last value assigned or None
"""
for last_value in reversed(last_values):
try:
return last_value + 1
@@ -605,7 +687,7 @@ class Enum(metaclass=EnumMeta):
@classmethod
def _missing_(cls, value):
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
return None
def __repr__(self):
return "<%s.%s: %r>" % (
@@ -615,21 +697,28 @@ class Enum(metaclass=EnumMeta):
return "%s.%s" % (self.__class__.__name__, self._name_)
def __dir__(self):
"""
Returns all members and all public methods
"""
added_behavior = [
m
for cls in self.__class__.mro()
for m in cls.__dict__
if m[0] != '_' and m not in self._member_map_
]
] + [m for m in self.__dict__ if m[0] != '_']
return (['__class__', '__doc__', '__module__'] + added_behavior)
def __format__(self, format_spec):
"""
Returns format using actual value type unless __str__ has been overridden.
"""
# mixed-in Enums should use the mixed-in type's __format__, otherwise
# we can get strange results with the Enum name showing up instead of
# the value
# pure Enum branch
if self._member_type_ is object:
# pure Enum branch, or branch with __str__ explicitly overridden
str_overridden = type(self).__str__ not in (Enum.__str__, Flag.__str__)
if self._member_type_ is object or str_overridden:
cls = str
val = str(self)
# mix-in branch
@@ -670,7 +759,9 @@ def _reduce_ex_by_name(self, proto):
return self.name
class Flag(Enum):
"""Support for flags"""
"""
Support for flags
"""
def _generate_next_value_(name, start, count, last_values):
"""
@@ -693,6 +784,9 @@ class Flag(Enum):
@classmethod
def _missing_(cls, value):
"""
Returns member (possibly creating it) if one can be found for value.
"""
original_value = value
if value < 0:
value = ~value
@@ -722,6 +816,9 @@ class Flag(Enum):
return pseudo_member
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'" % (
@@ -780,10 +877,15 @@ class Flag(Enum):
class IntFlag(int, Flag):
"""Support for integer-based Flags"""
"""
Support for integer-based Flags
"""
@classmethod
def _missing_(cls, value):
"""
Returns member (possibly creating it) if one can be found for value.
"""
if not isinstance(value, int):
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
new_member = cls._create_pseudo_member_(value)
@@ -791,6 +893,9 @@ class IntFlag(int, Flag):
@classmethod
def _create_pseudo_member_(cls, value):
"""
Create a composite member iff value contains only members.
"""
pseudo_member = cls._value2member_map_.get(value, None)
if pseudo_member is None:
need_to_create = [value]
@@ -845,11 +950,15 @@ class IntFlag(int, Flag):
def _high_bit(value):
"""returns index of highest bit, or -1 if value is zero or negative"""
"""
returns index of highest bit, or -1 if value is zero or negative
"""
return value.bit_length() - 1
def unique(enumeration):
"""Class decorator for enumerations ensuring unique member values."""
"""
Class decorator for enumerations ensuring unique member values.
"""
duplicates = []
for name, member in enumeration.__members__.items():
if name != member.name:
@@ -862,7 +971,9 @@ def unique(enumeration):
return enumeration
def _decompose(flag, value):
"""Extract all members from the value."""
"""
Extract all members from the value.
"""
# _decompose is only called if the value is not named
not_covered = value
negative = value < 0