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 26, 2023
1 parent efb34f2 commit b8755ba
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 2 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 EXT_LIB_DIRS, STD_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
15 changes: 14 additions & 1 deletion tests/test_modutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"]))

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 b8755ba

Please sign in to comment.