Coverage for src/rhiza_tools/commands/generate_badge.py: 100%
41 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-30 13:37 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-30 13:37 +0000
1#!/usr/bin/env python3
2"""Generate a coverage badge endpoint JSON for shields.io.
4This script reads a coverage report JSON file and creates a shields.io endpoint
5JSON file for a coverage badge at a specified output path.
6"""
8import json
9import sys
10from pathlib import Path
12from rhiza_tools import console
14# Coverage percentage bounds and the (minimum coverage, shields.io color) ladder,
15# ordered from highest to lowest. Higher coverage gets "greener" colors.
16MIN_COVERAGE = 0
17MAX_COVERAGE = 100
18_COLOR_THRESHOLDS: list[tuple[int, str]] = [
19 (90, "brightgreen"),
20 (80, "green"),
21 (70, "yellowgreen"),
22 (60, "yellow"),
23 (50, "orange"),
24]
25_DEFAULT_COLOR = "red"
28def get_badge_color(coverage: int) -> str:
29 """Determine badge color based on coverage percentage.
31 Colors follow a common convention where higher coverage gets "greener" colors.
33 Args:
34 coverage: Coverage percentage (0-100).
36 Returns:
37 Color name for shields.io badge.
39 Example:
40 >>> color = get_badge_color(95)
41 >>> print(color)
42 brightgreen
44 >>> color = get_badge_color(45)
45 >>> print(color)
46 red
47 """
48 for threshold, color in _COLOR_THRESHOLDS:
49 if coverage >= threshold:
50 return color
51 return _DEFAULT_COLOR
54def generate_coverage_badge_command(
55 coverage_json_path: Path,
56 output_path: Path,
57) -> None:
58 """Generate coverage badge JSON from coverage report.
60 Reads a pytest-cov generated coverage.json file and creates a shields.io
61 endpoint JSON file with appropriate color coding based on coverage percentage.
63 Args:
64 coverage_json_path: Path to the coverage.json file.
65 output_path: Path where the badge JSON should be written.
67 Raises:
68 SystemExit: If the coverage JSON is invalid or missing required data.
70 Example:
71 Generate badge from coverage report::
73 from pathlib import Path
74 generate_coverage_badge_command(
75 Path("_tests/coverage.json"),
76 Path("_book/tests/coverage-badge.json")
77 )
79 The generated JSON can be used with shields.io::
81 https://img.shields.io/endpoint?url=<url-to-badge-json>
82 """
83 # Check if coverage.json exists
84 if not coverage_json_path.exists():
85 console.warning(
86 f"Coverage JSON file not found at {coverage_json_path}, skipping badge generation",
87 )
88 return
90 console.info(f"Generating coverage badge from {coverage_json_path}...")
92 # Read and parse coverage data
93 try:
94 with coverage_json_path.open("r") as f:
95 data = json.load(f)
96 except json.JSONDecodeError as e:
97 console.error(f"Failed to parse coverage JSON: {e}")
98 sys.exit(1)
100 # Extract coverage percentage
101 try:
102 percent = data["totals"]["percent_covered"]
103 except KeyError as e:
104 console.error(f"Missing expected key in coverage JSON: {e}")
105 sys.exit(1)
107 # Round to nearest integer
108 coverage = round(percent)
110 if not MIN_COVERAGE <= coverage <= MAX_COVERAGE:
111 console.error(f"Coverage percentage {coverage} is out of valid range 0-100")
112 sys.exit(1)
114 console.info(f"Coverage: {coverage}%")
116 # Determine badge color
117 color = get_badge_color(coverage)
119 # Create output directory if it doesn't exist
120 output_path.parent.mkdir(parents=True, exist_ok=True)
122 # Generate shields.io endpoint JSON
123 badge_data = {
124 "schemaVersion": 1,
125 "label": "coverage",
126 "message": f"{coverage}%",
127 "color": color,
128 }
130 with output_path.open("w") as f:
131 json.dump(badge_data, f, indent=2)
132 f.write("\n") # Add trailing newline
134 console.info(f"Coverage badge JSON generated at {output_path}")