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

1#!/usr/bin/env python3 

2"""Generate a coverage badge endpoint JSON for shields.io. 

3 

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""" 

7 

8import json 

9import sys 

10from pathlib import Path 

11 

12from rhiza_tools import console 

13 

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" 

26 

27 

28def get_badge_color(coverage: int) -> str: 

29 """Determine badge color based on coverage percentage. 

30 

31 Colors follow a common convention where higher coverage gets "greener" colors. 

32 

33 Args: 

34 coverage: Coverage percentage (0-100). 

35 

36 Returns: 

37 Color name for shields.io badge. 

38 

39 Example: 

40 >>> color = get_badge_color(95) 

41 >>> print(color) 

42 brightgreen 

43 

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 

52 

53 

54def generate_coverage_badge_command( 

55 coverage_json_path: Path, 

56 output_path: Path, 

57) -> None: 

58 """Generate coverage badge JSON from coverage report. 

59 

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. 

62 

63 Args: 

64 coverage_json_path: Path to the coverage.json file. 

65 output_path: Path where the badge JSON should be written. 

66 

67 Raises: 

68 SystemExit: If the coverage JSON is invalid or missing required data. 

69 

70 Example: 

71 Generate badge from coverage report:: 

72 

73 from pathlib import Path 

74 generate_coverage_badge_command( 

75 Path("_tests/coverage.json"), 

76 Path("_book/tests/coverage-badge.json") 

77 ) 

78 

79 The generated JSON can be used with shields.io:: 

80 

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 

89 

90 console.info(f"Generating coverage badge from {coverage_json_path}...") 

91 

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) 

99 

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) 

106 

107 # Round to nearest integer 

108 coverage = round(percent) 

109 

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) 

113 

114 console.info(f"Coverage: {coverage}%") 

115 

116 # Determine badge color 

117 color = get_badge_color(coverage) 

118 

119 # Create output directory if it doesn't exist 

120 output_path.parent.mkdir(parents=True, exist_ok=True) 

121 

122 # Generate shields.io endpoint JSON 

123 badge_data = { 

124 "schemaVersion": 1, 

125 "label": "coverage", 

126 "message": f"{coverage}%", 

127 "color": color, 

128 } 

129 

130 with output_path.open("w") as f: 

131 json.dump(badge_data, f, indent=2) 

132 f.write("\n") # Add trailing newline 

133 

134 console.info(f"Coverage badge JSON generated at {output_path}")