Skip to content

Commit 85d3935

Browse files
committed
Don't add namespace packages to sys.path
1 parent bd9550d commit 85d3935

File tree

6 files changed

+54
-0
lines changed

6 files changed

+54
-0
lines changed

ChangeLog

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ Release date: TBA
1717

1818
Closes #4301
1919

20+
* Improved recognition of namespace packages. They should no longer be added to ``sys.path``
21+
which should prevent various issues with false positives.
22+
Because of the difficulities with mirroring the python import mechanics we would gladly
23+
hear feedback on this change.
24+
25+
Closes #5226, Closes #2648
26+
2027
* ``BaseChecker`` classes now require the ``linter`` argument to be passed.
2128

2229
* Fix a failure to respect inline disables for ``fixme`` occurring on the last line

doc/whatsnew/2.14.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ Other Changes
8181

8282
Closes #4301
8383

84+
* Improved recognition of namespace packages. They should no longer be added to ``sys.path``
85+
which should prevent various issues with false positives.
86+
Because of the difficulities with mirroring the python import mechanics we would gladly
87+
hear feedback on this change.
88+
89+
Closes #5226, Closes #2648
90+
8491
* Update ``invalid-slots-object`` message to show bad object rather than its inferred value.
8592

8693
Closes #6101

pylint/lint/utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from datetime import datetime
1212
from pathlib import Path
1313

14+
from astroid import modutils
15+
1416
from pylint.config import PYLINT_HOME
1517
from pylint.lint.expand_modules import get_python_path
1618

@@ -71,12 +73,32 @@ def get_fatal_error_message(filepath: str, issue_template_path: Path) -> str:
7173
)
7274

7375

76+
def _is_part_of_namespace_package(filename: str) -> bool:
77+
"""Check if a file is part of a namespace package."""
78+
filepath = Path(filename)
79+
try:
80+
modname = modutils.modpath_from_file(filename)
81+
except ImportError:
82+
modname = [filepath.stem]
83+
if filepath.is_dir():
84+
filepath = filepath / "__init__.py"
85+
86+
try:
87+
spec = modutils.file_info_from_modpath(modname)
88+
except ImportError:
89+
return False
90+
91+
return modutils.is_namespace(spec)
92+
93+
7494
def _patch_sys_path(args: Sequence[str]) -> list[str]:
7595
original = list(sys.path)
7696
changes = []
7797
seen = set()
7898
for arg in args:
7999
path = get_python_path(arg)
100+
if _is_part_of_namespace_package(path):
101+
continue
80102
if path not in seen:
81103
changes.append(path)
82104
seen.add(path)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
from existing import my_func # type: ignore[attr-defined]
6+
7+
my_func()

test_namespace_pkg/something_else/__init__.py

Whitespace-only changes.

tests/lint/unittest_lint.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import argparse
1010
import os
1111
import re
12+
import subprocess
1213
import sys
1314
import tempfile
1415
from collections.abc import Iterable, Iterator
@@ -170,6 +171,16 @@ def test_more_args(fake_path, case):
170171
assert sys.path == fake_path
171172

172173

174+
def test_namespace_package_sys_path() -> None:
175+
"""Test that we do not append namespace packages to sys.path."""
176+
result = subprocess.run(
177+
[sys.executable, "-m", "pylint", "test_namespace_pkg/"],
178+
check=False,
179+
stdout=subprocess.PIPE,
180+
)
181+
assert "Module import itself" not in result.stdout.decode("utf-8")
182+
183+
173184
@pytest.fixture(scope="module")
174185
def disable():
175186
return ["I"]

0 commit comments

Comments
 (0)