From b8755ba9eee151b33fb7ecd71c041bfac4dadd59 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 23 Jun 2023 01:01:16 -0400 Subject: [PATCH 1/2] 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 | 15 ++++++++++++++- tests/testdata/python3/data/copy.py | 1 + 4 files changed, 34 insertions(+), 2 deletions(-) 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..a7270c4e6a 100644 --- a/tests/test_modutils.py +++ b/tests/test_modutils.py @@ -20,7 +20,7 @@ import astroid from astroid import modutils -from astroid.const import PY310_PLUS +from astroid.const import PY310_PLUS, WIN32 from astroid.interpreter._import import spec from . import resources @@ -268,6 +268,19 @@ 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: + realpath = str(resources.RESOURCE_PATH) + if WIN32: + # Escape backslashes. + realpath = realpath.replace("\\", "\\\\") + sys.path.insert(0, realpath) + 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)""" From dae59214e81fb0ec9e33ada1c189c959d9f6a5f1 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 25 Jun 2023 22:27:41 -0400 Subject: [PATCH 2/2] fixup! Add skip --- tests/test_modutils.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_modutils.py b/tests/test_modutils.py index a7270c4e6a..ecb21829d7 100644 --- a/tests/test_modutils.py +++ b/tests/test_modutils.py @@ -20,7 +20,7 @@ import astroid from astroid import modutils -from astroid.const import PY310_PLUS, WIN32 +from astroid.const import PY310_PLUS, PY311_PLUS, WIN32 from astroid.interpreter._import import spec from . import resources @@ -268,12 +268,14 @@ def test_std_lib(self) -> None: os.path.realpath(os.path.__file__.replace(".pyc", ".py")), ) + @pytest.mark.skipif( + WIN32 and not PY311_PLUS, + reason="Fails on Windows below 3.11 for what seems like a test setup/isolation issue " + "rather than a functional issue. Possibly related: " + "https://github.com/python/cpython/pull/93653 (other surrounding tests add '.' to sys.path)", + ) def test_std_lib_found_before_same_named_package_on_path(self) -> None: - realpath = str(resources.RESOURCE_PATH) - if WIN32: - # Escape backslashes. - realpath = realpath.replace("\\", "\\\\") - sys.path.insert(0, realpath) + sys.path.insert(0, str(resources.RESOURCE_PATH)) self.addCleanup(sys.path.pop, 0) file = modutils.file_from_modpath(["copy"])