diff --git a/ChangeLog b/ChangeLog index 8d8b068cd9..af4bfeda06 100644 --- a/ChangeLog +++ b/ChangeLog @@ -221,6 +221,11 @@ Release date: TBA Closes pylint-dev/pylint#8802 + +* Fix false positives for ``no-member`` and ``invalid-name`` when using the ``_name_``, ``_value_`` and ``_ignore_`` sunders in Enums. + + Closes pylint-dev/pylint#9015 + * Fix inference of functions with ``@functools.lru_cache`` decorators without parentheses. diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index 7a2d40e1e3..998306b3de 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -403,7 +403,10 @@ def infer_enum_class(node: nodes.ClassDef) -> nodes.ClassDef: dunder_members = {} target_names = set() for local, values in node.locals.items(): - if any(not isinstance(value, nodes.AssignName) for value in values): + if ( + any(not isinstance(value, nodes.AssignName) for value in values) + or local == "_ignore_" + ): continue stmt = values[0].statement() @@ -440,8 +443,14 @@ class {name}({types}): def value(self): return {return_value} @property + def _value_(self): + return {return_value} + @property def name(self): return "{name}" + @property + def _name_(self): + return "{name}" """.format( name=target.name, types=", ".join(node.basenames), diff --git a/tests/brain/test_enum.py b/tests/brain/test_enum.py index bbdb812cee..9b0e6c535c 100644 --- a/tests/brain/test_enum.py +++ b/tests/brain/test_enum.py @@ -521,3 +521,45 @@ def __init__(self, mass, radius): mars, radius = enum_members.items assert mars[1].name == "MARS" assert radius[1].name == "radius" + + def test_enum_with_ignore(self) -> None: + """Exclude ``_ignore_`` from the ``__members__`` container + Originally reported in https://github.com/pylint-dev/pylint/issues/9015 + """ + + ast_node: nodes.Attribute = builder.extract_node( + """ + import enum + + + class MyEnum(enum.Enum): + FOO = enum.auto() + BAR = enum.auto() + _ignore_ = ["BAZ"] + BAZ = 42 + MyEnum.__members__ + """ + ) + inferred = next(ast_node.infer()) + members_names = [const_node.value for const_node, name_obj in inferred.items] + assert members_names == ["FOO", "BAR", "BAZ"] + + def test_enum_sunder_names(self) -> None: + """Test that both `_name_` and `_value_` sunder names exist""" + + sunder_name, sunder_value = builder.extract_node( + """ + import enum + + + class MyEnum(enum.Enum): + APPLE = 42 + MyEnum.APPLE._name_ #@ + MyEnum.APPLE._value_ #@ + """ + ) + inferred_name = next(sunder_name.infer()) + assert inferred_name.value == "APPLE" + + inferred_value = next(sunder_value.infer()) + assert inferred_value.value == 42