diff --git a/pyproject.toml b/pyproject.toml index 0712e1d..f3dd12b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,11 +14,11 @@ requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry.dependencies] +pytest = ">=5" python = "^3.8" ruff = ">=0.0.242" [tool.poetry.group.dev.dependencies] -pytest = "^7.2.0" pytest-mock = "^3.10.0" pytest-cov = "^4.1.0" diff --git a/pytest_ruff.py b/pytest_ruff/__init__.py similarity index 68% rename from pytest_ruff.py rename to pytest_ruff/__init__.py index 157fb12..dea76a7 100644 --- a/pytest_ruff.py +++ b/pytest_ruff/__init__.py @@ -1,40 +1,12 @@ from subprocess import Popen, PIPE -from pathlib import Path - -# Python<=3.8 don't support typing with builtin dict. -from typing import Dict - import pytest -try: - from pytest import StashKey -except ImportError: - from _pytest.store import StoreKey as StashKey - from ruff.__main__ import find_ruff_bin -PYTEST_VER = tuple(int(x) for x in pytest.__version__.split(".")[:2]) +from pytest_ruff._pytest_compat import get_stash, make_path_kwargs, set_stash HISTKEY = "ruff/mtimes" -_MTIMES_STASH_KEY = StashKey[Dict[str, float]]() - - -def _stash(config): - try: - return config.stash - except AttributeError: - return config._store - - -def _make_path_kwargs(p): - """ - Make keyword arguments passing either path or fspath, depending on pytest version. - - In pytest 7.0, the `fspath` argument to Nodes has been deprecated, so we pass `path` - instead. - """ - return dict(path=Path(p)) if PYTEST_VER >= (7, 0) else dict(fspath=p) def pytest_addoption(parser): @@ -51,7 +23,7 @@ def pytest_configure(config): if not config.option.ruff or not hasattr(config, "cache"): return - _stash(config)[_MTIMES_STASH_KEY] = config.cache.get(HISTKEY, {}) + set_stash(config, config.cache.get(HISTKEY, {})) def pytest_collect_file(path, parent, fspath=None): @@ -62,7 +34,7 @@ def pytest_collect_file(path, parent, fspath=None): if path.ext != ".py": return - return RuffFile.from_parent(parent, **_make_path_kwargs(path)) + return RuffFile.from_parent(parent, **make_path_kwargs(path)) def pytest_sessionfinish(session, exitstatus): @@ -75,7 +47,7 @@ def pytest_sessionfinish(session, exitstatus): # It works fine if pytest-xdist is not being used. if not hasattr(config, "workerinput"): cache = config.cache.get(HISTKEY, {}) - cache.update(_stash(config)[_MTIMES_STASH_KEY]) + cache.update(get_stash(config)) config.cache.set(HISTKEY, cache) @@ -120,16 +92,17 @@ def __init__(self, *k, **kwargs): self.add_marker("ruff") def setup(self): - ruffmtimes = _stash(self.config).get(_MTIMES_STASH_KEY, {}) + ruffmtimes = get_stash(self.config) self._ruffmtime = self.fspath.mtime() - old = ruffmtimes.get(str(self.fspath)) - if old == self._ruffmtime: - pytest.skip("file previously passed ruff checks") + if ruffmtimes: + old = ruffmtimes.get(str(self.fspath)) + if old == self._ruffmtime: + pytest.skip("file previously passed ruff checks") def runtest(self): self.handler(path=self.fspath) - ruffmtimes = _stash(self.config).get(_MTIMES_STASH_KEY, None) + ruffmtimes = get_stash(self.config) if ruffmtimes: ruffmtimes[str(self.fspath)] = self._ruffmtime diff --git a/pytest_ruff/_pytest_compat.py b/pytest_ruff/_pytest_compat.py new file mode 100644 index 0000000..2d2db7d --- /dev/null +++ b/pytest_ruff/_pytest_compat.py @@ -0,0 +1,46 @@ +from pathlib import Path + +# Python<=3.8 don't support typing with builtin dict. +from typing import Dict + +import pytest + +try: + from pytest import Stash, StashKey +except ImportError: + import _pytest.store + + Stash = _pytest.store.Store + StashKey = _pytest.store.StoreKey + +PYTEST_VER = tuple(int(x) for x in pytest.__version__.split(".")[:2]) +_MTIMES_STASH_KEY = StashKey[Dict[str, float]]() + + +def make_path_kwargs(p): + """ + Make keyword arguments passing either path or fspath, depending on pytest version. + + In pytest 7.0, the `fspath` argument to Nodes has been deprecated, so we pass `path` + instead. + """ + return dict(path=Path(p)) if PYTEST_VER >= (7, 0) else dict(fspath=p) + + +def get_stash_object(config): + try: + stash = config.stash + except AttributeError: + stash = config._store + return stash + + +def get_stash(config): + missing = object() + stash = get_stash_object(config).get(_MTIMES_STASH_KEY, default=missing) + assert stash is not missing + return stash + + +def set_stash(config, value): + get_stash_object(config)[_MTIMES_STASH_KEY] = value diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 3d8e67b..7775a22 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3,11 +3,8 @@ import pytest -try: - from pytest import Stash -except ImportError: - from _pytest.store import Store as Stash import pytest_ruff +from pytest_ruff._pytest_compat import Stash, get_stash def test_configure(mocker): @@ -17,19 +14,18 @@ def test_configure(mocker): stash=Stash(), ) pytest_ruff.pytest_configure(config) - assert config.stash[pytest_ruff._MTIMES_STASH_KEY] == mocker.sentinel.cache + assert get_stash(config) == mocker.sentinel.cache def test_configure_without_ruff(mocker): config = mocker.Mock( option=mocker.Mock(ruff=False), - stash=Stash(), # Mocking to `not hasattr(config, "cache")`. spec=["addinivalue_line", "option", "stash"], ) + set_stash_mock = mocker.patch("pytest_ruff.set_stash", spec=True) pytest_ruff.pytest_configure(config) - with pytest.raises(KeyError): - config.stash[pytest_ruff._MTIMES_STASH_KEY] + set_stash_mock.assert_not_called() def test_check_file(): @@ -65,7 +61,7 @@ def test_pytest_ruff_format(): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).communicate() - assert err == b"" + assert err.decode() == "" assert "File would be reformatted" in out.decode("utf-8") @@ -81,5 +77,5 @@ def test_pytest_ruff_noformat(): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).communicate() - assert err == b"" + assert err.decode() == "" assert "File would be reformatted" not in out.decode("utf-8")