Skip to content

Commit

Permalink
Prefer stdlib modules over same-named modules on sys.path
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jacobtylerwalls committed Jun 23, 2023
1 parent efb34f2 commit 23ee4e2
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 1 deletion.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ What's New in astroid 2.15.6?
=============================
Release date: TBA

* 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

* Harden ``get_module_part()`` against ``"."``.

Closes pylint-dev/pylint#8749
Expand Down
15 changes: 14 additions & 1 deletion astroid/interpreter/_import/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 STD_LIB_DIRS, EXT_LIB_DIRS

from . import util

Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions tests/test_modutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.find("data"))
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"]))

Expand Down
1 change: 1 addition & 0 deletions tests/testdata/python3/data/copy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""fake copy module (unlike email, we need one without __init__.py)"""

0 comments on commit 23ee4e2

Please sign in to comment.