Skip to content

Commit e6c3323

Browse files
authored
ci: cover consumer fixtures in style and strict typing gates (#414)
Fixes #393 Extend formatting, lint, and strict typing checks to the compatibility consumers and example applications. Discover Python sources so future consumer directories remain covered. ## Branch maintenance Refs #426. Targets `main`. The branch was refreshed without rewriting history to include `main` at `a576cc279739eae5e4cfc33ffab2a7fb56de24de`. The already-merged calibration patch is absent from this review diff; the original issue patch is preserved. ## Current-head validation At `857e52e8ecc9058edd2a7a820f7b00175f41219a`: uv lock freshness and baseline, runtime, strict typing, style, and contracts passed locally with all declared extras. Runtime result: 608 passed, 1 warning, 262 subtests passed in 9.23s. Hosted checks: 7/7 required checks passed; 0 checks pending; 0 unsuccessful checks at 2026-10-04T14:19:40.760978+00:00. See the PR Checks tab and #426 for subsequent results.
1 parent 30bcb1f commit e6c3323

12 files changed

Lines changed: 117 additions & 12 deletions

File tree

‎compatibility/consumers/atlas_click/src/atlas_click/cli.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
from __future__ import annotations
44

5-
import click
65
import base_cli
6+
import click
77

88

99
@click.group(name="atlas-consumer", help="Inventory resources managed by Atlas.")

‎compatibility/consumers/atlas_click/tests/test_consumer.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from pathlib import Path
55

66
import base_cli
7-
87
from atlas_click.cli import command
98

109

‎compatibility/consumers/beacon_typer/src/beacon_typer/cli.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import base_cli
66
import typer
77

8-
98
cli = typer.Typer(help="Deploy Beacon services with typed parameters.")
109

1110

‎compatibility/consumers/beacon_typer/tests/test_consumer.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from pathlib import Path
55

66
import base_cli
7-
87
from beacon_typer.cli import command
98

109

‎compatibility/consumers/cinder_automation/src/cinder_automation/cli.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
from __future__ import annotations
44

5-
import click
6-
import base_cli
5+
from typing import Any
76

7+
import base_cli
8+
import click
89

910
app = base_cli.App(
1011
name="cinder-consumer",
@@ -27,7 +28,7 @@
2728
default="json",
2829
show_default=True,
2930
)
30-
def reconcile(ctx: base_cli.Context, target: str, output_format: str) -> None:
31+
def reconcile(ctx: base_cli.Context[Any, Any, Any], target: str, output_format: str) -> None:
3132
"""Publish the result of one idempotent reconciliation step."""
3233

3334
action = "would-reconcile" if ctx.dry_run else "reconciled"

‎compatibility/consumers/cinder_automation/tests/test_consumer.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from pathlib import Path
55

66
import base_cli
7-
87
from cinder_automation.cli import app
98

109

‎docs/testing.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,22 @@ The full gate writes a machine-readable result to
5555
`$BASE_CLI_VALIDATION_RESULT` (or `/tmp/base-cli-validation-result.json`). If
5656
Node.js is unavailable, the result is marked `partial`, the gate exits with
5757
status `2`, and it cannot be reported as an authoritative pass.
58+
59+
Examples and compatibility consumers use the fully parameterized context type
60+
when strict mypy checks a callback directly:
61+
62+
```python
63+
from typing import Any
64+
import base_cli
65+
66+
def main(ctx: base_cli.Context[Any, Any, Any]) -> None:
67+
...
68+
```
69+
70+
### Consumer source quality
71+
72+
The style gate runs Ruff over the entire repository with its standard generated-file
73+
exclusions. The typing gate checks every Git-visible Python source outside `lib/`
74+
(checked separately), `scripts/` (validation tools), and `tests/` (test harnesses)
75+
with strict mypy. This includes example and compatibility consumer packages and
76+
new top-level source directories; untracked sources are included during development.

‎examples/automation_observability_app/src/automation_observability_app/cli.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from typing import Any
6+
57
import base_cli
68
import click
79

@@ -35,7 +37,7 @@
3537
)
3638
@base_cli.option("--api-token", hidden=True, help="Optional secret for a real adapter.")
3739
def run(
38-
ctx: base_cli.Context,
40+
ctx: base_cli.Context[Any, Any, Any],
3941
target: str,
4042
output_format: str,
4143
api_token: str | None,

‎examples/minimal_cli/src/minimal_cli/cli.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from typing import Any
6+
57
import base_cli
68

79
app = base_cli.App(
@@ -13,7 +15,7 @@
1315

1416
@app.command()
1517
@base_cli.option("--name", required=True, help="Name to greet.")
16-
def greet(ctx: base_cli.Context, name: str) -> None:
18+
def greet(ctx: base_cli.Context[Any, Any, Any], name: str) -> None:
1719
"""Print a deterministic greeting."""
1820

1921
ctx.log.info("greeting requested for %s", name)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Type-check every example and compatibility consumer at strict settings."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
import subprocess
7+
import sys
8+
from pathlib import Path
9+
10+
11+
def main() -> int:
12+
root = Path(__file__).resolve().parents[1]
13+
# Git discovery also includes new, untracked source directories. Infrastructure
14+
# scripts/tests have separate runtime gates; all product/example code is typed.
15+
result = subprocess.run(
16+
["git", "ls-files", "--cached", "--others", "--exclude-standard", "--", "*.py"],
17+
cwd=root,
18+
check=True,
19+
capture_output=True,
20+
text=True,
21+
)
22+
files = sorted(set(result.stdout.splitlines()))
23+
excluded = {"lib", "scripts", "tests"}
24+
sources = [name for name in files if Path(name).parts[0] not in excluded]
25+
if not sources:
26+
raise RuntimeError("No consumer Python sources found")
27+
print("Consumer typing sources:")
28+
print("\n".join(f"- {source}" for source in sources))
29+
return subprocess.run(
30+
[sys.executable, "-m", "mypy", "--strict", "--explicit-package-bases", *sources],
31+
cwd=root,
32+
env={
33+
**os.environ,
34+
"MYPYPATH": os.pathsep.join(
35+
str(p)
36+
for p in [
37+
root / "lib/python",
38+
*sorted(root.glob("examples/*/src")),
39+
*sorted(root.glob("compatibility/consumers/*/src")),
40+
]
41+
),
42+
},
43+
check=False,
44+
).returncode
45+
46+
47+
if __name__ == "__main__":
48+
raise SystemExit(main())

0 commit comments

Comments
 (0)