diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0565f7bd..57dded4dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -413,7 +413,7 @@ repos: - *uv_version - id: yamlfix - name: pyproject-fmt + name: yamlfix entry: uv run --extra=dev yamlfix language: python types_or: [yaml] diff --git a/ci/test_custom_linters.py b/ci/test_custom_linters.py index 3ba81680b..6988b1cac 100644 --- a/ci/test_custom_linters.py +++ b/ci/test_custom_linters.py @@ -1,5 +1,6 @@ """Custom lint tests.""" +import ast from pathlib import Path import pytest @@ -7,6 +8,61 @@ from beartype import beartype +def test_pre_commit_hook_identifiers_unique( + request: pytest.FixtureRequest, +) -> None: + """Each pre-commit hook has a unique ID and display name.""" + config_file = request.config.rootpath / ".pre-commit-config.yaml" + config = yaml.safe_load(stream=config_file.read_text()) + hooks = [ + hook + for repository in config["repos"] + for hook in repository.get("hooks", []) + ] + hook_ids = [hook["id"] for hook in hooks] + hook_names = [hook.get("name", hook["id"]) for hook in hooks] + + assert len(hook_ids) == len(set(hook_ids)), ( + "Pre-commit hook IDs must be unique." + ) + assert len(hook_names) == len(set(hook_names)), ( + "Pre-commit hook names must be unique." + ) + + +def test_validate_and_run_functions_are_keyword_only( + request: pytest.FixtureRequest, +) -> None: + """Validation and runner APIs do not accept positional arguments.""" + violations: list[str] = [] + source_root = request.config.rootpath / "src" + + for source_file in source_root.rglob("*.py"): + module = ast.parse(source_file.read_text()) + for function in ast.walk(module): + if not isinstance( + function, ast.FunctionDef | ast.AsyncFunctionDef + ): + continue + if not function.name.startswith(("validate_", "run_")): + continue + positional_arguments = [ + argument.arg + for argument in [ + *function.args.posonlyargs, + *function.args.args, + ] + if argument.arg not in {"self", "cls"} + ] + if positional_arguments: + path = source_file.relative_to(request.config.rootpath) + violations.append(f"{path}:{function.lineno} {function.name}") + + assert not violations, "Functions must be keyword-only: " + ", ".join( + violations + ) + + @beartype def _ci_patterns(*, repository_root: Path) -> set[str]: """Return the CI patterns given in the CI configuration file.""" diff --git a/src/mock_vws/_query_validators/accept_header_validators.py b/src/mock_vws/_query_validators/accept_header_validators.py index fe3e966f6..e2387f913 100644 --- a/src/mock_vws/_query_validators/accept_header_validators.py +++ b/src/mock_vws/_query_validators/accept_header_validators.py @@ -11,7 +11,7 @@ @beartype -def validate_accept_header(request_headers: Mapping[str, str]) -> None: +def validate_accept_header(*, request_headers: Mapping[str, str]) -> None: """Validate the accept header. Args: diff --git a/src/mock_vws/_query_validators/auth_validators.py b/src/mock_vws/_query_validators/auth_validators.py index 13553efa4..ddd4310ea 100644 --- a/src/mock_vws/_query_validators/auth_validators.py +++ b/src/mock_vws/_query_validators/auth_validators.py @@ -88,6 +88,7 @@ def validate_client_key_exists( @beartype def validate_auth_header_has_signature( + *, request_headers: Mapping[str, str], ) -> None: """Validate the authorization header includes a signature.