From 56b8e229d9150d57e090a1e4bd046b2fe8541d97 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 1 Mar 2021 12:49:45 +0100 Subject: Refactor modify sys_path for execution as python module --- ChangeLog | 5 ++++ pylint/__init__.py | 23 ++++++++++++++++++ pylint/__main__.py | 11 +-------- tests/test_self.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6af40d64b..d1d4aea6e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,11 @@ Release date: TBA Close #3263 +* Fix issue when executing with ``python -m pylint`` + + Closes #4161 + + What's New in Pylint 2.7.2? =========================== Release date: 2021-02-28 diff --git a/pylint/__init__.py b/pylint/__init__.py index 1fd6ffd16..20bb554d2 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -8,6 +8,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/COPYING +import os import sys from pylint.__pkginfo__ import version as __version__ @@ -44,4 +45,26 @@ def run_symilar(): SimilarRun(sys.argv[1:]) +def modify_sys_path() -> None: + """Modify sys path for execution as Python module. + + Strip out the current working directory from sys.path. + Having the working directory in `sys.path` means that `pylint` might + inadvertently import user code from modules having the same name as + stdlib or pylint's own modules. + CPython issue: https://bugs.python.org/issue33053 + + - Remove the first entry. This will always be either "" or the working directory + - Remove the working directory from the second and third entries. This can + occur if PYTHONPATH includes a ":" at the beginning or the end. + https://github.com/PyCQA/pylint/issues/3636 + - Don't remove the working directory from the rest. It will be included + if pylint is installed in an editable configuration (as the last item). + https://github.com/PyCQA/pylint/issues/4161 + """ + sys.path = [ + p for i, p in enumerate(sys.path) if i > 0 and not (i < 3 and p == os.getcwd()) + ] + + __all__ = ["__version__"] diff --git a/pylint/__main__.py b/pylint/__main__.py index 89bd19924..4d7653718 100644 --- a/pylint/__main__.py +++ b/pylint/__main__.py @@ -3,16 +3,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/COPYING -import os -import sys - import pylint -# Strip out the current working directory from sys.path. -# Having the working directory in `sys.path` means that `pylint` might -# inadvertently import user code from modules having the same name as -# stdlib or pylint's own modules. -# CPython issue: https://bugs.python.org/issue33053 -sys.path = [p for p in sys.path if p not in ("", os.getcwd())] - +pylint.modify_sys_path() pylint.run_pylint() diff --git a/tests/test_self.py b/tests/test_self.py index 5b55f6cca..42ef732ec 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -42,12 +42,16 @@ import subprocess import sys import textwrap import warnings +from copy import copy from io import StringIO from os.path import abspath, dirname, join +from typing import Generator from unittest import mock +from unittest.mock import patch import pytest +from pylint import modify_sys_path from pylint.constants import MAIN_CHECKER_NAME, MSG_TYPES_STATUS from pylint.lint import Run from pylint.reporters import JSONReporter @@ -699,6 +703,71 @@ class TestRunTC: code=0, ) + @staticmethod + def test_modify_sys_path() -> None: + @contextlib.contextmanager + def test_sys_path() -> Generator[None, None, None]: + original_path = sys.path + try: + yield + finally: + sys.path = original_path + + with test_sys_path(), patch("os.getcwd") as mock_getcwd: + cwd = "/tmp/pytest-of-root/pytest-0/test_do_not_import_files_from_0" + mock_getcwd.return_value = cwd + + paths = [ + cwd, + cwd, + "/usr/local/lib/python39.zip", + "/usr/local/lib/python3.9", + "/usr/local/lib/python3.9/lib-dynload", + "/usr/local/lib/python3.9/site-packages", + ] + sys.path = copy(paths) + modify_sys_path() + assert sys.path == paths[2:] + + paths = [ + cwd, + "/custom_pythonpath", + cwd, + "/usr/local/lib/python39.zip", + "/usr/local/lib/python3.9", + "/usr/local/lib/python3.9/lib-dynload", + "/usr/local/lib/python3.9/site-packages", + ] + sys.path = copy(paths) + modify_sys_path() + assert sys.path == [paths[1]] + paths[3:] + + paths = [ + "", + cwd, + "/custom_pythonpath", + "/usr/local/lib/python39.zip", + "/usr/local/lib/python3.9", + "/usr/local/lib/python3.9/lib-dynload", + "/usr/local/lib/python3.9/site-packages", + ] + sys.path = copy(paths) + modify_sys_path() + assert sys.path == paths[2:] + + paths = [ + "", + cwd, + "/usr/local/lib/python39.zip", + "/usr/local/lib/python3.9", + "/usr/local/lib/python3.9/lib-dynload", + "/usr/local/lib/python3.9/site-packages", + cwd, + ] + sys.path = copy(paths) + modify_sys_path() + assert sys.path == paths[2:] + @staticmethod def test_do_not_import_files_from_local_directory(tmpdir): p_astroid = tmpdir / "astroid.py" -- cgit v1.2.1