Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"autonumbering",
"autoupdate",
"bdist",
"capsys",
"celltoolbar",
"codecov",
"colab",
Expand All @@ -71,6 +72,7 @@
"nbcell",
"nbformat",
"nbhooks",
"nbmake",
"nbqa",
"nbstripout",
"noqa",
Expand Down Expand Up @@ -102,6 +104,7 @@
"tomlkit",
"tomlsort",
"unittests",
"usefixtures",
"venv"
],
"language": "en-US",
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ uv.lock

# Settings
.claude/
.codex/
.idea/
AGENTS.md
CLAUDE.md
**.code-workspace

Expand Down
2 changes: 1 addition & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ coverage:
status:
project:
default:
target: 33%
target: 85%
threshold: 1%
base: auto
if_no_uploads: error
Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ style = [
test = [
"pytest",
"pytest-cov",
"pytest-describe",
]
types = [
"pytest",
Expand Down Expand Up @@ -125,6 +126,7 @@ allow-labels = true
no-pypi = true

[tool.compwa.policy.python]
branch-coverage = false
keep-local-precommit = true
type-checker = ["ty"]

Expand All @@ -134,7 +136,7 @@ exclude_also = [
]

[tool.coverage.run]
branch = true
branch = false
omit = [
"benchmarks/**/*.py",
"docs/**/*.ipynb",
Expand Down Expand Up @@ -205,7 +207,7 @@ heading = "Testing"
cmd = """
pytest \
--cov=compwa_policy \
--cov-fail-under=33 \
--cov-fail-under=85 \
--cov-report=html \
--cov-report=xml \
${paths}
Expand Down Expand Up @@ -378,9 +380,11 @@ split-on-trailing-comma = false
"src/compwa_policy/config.py" = ["D100"]
"tests/*" = [
"ANN",
"C901",
"D",
"INP001",
"PLC2701",
"PLR0915",
"PLR2004",
"PLR6301",
"RUF069",
Expand Down
2 changes: 1 addition & 1 deletion src/compwa_policy/format/cspell.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def _update_config_content() -> None:
fixed_sections = sorted(
section_name
for section_name, section in config.items()
if section != original_config[section_name]
if section != original_config.get(section_name)
)
error_message = __express_list_of_sections(fixed_sections)
error_message += f" in {CONFIG_PATH.cspell} has been updated."
Expand Down
2 changes: 1 addition & 1 deletion src/compwa_policy/format/prettier.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from compwa_policy.utilities.precommit import ModifiablePrecommit

# cspell:ignore esbenp rettier
# cspell:ignore rettier
__VSCODE_EXTENSION_NAME = "esbenp.prettier-vscode"
__BADGE = """
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
Expand Down
52 changes: 31 additions & 21 deletions src/compwa_policy/python/pyright.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import TYPE_CHECKING

from compwa_policy.utilities import CONFIG_PATH, remove_lines, vscode
from compwa_policy.utilities.executor import Executor
from compwa_policy.utilities.precommit.struct import Hook, Repo
from compwa_policy.utilities.pyproject import ModifiablePyproject, complies_with_subset
from compwa_policy.utilities.toml import to_toml_array
Expand All @@ -17,15 +18,15 @@


def main(active: bool, precommit: ModifiablePrecommit) -> None:
with ModifiablePyproject.load() as pyproject:
_update_vscode_settings(active)
with Executor() as do, ModifiablePyproject.load() as pyproject:
do(_update_vscode_settings, active)
if active:
_merge_config_into_pyproject(pyproject)
_update_precommit(precommit)
_remove_excludes(pyproject)
_update_settings(pyproject)
do(_merge_config_into_pyproject, pyproject)
do(_update_precommit, precommit)
do(_remove_excludes, pyproject)
do(_update_settings, pyproject)
else:
_remove_pyright(precommit, pyproject)
do(_remove_pyright, precommit, pyproject)


def _merge_config_into_pyproject(
Expand Down Expand Up @@ -89,20 +90,29 @@ def _update_settings(pyproject: ModifiablePyproject) -> None:


def _update_vscode_settings(active: bool) -> None:
if active:
vscode.add_extension_recommendation("ms-python.vscode-pylance")
vscode.update_settings({
"python.analysis.autoImportCompletions": False,
"python.analysis.inlayHints.pytestParameters": True,
})
else:
vscode.remove_settings([
"python.analysis.autoImportCompletions",
"python.analysis.inlayHints.pytestParameters",
])
vscode.remove_extension_recommendation(
"ms-python.vscode-pylance", unwanted=True
)
with Executor() as do:
if active:
do(vscode.add_extension_recommendation, "ms-python.vscode-pylance")
do(
vscode.update_settings,
{
"python.analysis.autoImportCompletions": False,
"python.analysis.inlayHints.pytestParameters": True,
},
)
else:
do(
vscode.remove_settings,
[
"python.analysis.autoImportCompletions",
"python.analysis.inlayHints.pytestParameters",
],
)
do(
vscode.remove_extension_recommendation,
"ms-python.vscode-pylance",
unwanted=True,
)


def _remove_pyright(
Expand Down
37 changes: 20 additions & 17 deletions src/compwa_policy/python/ty.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ruamel.yaml.comments import CommentedSeq

from compwa_policy.utilities import vscode
from compwa_policy.utilities.executor import Executor
from compwa_policy.utilities.precommit.getters import find_hook
from compwa_policy.utilities.precommit.struct import Hook, Repo
from compwa_policy.utilities.pyproject import ModifiablePyproject
Expand All @@ -25,15 +26,15 @@ def main(
keep_precommit: bool,
precommit: ModifiablePrecommit,
) -> None:
with ModifiablePyproject.load() as pyproject:
_update_vscode_settings(type_checkers)
with Executor() as do, ModifiablePyproject.load() as pyproject:
do(_update_vscode_settings, type_checkers)
if "ty" in type_checkers:
_update_configuration(pyproject)
pyproject.add_dependency("ty", dependency_group=["style", "dev"])
do(_update_configuration, pyproject)
do(pyproject.add_dependency, "ty", dependency_group=["style", "dev"])
if not keep_precommit:
_update_precommit_config(precommit)
do(_update_precommit_config, precommit)
else:
_remove_ty(precommit, pyproject)
do(_remove_ty, precommit, pyproject)


def _update_vscode_settings(type_checkers: set[TypeChecker]) -> None:
Expand All @@ -42,17 +43,19 @@ def _update_vscode_settings(type_checkers: set[TypeChecker]) -> None:
"ty.diagnosticMode": "workspace",
"ty.importStrategy": "fromEnvironment",
}
if "ty" in type_checkers:
if "pyright" not in type_checkers:
vscode.remove_settings(["python.languageServer"])
vscode.add_extension_recommendation("astral-sh.ty")
vscode.update_settings(settings)
add_badge(
"[![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)"
)
else:
vscode.remove_extension_recommendation("astral-sh.ty", unwanted=True)
vscode.remove_settings([*settings, "python.languageServer"])
with Executor() as do:
if "ty" in type_checkers:
if "pyright" not in type_checkers:
do(vscode.remove_settings, ["python.languageServer"])
do(vscode.add_extension_recommendation, "astral-sh.ty")
do(vscode.update_settings, settings)
do(
add_badge,
"[![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)",
)
else:
do(vscode.remove_extension_recommendation, "astral-sh.ty", unwanted=True)
do(vscode.remove_settings, [*settings, "python.languageServer"])


def _update_configuration(pyproject: ModifiablePyproject) -> None:
Expand Down
9 changes: 9 additions & 0 deletions src/compwa_policy/repo/poe.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,15 @@ def _check_no_uv_run(pyproject: Pyproject) -> None:


def __has_uv_run(cmd: str | Sequence) -> bool:
"""Check whether a Poe task command shells out to :code:`uv run`.

>>> __has_uv_run("uv run pytest")
True
>>> __has_uv_run(["python", "-m", "pytest"])
False
>>> __has_uv_run(["uv run pytest", "coverage report"])
True
"""
if isinstance(cmd, str):
return "uv run" in cmd
if isinstance(cmd, Sequence):
Expand Down
2 changes: 0 additions & 2 deletions tests/.gitignore

This file was deleted.

118 changes: 118 additions & 0 deletions tests/cli/test_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import subprocess # noqa: S404
from pathlib import Path
from textwrap import dedent

import pytest
import typer

from compwa_policy.cli._checks import (
ALL_GROUPS,
check_dev_python_version,
compute_context,
dispatch,
run_all,
)
from compwa_policy.cli._options import build_arguments

_PYPROJECT = dedent("""
[project]
name = "my-package"
classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
""").lstrip()


def _git_commit(directory: Path) -> None:
subprocess.run(["git", "init", "-q"], cwd=directory, check=True) # noqa: S607
subprocess.run(["git", "add", "-A"], cwd=directory, check=True) # noqa: S607
git_author = ["-c", "user.name=t", "-c", "user.email=t@t"]
commit = ["git", *git_author, "commit", "-qm", "init", "--allow-empty"]
subprocess.run(commit, cwd=directory, check=True) # noqa: S603


def describe_check_dev_python_version():
def passes_without_pyproject(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
monkeypatch.chdir(tmp_path)
args = build_arguments(dev_python_version="3.12")
assert check_dev_python_version(args) == 0

def passes_for_supported_version(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
monkeypatch.chdir(tmp_path)
(tmp_path / "pyproject.toml").write_text(_PYPROJECT)
args = build_arguments(dev_python_version="3.12")
assert check_dev_python_version(args) == 0

def fails_for_unsupported_version(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
):
monkeypatch.chdir(tmp_path)
(tmp_path / "pyproject.toml").write_text(_PYPROJECT)
args = build_arguments(dev_python_version="3.9")
assert check_dev_python_version(args) == 1
assert "not listed in the supported Python versions" in capsys.readouterr().out


def describe_compute_context():
def detects_python_repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
(tmp_path / "src").mkdir()
(tmp_path / "src" / "module.py").write_text("x = 1\n")
_git_commit(tmp_path)
monkeypatch.chdir(tmp_path)
args = build_arguments(dev_python_version="3.12")
ctx = compute_context(args)
assert ctx.is_python_repo is True
assert ctx.has_notebooks is False

def respects_explicit_python_flag(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
_git_commit(tmp_path)
monkeypatch.chdir(tmp_path)
args = build_arguments(dev_python_version="3.12", python=False)
ctx = compute_context(args)
assert ctx.is_python_repo is False


def describe_all_groups():
def covers_every_subcommand_group():
assert (
frozenset({"python", "github", "env", "nb", "format", "repo"}) == ALL_GROUPS
)


def _runnable_repo(directory: Path) -> None:
(directory / ".pre-commit-config.yaml").write_text("repos: []\n")
(directory / "pyproject.toml").write_text(
'[project]\nname = "x"\nrequires-python = ">=3.12"\n'
)
(directory / "src" / "x").mkdir(parents=True)
(directory / "src" / "x" / "module.py").write_text("x = 1\n")
_git_commit(directory)


def describe_run_all():
def applies_changes_and_returns_one(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture,
):
_runnable_repo(tmp_path)
monkeypatch.chdir(tmp_path)
args = build_arguments(dev_python_version="3.12", package_manager="uv")
assert run_all(args) == 1 # pristine repo needs updates
capsys.readouterr()


def describe_dispatch():
def raises_typer_exit(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture,
):
_runnable_repo(tmp_path)
monkeypatch.chdir(tmp_path)
args = build_arguments(dev_python_version="3.12", package_manager="uv")
with pytest.raises(typer.Exit):
dispatch(args, "env")
capsys.readouterr()
Loading
Loading