From 1437e73ce4bc386df76806da461f4dfc7388a0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 5 Oct 2021 13:40:17 +0200 Subject: [PATCH] Fix ``relative-beyond-top-level`` false positive (#1186) * Fix ``relative-beyond-top-level`` false positive --- ChangeLog | 4 ++++ astroid/nodes/scoped_nodes.py | 12 ++++++++++-- .../python3/data/beyond_top_level/import_package.py | 3 +++ .../namespace_package/lower_level/helper_function.py | 5 +++++ .../beyond_top_level/namespace_package/plugin_api.py | 2 ++ .../namespace_package/top_level_function.py | 5 +++++ tests/unittest_inference.py | 5 +++++ 7 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/testdata/python3/data/beyond_top_level/import_package.py create mode 100644 tests/testdata/python3/data/beyond_top_level/namespace_package/lower_level/helper_function.py create mode 100644 tests/testdata/python3/data/beyond_top_level/namespace_package/plugin_api.py create mode 100644 tests/testdata/python3/data/beyond_top_level/namespace_package/top_level_function.py diff --git a/ChangeLog b/ChangeLog index c80c57664a..0b55c004f5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -36,6 +36,10 @@ Release date: TBA * Improve brain for ``typing.Callable`` and ``typing.Type``. +* Fix bug with importing namespace packages with relative imports + + Closes PyCQA/pylint#5059 + What's New in astroid 2.8.0? ============================ diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index e64f736e15..ba8fca0cfb 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -43,6 +43,7 @@ import builtins import io import itertools +import os import typing from typing import List, Optional, TypeVar @@ -732,10 +733,17 @@ def relative_to_absolute_name(self, modname, level): if level: if self.package: level = level - 1 + package_name = self.name.rsplit(".", level)[0] + elif not os.path.exists("__init__.py") and os.path.exists( + modname.split(".")[0] + ): + level = level - 1 + package_name = "" + else: + package_name = self.name.rsplit(".", level)[0] if level and self.name.count(".") < level: raise TooManyLevelsError(level=level, name=self.name) - package_name = self.name.rsplit(".", level)[0] elif self.package: package_name = self.name else: @@ -744,7 +752,7 @@ def relative_to_absolute_name(self, modname, level): if package_name: if not modname: return package_name - return f"{package_name}.{modname}" + return f"{package_name}.{modname.split('.')[0]}" return modname def wildcard_import_names(self): diff --git a/tests/testdata/python3/data/beyond_top_level/import_package.py b/tests/testdata/python3/data/beyond_top_level/import_package.py new file mode 100644 index 0000000000..885d4c541a --- /dev/null +++ b/tests/testdata/python3/data/beyond_top_level/import_package.py @@ -0,0 +1,3 @@ +from namespace_package import top_level_function + +top_level_function.do_something() diff --git a/tests/testdata/python3/data/beyond_top_level/namespace_package/lower_level/helper_function.py b/tests/testdata/python3/data/beyond_top_level/namespace_package/lower_level/helper_function.py new file mode 100644 index 0000000000..1d0b12b347 --- /dev/null +++ b/tests/testdata/python3/data/beyond_top_level/namespace_package/lower_level/helper_function.py @@ -0,0 +1,5 @@ +from ..plugin_api import top_message + + +def plugin_message(msg): + return "plugin_message: %s" % top_message(msg) \ No newline at end of file diff --git a/tests/testdata/python3/data/beyond_top_level/namespace_package/plugin_api.py b/tests/testdata/python3/data/beyond_top_level/namespace_package/plugin_api.py new file mode 100644 index 0000000000..3941f197ec --- /dev/null +++ b/tests/testdata/python3/data/beyond_top_level/namespace_package/plugin_api.py @@ -0,0 +1,2 @@ +def top_message(msg): + return "top_message: %s" % msg diff --git a/tests/testdata/python3/data/beyond_top_level/namespace_package/top_level_function.py b/tests/testdata/python3/data/beyond_top_level/namespace_package/top_level_function.py new file mode 100644 index 0000000000..8342bd09f9 --- /dev/null +++ b/tests/testdata/python3/data/beyond_top_level/namespace_package/top_level_function.py @@ -0,0 +1,5 @@ +from .lower_level.helper_function import plugin_message + + +def do_something(): + return plugin_message("called by do_something") diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index ac52013836..dc662641bb 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -6535,5 +6535,10 @@ def play(): assert next(node.infer()).pytype() == ".B" +def test_namespace_package() -> None: + """check that a file using namespace packages and relative imports is parseable""" + resources.build_file("data/beyond_top_level/import_package.py") + + if __name__ == "__main__": unittest.main()