8000 gh-95077: [Enum] add code-based deprecation warnings for member.member access by ethanfurman · Pull Request #95083 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-95077: [Enum] add code-based deprecation warnings for member.member access #95083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
issue deprecation warning for member.member access
  • Loading branch information
ethanfurman committed Jul 21, 2022
commit a81c0e54fdcc876cec9a73eb2fbc5b39e3a7c489
46 changes: 31 additions & 15 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,23 @@ def __get__(self, instance, ownerclass=None):
)
else:
if self.fget is None:
raise AttributeError(
'%r member has no attribute %r' % (ownerclass, self.name)
if self.member is None: # not sure this can happen, but just in case
raise AttributeError(
'%r has no attribute %r' % (ownerclass, self.name)
)
# issue warning deprecating this behavior
import warnings
warnings.warn(
"`member.member` access (e.g. `Color.RE 8000 D.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:
return self.fget(instance)

Expand Down Expand Up @@ -301,23 +315,19 @@ def __set_name__(self, enum_class, member_name):
enum_class._member_names_.append(member_name)
# get redirect in place before adding to _member_map_
# but check for other instances in parent classes first
need_override = False
descriptor = None
for base in enum_class.__mro__[1:]:
descriptor = base.__dict__.get(member_name)
if descriptor is not None:
if isinstance(descriptor, (property, DynamicClassAttribute)):
break
else:
need_override = True
# keep looking for an enum.property
redirect = property()
redirect.member = enum_member
redirect.__set_name__(enum_class, member_name)
if descriptor:
redirect.fget = descriptor.fget
redirect.fset = descriptor.fset
redirect.fdel = descriptor.fdel
redirect.fget = getattr(descriptor, 'fget', None)
redirect.fset = getattr(descriptor, 'fset', None)
redirect.fdel = getattr(descriptor, 'fdel', None)
setattr(enum_class, member_name, redirect)
# now add to _member_map_ (even aliases)
enum_class._member_map_[member_name] = enum_member
Expand Down Expand Up @@ -1180,10 +1190,10 @@ def __reduce_ex__(self, proto):
# enum.property is used to provide access to the `name` and
# `value` attributes of enum members while keeping some measure of
# protection from modification, while still allowing for an enumeration
# to have members named `name` and `value`. This works because enumeration
# members are not set directly on the enum class; they are kept in a
# separate structure, _member_map_, which is where enum.property looks for
# them
# to have members named `name` and `value`. This works because each
# instance of enum.property saves its companion member, which it returns
# on class lookup; on instance lookup it either executes a provided function
# or raises an AttributeError.

@property
def name(self):
Expand Down Expand Up @@ -1657,10 +1667,12 @@ def convert_class(cls):
value = gnv(name, 1, len(member_names), gnv_last_values)
if value in value2member_map:
# an alias to an existing member
member = value2member_map[value]
redirect = property()
redirect.member = member
redirect.__set_name__(enum_class, name)
setattr(enum_class, name, redirect)
member_map[name] = value2member_map[value]
member_map[name] = member
else:
# create the member
if use_args:
Expand All @@ -1676,6 +1688,7 @@ def convert_class(cls):
member.__objclass__ = enum_class
member.__init__(value)
redirect = property()
redirect.member = member
redirect.__set_name__(enum_class, name)
setattr(enum_class, name, redirect)
member_map[name] = member
Expand Down Expand Up @@ -1703,10 +1716,12 @@ def convert_class(cls):
value = value.value
if value in value2member_map:
# an alias to an existing member
member = value2member_map[value]
redirect = property()
redirect.member = member
redirect.__set_name__(enum_class, name)
setattr(enum_class, name, redirect)
member_map[name] = value2member_map[value]
member_map[name] = member
else:
# create the member
if use_args:
Expand All @@ -1723,6 +1738,7 @@ def convert_class(cls):
member.__init__(value)
member._sort_order_ = len(member_names)
redirect = property()
redirect.member = member
redirect.__set_name__(enum_class, name)
setattr(enum_class, name, redirect)
member_map[name] = member
Expand Down
16 changes: 15 additions & 1 deletion Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -2646,14 +2646,28 @@ class Private(Enum):
self.assertEqual(Private._Private__corporal, 'Radar')
self.assertEqual(Private._Private__major_, 'Hoolihan')

@unittest.skip("Accessing all values retained for performance reasons, see GH-93910")
@unittest.skipIf(
python_version <= (3, 13),
'member.member access currently deprecated',
)
def test_exception_for_member_from_member_access(self):
with self.assertRaisesRegex(AttributeError, "<enum .Di.> member has no attribute .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)

def test_dynamic_members_with_static_methods(self):
#
Expand Down
0