diff --git a/ChangeLog b/ChangeLog index e575805e70..60ad66ba19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,13 @@ Release date: TBA Closes #4301 +* Improved recognition of namespace packages. They should no longer be added to ``sys.path`` + which should prevent various issues with false positives. + Because of the difficulties with mirroring the python import mechanics we would gladly + hear feedback on this change. + + Closes #5226, Closes #2648 + * ``BaseChecker`` classes now require the ``linter`` argument to be passed. * Fix a failure to respect inline disables for ``fixme`` occurring on the last line diff --git a/doc/whatsnew/2.14.rst b/doc/whatsnew/2.14.rst index 0e6ae101f1..895bfcd79f 100644 --- a/doc/whatsnew/2.14.rst +++ b/doc/whatsnew/2.14.rst @@ -94,6 +94,13 @@ Other Changes Closes #4301 +* Improved recognition of namespace packages. They should no longer be added to ``sys.path`` + which should prevent various issues with false positives. + Because of the difficulties with mirroring the python import mechanics we would gladly + hear feedback on this change. + + Closes #5226, Closes #2648 + * Update ``invalid-slots-object`` message to show bad object rather than its inferred value. Closes #6101 diff --git a/pylint/lint/utils.py b/pylint/lint/utils.py index 30694c25cf..a08cd6d2a9 100644 --- a/pylint/lint/utils.py +++ b/pylint/lint/utils.py @@ -11,6 +11,8 @@ from datetime import datetime from pathlib import Path +from astroid import modutils + from pylint.config import PYLINT_HOME from pylint.lint.expand_modules import get_python_path @@ -71,12 +73,29 @@ def get_fatal_error_message(filepath: str, issue_template_path: Path) -> str: ) +def _is_part_of_namespace_package(filename: str) -> bool: + """Check if a file is part of a namespace package.""" + try: + modname = modutils.modpath_from_file(filename) + except ImportError: + modname = [Path(filename).stem] + + try: + spec = modutils.file_info_from_modpath(modname) + except ImportError: + return False + + return modutils.is_namespace(spec) + + def _patch_sys_path(args: Sequence[str]) -> list[str]: original = list(sys.path) changes = [] seen = set() for arg in args: path = get_python_path(arg) + if _is_part_of_namespace_package(path): + continue if path not in seen: changes.append(path) seen.add(path) diff --git a/tests/lint/test_namespace_packages.py b/tests/lint/test_namespace_packages.py new file mode 100644 index 0000000000..6ac96f09ec --- /dev/null +++ b/tests/lint/test_namespace_packages.py @@ -0,0 +1,25 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + +"""Tests related to linting of namespace packages.""" + +from io import StringIO + +from pylint.reporters.text import TextReporter +from pylint.testutils._run import _Run as Run + + +def test_namespace_package_sys_path() -> None: + """Test that we do not append namespace packages to sys.path. + + The test package is based on https://github.com/PyCQA/pylint/issues/2648. + """ + pylint_output = StringIO() + reporter = TextReporter(pylint_output) + runner = Run( + ["tests/regrtest_data/namespace_import_self/"], + reporter=reporter, + exit=False, + ) + assert not runner.linter.stats.by_msg diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 2e1875d968..36fd7ed4d7 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -69,6 +69,14 @@ def test__is_in_ignore_list_re_match() -> None: "path": str(TEST_DIRECTORY / "lint/test_pylinter.py"), } +test_namespace_packages = { + "basename": "lint", + "basepath": INIT_PATH, + "isarg": False, + "name": "lint.test_namespace_packages", + "path": str(TEST_DIRECTORY / "lint/test_namespace_packages.py"), +} + init_of_package = { "basename": "lint", "basepath": INIT_PATH, @@ -98,6 +106,7 @@ class Checker(BaseChecker): [str(Path(__file__).parent)], [ init_of_package, + test_namespace_packages, test_pylinter, test_utils, this_file_from_init, diff --git a/tests/regrtest_data/namespace_import_self/else/__init__.py b/tests/regrtest_data/namespace_import_self/else/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/namespace_import_self/pylint/__init__.py b/tests/regrtest_data/namespace_import_self/pylint/__init__.py new file mode 100644 index 0000000000..cce5e53024 --- /dev/null +++ b/tests/regrtest_data/namespace_import_self/pylint/__init__.py @@ -0,0 +1,5 @@ +"""Module that imports from its own namespace.""" + +from pylint import run_pylint + +run_pylint()