Skip to content
Open
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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
56 changes: 56 additions & 0 deletions ci/test_custom_linters.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,68 @@
"""Custom lint tests."""

import ast
from pathlib import Path

import pytest
import yaml
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."""
Expand Down
2 changes: 1 addition & 1 deletion src/mock_vws/_query_validators/accept_header_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/mock_vws/_query_validators/auth_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down