diff --git a/README.md b/README.md index 05f91453..045ba03a 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ for family, grp in itertools.groupby(collected.checks.items(), key=lambda x: x[1 - [`PY004`](https://learn.scientific-python.org/development/guides/packaging-simple#PY004): Has docs folder - [`PY005`](https://learn.scientific-python.org/development/guides/packaging-simple#PY005): Has tests folder - [`PY006`](https://learn.scientific-python.org/development/guides/style#PY006): Has pre-commit config -- [`PY007`](https://learn.scientific-python.org/development/guides/tasks#PY007): Supports an easy task runner (nox or tox) +- [`PY007`](https://learn.scientific-python.org/development/guides/tasks#PY007): Supports an easy task runner (nox, tox, pixi, etc.) ### PyProject - [`PP002`](https://learn.scientific-python.org/development/guides/packaging-simple#PP002): Has a proper build-system table diff --git a/pyproject.toml b/pyproject.toml index d995202e..f66b6abf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,10 +113,12 @@ strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] warn_unreachable = true disallow_untyped_defs = false +disallow_incomplete_defs = false [[tool.mypy.overrides]] module = "sp_repo_review.*" disallow_untyped_defs = true +disallow_incomplete_defs = true [tool.pylint] diff --git a/src/sp_repo_review/checks/general.py b/src/sp_repo_review/checks/general.py index 94112994..d401dcb5 100644 --- a/src/sp_repo_review/checks/general.py +++ b/src/sp_repo_review/checks/general.py @@ -104,7 +104,7 @@ def check(root: Traversable) -> bool: class PY007(General): - "Supports an easy task runner (nox or tox)" + "Supports an easy task runner (nox, tox, pixi, etc.)" url = mk_url("tasks") @@ -119,6 +119,8 @@ def check(root: Traversable, pyproject: dict[str, Any]) -> bool: return True if root.joinpath("tox.ini").is_file(): return True + if root.joinpath("pixi.toml").is_file(): + return True match pyproject.get("tool", {}): case {"hatch": {"envs": object()}}: return True @@ -126,6 +128,12 @@ def check(root: Traversable, pyproject: dict[str, Any]) -> bool: return True case {"tox": object()}: return True + case {"pixi": {"tasks": {}}}: + return True + case {"pixi": {"feature": feats}} if any( + "tasks" in feat for feat in feats.values() + ): + return True case _: return False diff --git a/tests/test_general.py b/tests/test_general.py new file mode 100644 index 00000000..1eb325b1 --- /dev/null +++ b/tests/test_general.py @@ -0,0 +1,136 @@ +from pathlib import Path + +import pytest +from repo_review.testing import compute_check + +from sp_repo_review._compat import tomllib + + +def test_py001(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + simple.joinpath("pyproject.toml").touch() + assert compute_check("PY001", package=simple).result + + +def test_py001_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + assert not compute_check("PY001", package=simple).result + + +@pytest.mark.parametrize("readme", ["README.md", "README.rst"]) +def test_py002(tmp_path: Path, readme: str): + simple = tmp_path / "simple" + simple.mkdir() + simple.joinpath(readme).touch() + assert compute_check("PY002", root=simple).result + + +def test_py002_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + assert not compute_check("PY002", root=simple).result + + +@pytest.mark.parametrize("license", ["LICENSE", "LICENCE", "COPYING"]) +def test_py003(tmp_path: Path, license: str): + simple = tmp_path / "simple" + simple.mkdir() + simple.joinpath(license).touch() + assert compute_check("PY003", package=simple).result + + +def test_py003_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + assert not compute_check("PY003", package=simple).result + + +def test_py004(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + simple.joinpath("docs").mkdir() + assert compute_check("PY004", package=simple).result + + +def test_py004_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + assert not compute_check("PY004", package=simple).result + + +def test_py005(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + simple.joinpath("tests").mkdir() + assert compute_check("PY005", package=simple).result + + +def test_py005_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + assert not compute_check("PY005", package=simple).result + + +def test_py005_src(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + src = simple.joinpath("src") + src.mkdir() + pkg = src.joinpath("pkg") + pkg.mkdir() + pkg.joinpath("test").mkdir() + assert compute_check("PY005", package=simple).result + + +def test_py005_src_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + src = simple.joinpath("src") + src.mkdir() + assert not compute_check("PY005", package=simple).result + + +def test_py006(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + simple.joinpath(".pre-commit-config.yaml").touch() + assert compute_check("PY006", root=simple).result + + +def test_py006_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + assert not compute_check("PY006", root=simple).result + + +@pytest.mark.parametrize("runnerfile", ["noxfile.py", "tox.ini", "pixi.toml"]) +def test_py007(tmp_path: Path, runnerfile: str): + simple = tmp_path / "simple" + simple.mkdir() + simple.joinpath(runnerfile).touch() + assert compute_check("PY007", root=simple, pyproject={}).result + + +@pytest.mark.parametrize( + "section", + [ + "[tool.hatch.envs]", + "[tool.spin]", + "[tool.tox]", + "[tool.pixi.tasks]", + "[tool.pixi.feature.thing.tasks]", + ], +) +def test_py007_pyproject_sections(tmp_path: Path, section: str): + pyproject = tomllib.loads(section) + simple = tmp_path / "simple" + simple.mkdir() + assert compute_check("PY007", root=simple, pyproject=pyproject).result + + +def test_py007_missing(tmp_path: Path): + simple = tmp_path / "simple" + simple.mkdir() + assert not compute_check("PY007", root=simple, pyproject={}).result