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

Do not append namespace packages to sys.path #6405

Merged
merged 10 commits into from
May 2, 2022
7 changes: 7 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions doc/whatsnew/2.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions pylint/lint/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions tests/lint/test_namespace_packages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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)
Run(["tests/regrtest_data/namespace_import_self/"], reporter=reporter, exit=False)
assert "Module import itself" not in pylint_output.getvalue()
9 changes: 9 additions & 0 deletions tests/lint/unittest_expand_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from existing import my_func # type: ignore[attr-defined]

my_func()