gh-93910: [Enum] remove member.member deprecation (GH-103236)

i.e. Color.RED.BLUE is now officially supported.
This commit is contained in:
Ethan Furman 2023-04-05 17:33:52 -07:00 committed by GitHub
parent b4978ff872
commit 4ec8dd10bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 37 additions and 50 deletions

View file

@ -988,12 +988,11 @@ but remain normal attributes.
"""""""""""""""""""" """"""""""""""""""""
Enum members are instances of their enum class, and are normally accessed as Enum members are instances of their enum class, and are normally accessed as
``EnumClass.member``. In Python versions starting with ``3.5`` you could access ``EnumClass.member``. In certain situations, such as writing custom enum
members from other members -- this practice is discouraged, is deprecated behavior, being able to access one member directly from another is useful,
in ``3.12``, and will be removed in ``3.14``. and is supported.
.. versionchanged:: 3.5 .. versionchanged:: 3.5
.. versionchanged:: 3.12
Creating members that are mixed with other data types Creating members that are mixed with other data types

View file

@ -201,23 +201,13 @@ class property(DynamicClassAttribute):
) )
else: else:
if self.fget is None: if self.fget is None:
if self.member is None: # not sure this can happen, but just in case # look for a member by this name.
try:
return ownerclass._member_map_[self.name]
except KeyError:
raise AttributeError( raise AttributeError(
'%r has no attribute %r' % (ownerclass, self.name) '%r has no attribute %r' % (ownerclass, self.name)
) )
# issue warning deprecating this behavior
import warnings
warnings.warn(
"`member.member` access (e.g. `Color.RED.BLUE`) is "
"deprecated and will be removed in 3.14.",
DeprecationWarning,
stacklevel=2,
)
return self.member
# XXX: uncomment in 3.14 and remove warning above
# raise AttributeError(
# '%r member has no attribute %r' % (ownerclass, self.name)
# )
else: else:
return self.fget(instance) return self.fget(instance)
@ -314,22 +304,32 @@ class _proto_member:
): ):
# no other instances found, record this member in _member_names_ # no other instances found, record this member in _member_names_
enum_class._member_names_.append(member_name) enum_class._member_names_.append(member_name)
# get redirect in place before adding to _member_map_ # if necessary, get redirect in place and then add it to _member_map_
# but check for other instances in parent classes first found_descriptor = None
descriptor = None
for base in enum_class.__mro__[1:]: for base in enum_class.__mro__[1:]:
descriptor = base.__dict__.get(member_name) descriptor = base.__dict__.get(member_name)
if descriptor is not None: if descriptor is not None:
if isinstance(descriptor, (property, DynamicClassAttribute)): if isinstance(descriptor, (property, DynamicClassAttribute)):
found_descriptor = descriptor
break break
redirect = property() elif (
redirect.member = enum_member hasattr(descriptor, 'fget') and
redirect.__set_name__(enum_class, member_name) hasattr(descriptor, 'fset') and
if descriptor: hasattr(descriptor, 'fdel')
redirect.fget = getattr(descriptor, 'fget', None) ):
redirect.fset = getattr(descriptor, 'fset', None) found_descriptor = descriptor
redirect.fdel = getattr(descriptor, 'fdel', None) continue
setattr(enum_class, member_name, redirect) if found_descriptor:
redirect = property()
redirect.member = enum_member
redirect.__set_name__(enum_class, member_name)
# earlier descriptor found; copy fget, fset, fdel to this one.
redirect.fget = found_descriptor.fget
redirect.fset = found_descriptor.fset
redirect.fdel = found_descriptor.fdel
setattr(enum_class, member_name, redirect)
else:
setattr(enum_class, member_name, enum_member)
# now add to _member_map_ (even aliases) # now add to _member_map_ (even aliases)
enum_class._member_map_[member_name] = enum_member enum_class._member_map_[member_name] = enum_member
try: try:

View file

@ -2686,28 +2686,15 @@ class TestSpecial(unittest.TestCase):
self.assertEqual(Private._Private__corporal, 'Radar') self.assertEqual(Private._Private__corporal, 'Radar')
self.assertEqual(Private._Private__major_, 'Hoolihan') self.assertEqual(Private._Private__major_, 'Hoolihan')
@unittest.skipIf( def test_member_from_member_access(self):
python_version <= (3, 13), class Di(Enum):
'member.member access currently deprecated', YES = 1
) NO = 0
def test_exception_for_member_from_member_access(self): name = 3
with self.assertRaisesRegex(AttributeError, "<enum .Di.> member has no attribute .NO."): warn = Di.YES.NO
class Di(Enum):
YES = 1
NO = 0
nope = Di.YES.NO
@unittest.skipIf(
python_version > (3, 13),
'member.member access now raises',
)
def test_warning_for_member_from_member_access(self):
with self.assertWarnsRegex(DeprecationWarning, '`member.member` access .* is deprecated and will be removed in 3.14'):
class Di(Enum):
YES = 1
NO = 0
warn = Di.YES.NO
self.assertIs(warn, Di.NO) self.assertIs(warn, Di.NO)
self.assertIs(Di.name, Di['name'])
self.assertEqual(Di.name.name, 'name')
def test_dynamic_members_with_static_methods(self): def test_dynamic_members_with_static_methods(self):
# #

View file

@ -0,0 +1 @@
Remove deprecation of enum ``memmber.member`` access.