chore(ci): add ruff and mypy, wire them into CI

Back the shipped py.typed promise with an enforced type check and a linter:

- Add ruff + mypy (+ types-requests) to the dev extras and dev requirements,
  with [tool.ruff]/[tool.mypy] config in pyproject.toml (mypy checks the
  package, not the tests).
- Add a lint job to the Forgejo workflow running ruff check + mypy.
- Fix the issues this surfaced: type FileLock.fd as TextIO | None, add a
  targeted type: ignore for the intentional socket.getaddrinfo monkeypatch,
  and drop an unused import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-07-07 10:28:32 +02:00
commit 9d47424fc1
8 changed files with 41 additions and 5 deletions

View file

@ -6,6 +6,24 @@ on:
pull_request: pull_request:
jobs: jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install package with dev extras
run: pip install -e ".[dev]"
- name: Ruff (lint)
run: ruff check src/ tests/
- name: Mypy (type check)
run: mypy
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:

View file

@ -17,6 +17,9 @@ dependencies = [
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"pytest>=7.4", "pytest>=7.4",
"ruff>=0.6",
"mypy>=1.11",
"types-requests",
] ]
[project.scripts] [project.scripts]
@ -27,3 +30,13 @@ where = ["src"]
[tool.setuptools.package-data] [tool.setuptools.package-data]
llamacppctl = ["py.typed"] llamacppctl = ["py.typed"]
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.mypy]
python_version = "3.10"
files = ["src/llamacppctl"]
warn_unused_ignores = true
warn_redundant_casts = true

View file

@ -2,3 +2,6 @@
# in pyproject.toml). # in pyproject.toml).
-r requirements.txt -r requirements.txt
pytest>=7.4 pytest>=7.4
ruff>=0.6
mypy>=1.11
types-requests

View file

@ -23,7 +23,6 @@ from .docker_ops import (
DockerError, DockerError,
build_run_command, build_run_command,
container_logs, container_logs,
docker_available,
format_command_for_display, format_command_for_display,
inspect_container, inspect_container,
remove_container, remove_container,

View file

@ -11,7 +11,6 @@ import shlex
import subprocess import subprocess
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Optional
from .schema import ServerConfig from .schema import ServerConfig

View file

@ -8,6 +8,7 @@ from __future__ import annotations
import fcntl import fcntl
from pathlib import Path from pathlib import Path
from typing import TextIO
class LockError(RuntimeError): class LockError(RuntimeError):
@ -17,7 +18,7 @@ class LockError(RuntimeError):
class FileLock: class FileLock:
def __init__(self, path: Path): def __init__(self, path: Path):
self.path = Path(path) self.path = Path(path)
self.fd = None self.fd: TextIO | None = None
def __enter__(self) -> "FileLock": def __enter__(self) -> "FileLock":
self.path.parent.mkdir(parents=True, exist_ok=True) self.path.parent.mkdir(parents=True, exist_ok=True)

View file

@ -202,7 +202,10 @@ def _pin_dns(host: str, allowed_ips: list):
) )
return results return results
socket.getaddrinfo = pinned # Intentional monkeypatch: pin DNS to the pre-validated addresses for the
# duration of the request (SSRF/rebinding defense). Signature differs from
# the stdlib function, hence the targeted ignore.
socket.getaddrinfo = pinned # type: ignore[assignment]
try: try:
yield yield
finally: finally:

View file

@ -15,7 +15,7 @@ from llamacppctl.http_ops import ( # noqa: E402
check_models, check_models,
wait_until_ready, wait_until_ready,
) )
from llamacppctl.schema import PromptConfig, ServerConfig # noqa: E402 from llamacppctl.schema import PromptConfig # noqa: E402
from tests.test_docker_ops import make_cfg # noqa: E402 from tests.test_docker_ops import make_cfg # noqa: E402