diff --git a/astroid/manager.py b/astroid/manager.py index 7206be616a..10796572b5 100644 --- a/astroid/manager.py +++ b/astroid/manager.py @@ -219,7 +219,14 @@ def ast_from_module_name( # noqa: C901 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] if modname == "__main__": return self._build_stub_module(modname) if context_file: diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py index 96c3c1c06d..9db4da78d2 100644 --- a/astroid/nodes/_base_nodes.py +++ b/astroid/nodes/_base_nodes.py @@ -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, ) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index af3b9d39a8..e50fa1ac29 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -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 @@ -438,6 +439,7 @@ def absolute_import_activated(self) -> bool: def import_module( self, modname: str, + context_file: str | None = None, relative_only: bool = False, level: int | None = None, use_cache: bool = True, @@ -460,7 +462,7 @@ def import_module( 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, @@ -471,7 +473,9 @@ def import_module( # 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. @@ -590,6 +594,14 @@ def bool_value(self, context: InferenceContext | None = None) -> bool: 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 + def frame(self: _T, *, future: Literal[None, True] = None) -> _T: """The node's frame node. diff --git a/tests/test_inference.py b/tests/test_inference.py index a8b11b1614..850dc4cf4b 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -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 = """ diff --git a/tests/test_nodes.py b/tests/test_nodes.py index 64cae2f676..aa249fb83a 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -34,6 +34,7 @@ AstroidBuildingError, AstroidSyntaxError, AttributeInferenceError, + InferenceError, ParentMissingError, StatementMissing, ) @@ -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")))