From 8768ec30c454841d70d69e892142262473ca4cc5 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 23 Jun 2023 01:01:16 -0400 Subject: [PATCH] Prefer stdlib modules over same-named modules on sys.path For example: `import copy` now finds `copy` instead of `copy.py`. This worked correctly before in at least some cases if there was a (more?) complete chain of __init__.py files from cwd all the way to the location of the `copy.py` module. Closes pylint-dev/pylint#6535 --- ChangeLog | 5 +++++ astroid/interpreter/_import/spec.py | 15 ++++++++++++++- tests/test_modutils.py | 9 +++++++++ tests/testdata/python3/data/copy.py | 1 + 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/testdata/python3/data/copy.py diff --git a/ChangeLog b/ChangeLog index ec63e2dade..3f7a0a1f12 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,11 @@ Release date: TBA Closes #1780 Refs #2140 +* Prefer standard library modules over same-named modules on sys.path. For example + ``import copy`` now finds ``copy`` instead of ``copy.py``. Solves ``no-member`` issues. + + Closes pylint-dev/pylint#6535 + * Reduce file system access in ``ast_from_file()``. * Reduce time to ``import astroid`` by delaying ``astroid_bootstrapping()`` until diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 3c21fd73b4..e1df3d56d7 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -20,7 +20,7 @@ from typing import Any, Literal, NamedTuple, Protocol from astroid.const import PY310_PLUS -from astroid.modutils import EXT_LIB_DIRS +from astroid.modutils import EXT_LIB_DIRS, STD_LIB_DIRS from . import util @@ -157,6 +157,19 @@ def find_module( location=getattr(spec.loader_state, "filename", None), type=ModuleType.PY_FROZEN, ) + if ( + spec + and isinstance(spec.loader, importlib.machinery.SourceFileLoader) + and any(spec.origin.startswith(std_lib) for std_lib in STD_LIB_DIRS) + and not spec.origin.endswith("__init__.py") + ): + # Return standard library modules before local modules + # https://github.com/pylint-dev/pylint/issues/6535 + return ModuleSpec( + name=modname, + location=spec.origin, + type=ModuleType.PY_SOURCE, + ) except ValueError: pass submodule_path = sys.path diff --git a/tests/test_modutils.py b/tests/test_modutils.py index 929c58992c..692984b305 100644 --- a/tests/test_modutils.py +++ b/tests/test_modutils.py @@ -268,6 +268,15 @@ def test_std_lib(self) -> None: os.path.realpath(os.path.__file__.replace(".pyc", ".py")), ) + def test_std_lib_found_before_same_named_package_on_path(self) -> None: + sys.path.insert(0, resources.RESOURCE_PATH) + self.addCleanup(sys.path.pop, 0) + + file = modutils.file_from_modpath(["copy"]) + + self.assertNotIn("test", file) # tests/testdata/python3/data/copy.py + self.assertTrue(any(stdlib in file for stdlib in modutils.STD_LIB_DIRS)) + def test_builtin(self) -> None: self.assertIsNone(modutils.file_from_modpath(["sys"])) diff --git a/tests/testdata/python3/data/copy.py b/tests/testdata/python3/data/copy.py new file mode 100644 index 0000000000..5f67cbc1ca --- /dev/null +++ b/tests/testdata/python3/data/copy.py @@ -0,0 +1 @@ +"""fake copy module (unlike email, we need one without __init__.py)"""