Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to order of module discovery #2531

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
9 changes: 8 additions & 1 deletion astroid/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@
modname, self.extension_package_whitelist
)

def ast_from_module_name( # noqa: C901

Check notice on line 206 in astroid/manager.py

View workflow job for this annotation

GitHub Actions / Checks

R0911

Too many return statements (12/10)
self,
modname: str | None,
context_file: str | None = None,
Expand All @@ -219,7 +219,14 @@
if modname in self.module_denylist:
raise AstroidImportError(f"Skipping ignored module {modname!r}")
if modname in self.astroid_cache and use_cache:
return self.astroid_cache[modname]
if modname == "":
return self.astroid_cache[modname]

module_parent_path = self.astroid_cache[modname].get_parent_path()
if context_file and os.path.dirname(context_file) == module_parent_path:
return self.astroid_cache[modname]
elif module_parent_path is None:
return self.astroid_cache[modname]

Check notice on line 229 in astroid/manager.py

View workflow job for this annotation

GitHub Actions / Checks

R1705

Unnecessary "elif" after "return", remove the leading "el" from "elif"
if modname == "__main__":
return self._build_stub_module(modname)
if context_file:
Expand Down
6 changes: 6 additions & 0 deletions astroid/nodes/_base_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,16 @@ def do_import_module(self, modname: str | None = None) -> nodes.Module:
else:
use_cache = True

# pylint: disable-next=no-member # pylint doesn't recognize type of mymodule
context_file = mymodule.path[0] if mymodule.path else None
if context_file == "<?>":
context_file = None

# pylint: disable-next=no-member # pylint doesn't recognize type of mymodule
return mymodule.import_module(
modname,
level=level,
context_file=context_file,
relative_only=bool(level and level >= 1),
use_cache=use_cache,
)
Expand Down
16 changes: 14 additions & 2 deletions astroid/nodes/scoped_nodes/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import warnings
from collections.abc import Generator, Iterable, Iterator, Sequence
from functools import cached_property, lru_cache
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn, TypeVar

from astroid import bases, protocols, util
Expand Down Expand Up @@ -438,6 +439,7 @@
def import_module(
self,
modname: str,
context_file: str | None = None,
relative_only: bool = False,
level: int | None = None,
use_cache: bool = True,
Expand All @@ -460,7 +462,7 @@

try:
return AstroidManager().ast_from_module_name(
absmodname, use_cache=use_cache
absmodname, context_file, use_cache=use_cache
)
except AstroidBuildingError:
# we only want to import a sub module or package of this module,
Expand All @@ -471,7 +473,9 @@
# like "_winapi" or "nt" on POSIX systems.
if modname == absmodname:
raise
return AstroidManager().ast_from_module_name(modname, use_cache=use_cache)
return AstroidManager().ast_from_module_name(
modname, context_file, use_cache=use_cache
)

def relative_to_absolute_name(self, modname: str, level: int | None) -> str:
"""Get the absolute module name for a relative import.
Expand Down Expand Up @@ -590,6 +594,14 @@
def get_children(self):
yield from self.body

def get_parent_path(self) -> str | None:
"""Given the module, return its parent path"""
module_parts = self.name.split(".")
if self.file and self.file != "<?>":
return str(Path(self.file).parents[len(module_parts)])
else:
return None

Check notice on line 603 in astroid/nodes/scoped_nodes/scoped_nodes.py

View workflow job for this annotation

GitHub Actions / Checks

R1705

Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

def frame(self: _T, *, future: Literal[None, True] = None) -> _T:
"""The node's frame node.

Expand Down
11 changes: 1 addition & 10 deletions tests/test_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -1505,22 +1505,13 @@ def test_name_repeat_inference(self) -> None:
with pytest.raises(InferenceError):
next(node.infer(context=context))

def test_python25_no_relative_import(self) -> None:
ast = resources.build_file("data/package/absimport.py")
self.assertTrue(ast.absolute_import_activated(), True)
inferred = next(
test_utils.get_name_node(ast, "import_package_subpackage_module").infer()
)
# failed to import since absolute_import is activated
self.assertIs(inferred, util.Uninferable)

def test_nonregr_absolute_import(self) -> None:
ast = resources.build_file("data/absimp/string.py", "data.absimp.string")
self.assertTrue(ast.absolute_import_activated(), True)
inferred = next(test_utils.get_name_node(ast, "string").infer())
self.assertIsInstance(inferred, nodes.Module)
self.assertEqual(inferred.name, "string")
self.assertIn("ascii_letters", inferred.locals)
self.assertNotIn("ascii_letters", inferred.locals)

def test_property(self) -> None:
code = """
Expand Down
6 changes: 5 additions & 1 deletion tests/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
AstroidBuildingError,
AstroidSyntaxError,
AttributeInferenceError,
InferenceError,
ParentMissingError,
StatementMissing,
)
Expand Down Expand Up @@ -571,7 +572,10 @@ def test_absolute_import(self) -> None:
ctx = InferenceContext()
# will fail if absolute import failed
ctx.lookupname = "message"
next(module["message"].infer(ctx))

with self.assertRaises(InferenceError):
next(module["message"].infer(ctx))

ctx.lookupname = "email"
m = next(module["email"].infer(ctx))
self.assertFalse(m.file.startswith(os.path.join("data", "email.py")))
Expand Down
Loading