diff options
author | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2022-05-05 23:02:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-05 23:02:24 +0200 |
commit | 58c4f370c7395d9d4e202ba83623768abcc3ac24 (patch) | |
tree | 30087c371bcfbf2debd5cc191fc2b3cf5d04061f | |
parent | 40f50af81c493be83ba89bef814e117626dfbb25 (diff) | |
download | pylint-git-58c4f370c7395d9d4e202ba83623768abcc3ac24.tar.gz |
Move caching to pylint.lint from pylint.config and deprecate (#6468)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>
-rw-r--r-- | pylint/config/__init__.py | 54 | ||||
-rw-r--r-- | pylint/lint/caching.py | 63 | ||||
-rw-r--r-- | tests/lint/test_caching.py | 68 | ||||
-rw-r--r-- | tests/lint/unittest_expand_modules.py | 9 | ||||
-rw-r--r-- | tests/test_deprecation.py | 9 |
5 files changed, 173 insertions, 30 deletions
diff --git a/pylint/config/__init__.py b/pylint/config/__init__.py index 485a08edd..7dc96f0cf 100644 --- a/pylint/config/__init__.py +++ b/pylint/config/__init__.py @@ -4,11 +4,6 @@ from __future__ import annotations -import os -import pathlib -import pickle -import sys - __all__ = [ "ConfigurationMixIn", # Deprecated "find_default_config_files", @@ -21,8 +16,12 @@ __all__ = [ "PYLINTRC", "USER_HOME", # Compatibility with the old API "PYLINT_HOME", # Compatibility with the old API + "save_results", # Compatibility with the old API # Deprecated + "load_results", # Compatibility with the old API # Deprecated ] +import warnings + from pylint.config.arguments_provider import UnsupportedAction from pylint.config.configuration_mixin import ConfigurationMixIn from pylint.config.environment_variable import PYLINTRC @@ -38,32 +37,27 @@ from pylint.constants import PYLINT_HOME, USER_HOME from pylint.utils import LinterStats -def _get_pdata_path(base_name: str, recurs: int) -> pathlib.Path: - base_name = base_name.replace(os.sep, "_") - return pathlib.Path(PYLINT_HOME) / f"{base_name}{recurs}.stats" - - def load_results(base: str) -> LinterStats | None: - data_file = _get_pdata_path(base, 1) - try: - with open(data_file, "rb") as stream: - data = pickle.load(stream) - if not isinstance(data, LinterStats): - raise TypeError - return data - except Exception: # pylint: disable=broad-except - return None + # TODO: 3.0: Remove deprecated function + # pylint: disable=import-outside-toplevel + from pylint.lint.caching import load_results as _real_load_results + + warnings.warn( + "'pylint.config.load_results' is deprecated, please use " + "'pylint.lint.load_results' instead. This will be removed in 3.0.", + DeprecationWarning, + ) + return _real_load_results(base, PYLINT_HOME) def save_results(results: LinterStats, base: str) -> None: - if not os.path.exists(PYLINT_HOME): - try: - os.makedirs(PYLINT_HOME) - except OSError: - print(f"Unable to create directory {PYLINT_HOME}", file=sys.stderr) - data_file = _get_pdata_path(base, 1) - try: - with open(data_file, "wb") as stream: - pickle.dump(results, stream) - except OSError as ex: - print(f"Unable to create file {data_file}: {ex}", file=sys.stderr) + # TODO: 3.0: Remove deprecated function + # pylint: disable=import-outside-toplevel + from pylint.lint.caching import save_results as _real_save_results + + warnings.warn( + "'pylint.config.save_results' is deprecated, please use " + "'pylint.lint.save_results' instead. This will be removed in 3.0.", + DeprecationWarning, + ) + return _real_save_results(results, base, PYLINT_HOME) diff --git a/pylint/lint/caching.py b/pylint/lint/caching.py new file mode 100644 index 000000000..ec5a9f78a --- /dev/null +++ b/pylint/lint/caching.py @@ -0,0 +1,63 @@ +# 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 + +from __future__ import annotations + +import pickle +import sys +import warnings +from pathlib import Path + +from pylint.constants import PYLINT_HOME +from pylint.utils import LinterStats + + +def _get_pdata_path( + base_name: Path, recurs: int, pylint_home: Path = Path(PYLINT_HOME) +) -> Path: + underscored_name = "_".join(str(p) for p in base_name.parts) + return pylint_home / f"{underscored_name}_{recurs}.stats" + + +def load_results( + base: str | Path, pylint_home: str | Path = PYLINT_HOME +) -> LinterStats | None: + base = Path(base) + pylint_home = Path(pylint_home) + data_file = _get_pdata_path(base, 1, pylint_home) + + if not data_file.exists(): + return None + + try: + with open(data_file, "rb") as stream: + data = pickle.load(stream) + if not isinstance(data, LinterStats): + warnings.warn( + "You're using an old pylint cache with invalid data following " + f"an upgrade, please delete '{data_file}'.", + UserWarning, + ) + raise TypeError + return data + except Exception: # pylint: disable=broad-except + # There's an issue with the cache but we just continue as if it isn't there + return None + + +def save_results( + results: LinterStats, base: str | Path, pylint_home: str | Path = PYLINT_HOME +) -> None: + base = Path(base) + pylint_home = Path(pylint_home) + try: + pylint_home.mkdir(parents=True, exist_ok=True) + except OSError: # pragma: no cover + print(f"Unable to create directory {pylint_home}", file=sys.stderr) + data_file = _get_pdata_path(base, 1) + try: + with open(data_file, "wb") as stream: + pickle.dump(results, stream) + except OSError as ex: # pragma: no cover + print(f"Unable to create file {data_file}: {ex}", file=sys.stderr) diff --git a/tests/lint/test_caching.py b/tests/lint/test_caching.py new file mode 100644 index 000000000..8d3f7afd4 --- /dev/null +++ b/tests/lint/test_caching.py @@ -0,0 +1,68 @@ +# 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 + +# Pytest fixtures work like this by design +# pylint: disable=redefined-outer-name + +from pathlib import Path + +import pytest + +from pylint.constants import PYLINT_HOME +from pylint.lint.caching import _get_pdata_path, load_results, save_results +from pylint.utils import LinterStats +from pylint.utils.linterstats import BadNames + +PYLINT_HOME_PATH = Path(PYLINT_HOME) + + +@pytest.mark.parametrize( + "path,recur,expected", + [ + ["", 1, PYLINT_HOME_PATH / "_1.stats"], + ["", 2, PYLINT_HOME_PATH / "_2.stats"], + ["a/path", 42, PYLINT_HOME_PATH / "a_path_42.stats"], + ], +) +def test__get_pdata_path(path: str, recur: int, expected: Path) -> None: + assert _get_pdata_path(Path(path), recur) == expected + + +@pytest.fixture +def linter_stats() -> LinterStats: + return LinterStats( + bad_names=BadNames( + argument=1, + attr=2, + klass=3, + class_attribute=4, + class_const=5, + const=6, + inlinevar=7, + function=8, + method=9, + module=10, + variable=11, + typevar=12, + ) + ) + + +@pytest.mark.parametrize("path", [".tests/", ".tests/a/path/"]) +def test_save_and_load_result(path: str, linter_stats: LinterStats) -> None: + save_results(linter_stats, path) + loaded = load_results(path) + assert loaded is not None + assert loaded.bad_names == linter_stats.bad_names + + +@pytest.mark.parametrize("path", [".tests", ".tests/a/path/"]) +def test_save_and_load_not_a_linter_stats(path: str) -> None: + # type ignore because this is what we're testing + save_results(1, path) # type: ignore[arg-type] + with pytest.warns(UserWarning) as warn: + loaded = load_results(path) + assert loaded is None + warn_str = str(warn.pop().message) + assert "old pylint cache with invalid data" in warn_str diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 36fd7ed4d..1ce5b6c4e 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -69,6 +69,14 @@ test_pylinter = { "path": str(TEST_DIRECTORY / "lint/test_pylinter.py"), } +test_caching = { + "basename": "lint", + "basepath": INIT_PATH, + "isarg": False, + "name": "lint.test_caching", + "path": str(TEST_DIRECTORY / "lint/test_caching.py"), +} + test_namespace_packages = { "basename": "lint", "basepath": INIT_PATH, @@ -106,6 +114,7 @@ class TestExpandModules(CheckerTestCase): [str(Path(__file__).parent)], [ init_of_package, + test_caching, test_namespace_packages, test_pylinter, test_utils, diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py index a8d857b3a..2fd0a56c3 100644 --- a/tests/test_deprecation.py +++ b/tests/test_deprecation.py @@ -12,6 +12,7 @@ import pytest from pylint.checkers import BaseChecker from pylint.checkers.mapreduce_checker import MapReduceMixin +from pylint.config import load_results, save_results from pylint.interfaces import ( IAstroidChecker, IChecker, @@ -78,3 +79,11 @@ def test_interfaces() -> None: IChecker() with pytest.warns(DeprecationWarning): ITokenChecker() + + +def test_load_and_save_results() -> None: + """Test that load_results and save_results are deprecated.""" + with pytest.warns(DeprecationWarning): + save_results(object(), "") # type: ignore[arg-type] + with pytest.warns(DeprecationWarning): + load_results("") |