Skip to content
Snippets Groups Projects
Commit e37329bd authored by Conor Finn's avatar Conor Finn
Browse files

RE #27722 Remove py3compat

parent 7fa823ee
No related branches found
No related tags found
No related merge requests found
# Copyright (C) 2004-2017 Barry Warsaw
#
# This file is part of flufl.enum
#
# flufl.enum is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# flufl.enum is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with flufl.enum. If not, see <http://www.gnu.org/licenses/>.
#
# Author: Barry Warsaw <barry@python.org>
#
# Used to provide backwards compatability with Python 2
"""Python enumerations.
"""
import re
import sys
import warnings
from operator import itemgetter
__metaclass__ = type
__all__ = [
'Enum',
'EnumValue',
'IntEnum',
'make',
]
COMMASPACE = ', '
SPACE = ' '
IDENTIFIER_RE = r'^[a-zA-Z_][a-zA-Z0-9_]*$'
class EnumMetaclass(type):
"""Meta class for Enums."""
def __init__(cls, name, bases, attributes):
"""Create an Enum class.
:param cls: The class being defined.
:param name: The name of the class.
:param bases: The class's base classes.
:param attributes: The class attributes.
"""
super(EnumMetaclass, cls).__init__(name, bases, attributes)
# Store EnumValues here for easy access.
cls._enums = {}
# Figure out if this class has a custom factory for building enum
# values. The default is EnumValue, but the class (or one of its
# bases) can declare a custom one with a special attribute.
factory = attributes.get('__value_factory__')
# Figure out the set of enum values on the base classes, to ensure
# that we don't get any duplicate values. At the same time, check the
# base classes for the special attribute.
for basecls in cls.__mro__:
if hasattr(basecls, '_enums'):
cls._enums.update(basecls._enums)
if hasattr(basecls, '__value_factory__'):
basecls_factory = basecls.__value_factory__
if factory is not None and basecls_factory != factory:
raise TypeError(
'Conflicting enum factory in base class: {}'.format(
basecls_factory))
factory = basecls_factory
# Set the factory default if necessary.
if factory is None:
factory = EnumValue
# For each class attribute, create an enum value and store that back
# on the class instead of the original value. Skip Python reserved
# names. Also add a mapping from the original value to the enum value
# instance so we can return the same object on conversion.
for attr in attributes:
if not (attr.startswith('__') and attr.endswith('__')):
value = attributes[attr]
enumval = factory(cls, value, attr)
if value in cls._enums:
other = cls._enums[value]
# Without this, sort order is undefined and causes
# unpredictable results for the test suite.
first = (attr if attr < other else other)
second = (other if attr < other else attr)
raise ValueError("Conflicting enum value '{}' "
"for names: '{}' and '{}'".format(
value, first, second))
# Store as an attribute on the class, and save the attr name.
setattr(cls, attr, enumval)
cls._enums[value] = attr
def __dir__(cls):
# For Python 3.2, we must explicitly convert the dict view to a list.
# Order is not guaranteed, so don't sort it.
return list(cls._enums.values())
def __repr__(cls):
# We want predictable reprs. Because base Enum items can have any
# value, the only reliable way to sort the keys for the repr is based
# on the attribute name, which must be Python identifiers.
return '<{0} {{{1}}}>'.format(cls.__name__, COMMASPACE.join(
'{0}: {1}'.format(value, key)
for key, value in sorted(cls._enums.items(), key=itemgetter(1))))
def __iter__(cls):
for value in sorted(cls._enums.values()):
yield getattr(cls, value)
def __getitem__(cls, item):
attr = cls._enums.get(item)
if attr is None:
# If this is an EnumValue, try it's .value attribute.
if hasattr(item, 'value'):
attr = cls._enums.get(item.value)
if attr is None:
# It wasn't value-ish -- try the attribute name. This was
# deprecated in LP: #1167091.
try:
warnings.warn('Enum[item_name] is deprecated; '
'use getattr(Enum, item_name)',
DeprecationWarning, 2)
return getattr(cls, item)
except (AttributeError, TypeError):
raise ValueError(item)
return getattr(cls, attr)
def __call__(cls, *args):
# One-argument calling is a deprecated synonym for getitem.
if len(args) == 1:
warnings.warn('MyEnum(arg) is deprecated; use MyEnum[arg]',
DeprecationWarning, 2)
return cls.__getitem__(args[0])
name, source = args
return _make(cls, name, source)
class EnumValue:
"""Class to represent an enumeration value.
EnumValue('Color', 'red', 12) prints as 'Color.red' and can be converted
to the integer 12.
"""
def __init__(self, enum, value, name):
self._enum = enum
self._value = value
self._name = name
def __repr__(self):
return '<EnumValue: {0}.{1} [value={2}]>'.format(
self._enum.__name__, self._name, self._value)
def __str__(self):
return '{0}.{1}'.format(self._enum.__name__, self._name)
def __int__(self):
warnings.warn('int() is deprecated; use IntEnums',
DeprecationWarning, 2)
return self._value
def __reduce__(self):
return getattr, (self._enum, self._name)
@property
def enum(self):
"""Return the class associated with the enum value."""
return self._enum
@property
def name(self):
"""Return the name of the enum value."""
return self._name
@property
def value(self):
"""Return the underlying value."""
return self._value
# Support only comparison by identity and equality. Ordered comparisions
# are not supported.
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def __lt__(self, other):
# In Python 3, returning NotImplemented from an ordered comparison
# operator will cause a TypeError to be raised. This doesn't work in
# Python 2 though, and you'd end up with working, but incorrect,
# ordered comparisons. In Python 2 we raise the TypeError explicitly.
if sys.version_info[0] < 3:
raise TypeError(
'unorderable types: {}() < {}()'.format(
self.__class__.__name__, other.__class__.__name__))
return NotImplemented
def __gt__(self, other):
if sys.version_info[0] < 3:
raise TypeError(
'unorderable types: {}() > {}()'.format(
self.__class__.__name__, other.__class__.__name__))
return NotImplemented
def __le__(self, other):
if sys.version_info[0] < 3:
raise TypeError(
'unorderable types: {}() <= {}()'.format(
self.__class__.__name__, other.__class__.__name__))
return NotImplemented
def __ge__(self, other):
if sys.version_info[0] < 3:
raise TypeError(
'unorderable types: {}() >= {}()'.format(
self.__class__.__name__, other.__class__.__name__))
return NotImplemented
__hash__ = object.__hash__
# Define the Enum class using metaclass syntax compatible with both Python 2
# and Python 3.
Enum = EnumMetaclass(str('Enum'), (), {
'__doc__': 'The public API Enum class.',
})
class IntEnumValue(int, EnumValue):
"""An EnumValue that is also an integer."""
def __new__(cls, enum, value, attr):
return super(IntEnumValue, cls).__new__(cls, value)
__repr__ = EnumValue.__repr__
__str__ = EnumValue.__str__
# For Python 2 (Python 3 doesn't need this to work).
__eq__ = int.__eq__
__ne__ = int.__ne__
__le__ = int.__le__
__lt__ = int.__lt__
__gt__ = int.__gt__
__ge__ = int.__ge__
# The non-deprecated version of this method.
def __int__(self):
return self._value
# For slices and index().
__index__ = __int__
class IntEnumMetaclass(EnumMetaclass):
# Define an iteration over the integer values instead of the attribute
# names.
def __iter__(cls):
for key in sorted(cls._enums):
yield getattr(cls, cls._enums[key])
IntEnum = IntEnumMetaclass(str('IntEnum'), (Enum,), {
'__doc__': 'A specialized enumeration with values that are also integers.',
'__value_factory__': IntEnumValue,
})
if str is bytes:
# Python 2
STRING_TYPE = basestring
else:
# Python 3
STRING_TYPE = str
def _swap(sequence):
for key, value in sequence:
yield value, key
def _make(enum_class, name, source):
"""The common implementation for `Enum()` and `IntEnum()`."""
namespace = {}
illegals = []
have_strings = None
# Auto-splitting of strings.
if isinstance(source, STRING_TYPE):
source = source.split()
# Look for dict-like arguments. Specifically, it must have a callable
# .items() attribute. Because of the way enumerate() works, here we have
# to swap the key/values.
try:
source = _swap(source.items())
except (TypeError, AttributeError):
source = enumerate(source, start=1)
for i, item in source:
if isinstance(item, STRING_TYPE):
if have_strings is None:
have_strings = True
elif not have_strings:
raise ValueError('heterogeneous source')
namespace[item] = i
if re.match(IDENTIFIER_RE, item) is None:
illegals.append(item)
else:
if have_strings is None:
have_strings = False
elif have_strings:
raise ValueError('heterogeneous source')
item_name, item_value = item
namespace[item_name] = item_value
if re.match(IDENTIFIER_RE, item_name) is None:
illegals.append(item_name)
if len(illegals) > 0:
raise ValueError('non-identifiers: {0}'.format(SPACE.join(illegals)))
return EnumMetaclass(str(name), (enum_class,), namespace)
def make(name, source):
"""Return an Enum class from a name and source.
This is a convenience function for defining a new enumeration given an
existing sequence. When an sequence is used, it is iterated over to get
the enumeration value items. The sequence iteration can either return
strings or 2-tuples. When strings are used, values are automatically
assigned starting from 1. When 2-tuples are used, the first item of the
tuple is a string and the second item is the integer value.
`source` must be homogeneous. You cannot mix string-only and 2-tuple
items in the sequence.
:param name: The resulting enum's class name.
:type name: byte string (or ASCII-only unicode string)
:param source: An object giving the enumeration value items.
:type source: A sequence of strings or 2-tuples.
:return: The new enumeration class.
:rtype: instance of `EnumMetaClass`
:raises ValueError: when a heterogeneous source is given, or when
non-identifiers are used as enumeration value names.
"""
warnings.warn('make() is deprecated; use Enum(name, source)',
DeprecationWarning, 2)
return _make(Enum, name, source)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment