Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
- name: Install Qt
if: ${{ matrix.python-version == '3.10' }}
run: |
sudo apt-get update
sudo apt-get install build-essential libgl1-mesa-dev
- name: Generate partial Python venv restore key
id: generate-python-key
Expand Down
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Release date: TBA

Closes #1958

* Fix a false positive when an attribute named ``Enum`` was confused with ``enum.Enum``.
Calls to ``Enum`` are now inferred & the qualified name is checked.

Refs PyCQA/pylint#5719

* Remove unnecessary typing_extensions dependency on Python 3.11 and newer

What's New in astroid 2.13.2?
Expand Down
22 changes: 21 additions & 1 deletion astroid/brain/brain_namedtuple_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import functools
import keyword
import sys
from collections.abc import Iterator
from textwrap import dedent

Expand All @@ -24,7 +25,12 @@
)
from astroid.manager import AstroidManager

TYPING_NAMEDTUPLE_BASENAMES = {"NamedTuple", "typing.NamedTuple"}
if sys.version_info >= (3, 8):
from typing import Final
else:
from typing_extensions import Final


ENUM_BASE_NAMES = {
"Enum",
"IntEnum",
Expand All @@ -33,6 +39,8 @@
"IntFlag",
"enum.IntFlag",
}
ENUM_QNAME: Final[str] = "enum.Enum"
TYPING_NAMEDTUPLE_BASENAMES: Final[set[str]] = {"NamedTuple", "typing.NamedTuple"}


def _infer_first(node, context):
Expand Down Expand Up @@ -298,6 +306,18 @@ def infer_enum(
node: nodes.Call, context: InferenceContext | None = None
) -> Iterator[bases.Instance]:
"""Specific inference function for enum Call node."""
# Raise `UseInferenceDefault` if `node` is a call to a a user-defined Enum.
try:
inferred = node.func.infer(context)
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc

if not any(
isinstance(item, nodes.ClassDef) and item.qname() == ENUM_QNAME
for item in inferred
):
raise UseInferenceDefault

enum_meta = _extract_single_node(
"""
class EnumMeta(object):
Expand Down
44 changes: 44 additions & 0 deletions tests/unittest_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,50 @@ class MyEnum(PyEnum):
assert isinstance(inferred, bases.Instance)
assert inferred._proxied.name == "ENUM_KEY"

def test_class_named_enum(self) -> None:
"""Test that the user-defined class named `Enum` is not inferred as `enum.Enum`"""
astroid.extract_node(
"""
class Enum:
def __init__(self, one, two):
self.one = one
self.two = two
def pear(self):
...
""",
"module_with_class_named_enum",
)

attribute_nodes = astroid.extract_node(
"""
import module_with_class_named_enum
module_with_class_named_enum.Enum("apple", "orange") #@
typo_module_with_class_named_enum.Enum("apple", "orange") #@
"""
)

name_nodes = astroid.extract_node(
"""
from module_with_class_named_enum import Enum
Enum("apple", "orange") #@
TypoEnum("apple", "orange") #@
"""
)

# Test that both of the successfully inferred `Name` & `Attribute`
# nodes refer to the user-defined Enum class.
for inferred in (attribute_nodes[0].inferred()[0], name_nodes[0].inferred()[0]):
assert isinstance(inferred, astroid.Instance)
assert inferred.name == "Enum"
assert inferred.qname() == "module_with_class_named_enum.Enum"
assert "pear" in inferred.locals

# Test that an `InferenceError` is raised when an attempt is made to
# infer a `Name` or `Attribute` node & they cannot be found.
for node in (attribute_nodes[1], name_nodes[1]):
with pytest.raises(InferenceError):
node.inferred()


@unittest.skipUnless(HAS_DATEUTIL, "This test requires the dateutil library.")
class DateutilBrainTest(unittest.TestCase):
Expand Down