Coverage for diagrams / code_diagrams / _readme.py: 100.00%

44 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-09-14 22:07 +0000

1"""Hierarchical ``code_diagrams/README.md`` renderer. 

2 

3The README is regenerated on every run so the index never drifts 

4from what's actually on disk. It groups flowcharts by top-level 

5source directory (``lambda/``, ``cli/``, ``gco/``, ...) and then by 

6parent directory within that, mirroring the project layout. 

7""" 

8 

9from __future__ import annotations 

10 

11from collections import defaultdict 

12from pathlib import Path 

13 

14from diagrams.code_diagrams._renderer import RenderedTarget 

15 

16_HEADER = """\ 

17# GCO Code Flowcharts 

18 

19<!-- Generated at (UTC): __GENERATED_AT__ --> 

20<!-- Generated from Git commit: __SOURCE_COMMIT__ --> 

21*Generated at (UTC): `__GENERATED_AT__`.* 

22*Generated from Git commit: `__SOURCE_COMMIT__`.* 

23 

24This directory holds auto-generated control-flow diagrams for the 

25Python source files listed below. Each target produces an interactive 

26[flowchart.js](https://github.com/adrai/flowchart.js) HTML page and (if 

27Playwright is available) a rendered PNG. 

28 

29> Interactive HTML is the primary artifact — open it in any browser to 

30> pan, zoom, and export SVG/PNG directly. The PNGs are included for 

31> embedding in READMEs and pull requests where JS can't run. 

32 

33## Table of Contents 

34 

35- [Regeneration](#regeneration) 

36- [Prerequisites](#prerequisites) 

37- [Flowchart index](#flowchart-index) 

38 

39## Regeneration 

40 

41Use the aggregate driver for canonical committed output: 

42 

43```bash 

44# Full code + infrastructure catalogues at the reviewed timestamp 

45SOURCE_DATE_EPOCH=1788091200 \ 

46GCO_DIAGRAM_SOURCE_COMMIT=<40-char-sha> \ 

47python diagrams/generate.py 

48 

49# Read-only artifact/index/marker/PNG contract 

50python diagrams/generate.py --check 

51 

52# One catalogue only 

53SOURCE_DATE_EPOCH=1788091200 \ 

54GCO_DIAGRAM_SOURCE_COMMIT=<40-char-sha> \ 

55python diagrams/generate.py --code-only 

56python diagrams/generate.py --infra-only 

57 

58# A single target for local diagnosis 

59GCO_DIAGRAM_SOURCE_COMMIT=<40-char-sha> \\ 

60python diagrams/code_diagrams/generate.py \\ 

61 --target lambda/analytics-presigned-url/handler.py:lambda_handler 

62 

63# HTML only (skip Playwright and remove older PNGs for selected targets) 

64GCO_DIAGRAM_SOURCE_COMMIT=<40-char-sha> \\ 

65python diagrams/code_diagrams/generate.py --skip-png 

66 

67# Don't insert/refresh the ``# Flowchart:`` markers in source files 

68GCO_DIAGRAM_SOURCE_COMMIT=<40-char-sha> \\ 

69python diagrams/code_diagrams/generate.py --skip-marker 

70 

71# Remove every existing marker from the source tree and exit 

72# (useful when tearing the feature down or before a big refactor 

73# of placement rules) 

74python diagrams/code_diagrams/generate.py --strip-markers 

75``` 

76 

77See the [Prerequisites](#prerequisites) section below for one-time 

78browser install steps. 

79 

80## Prerequisites 

81 

82Install the project's ``diagrams`` extra, which pins ``pyflowchart`` and 

83``playwright`` to known-good versions: 

84 

85```bash 

86pip install -e '.[diagrams]' 

87playwright install chromium 

88``` 

89 

90Without Playwright's browser, direct code-generator runs still write HTML and 

91remove any older PNG for the selected targets so mixed generation times are 

92impossible. Canonical aggregate generation requires ``SOURCE_DATE_EPOCH`` and 

93``GCO_DIAGRAM_SOURCE_COMMIT``. Commit substantive source changes first, then 

94supply that clean source commit while generating and commit the derived 

95artifacts separately; this avoids an impossible self-referential commit SHA. 

96The generator verifies every marker-stripped charted source against the supplied 

97commit. It records one UTC timestamp and source commit in HTML, PNG pixels, the 

98catalogue, and source markers. Each HTML/PNG pair also displays a deterministic 

99digest of the pre-annotation flow HTML, so source-flow changes remain visible 

100even when flowchart.js collapses them into the same SVG shape. Fixing the 

101timestamp prevents metadata-only churn; none of these mechanisms promises 

102byte-identical Chromium or Graphviz rasterization across toolchain versions or 

103platforms. 

104``python diagrams/generate.py --check`` enforces structural 

105contracts; ``tests/test_diagram_artifact_contract.py`` also verifies every PNG 

106with Pillow. 

107 

108## Flowchart index 

109 

110Entries below are grouped by top-level directory and listed in source 

111order. Each source file may contribute more than one flowchart if it 

112has multiple charted entry points. 

113""" 

114 

115 

116def render_readme( 

117 results: list[RenderedTarget], 

118 *, 

119 output_dir: Path, 

120) -> str: 

121 """Render the full README markdown body as a string.""" 

122 sections = _group_by_toplevel(results) 

123 # Provenance is per source (see ``provenance.json``), so the catalogue is 

124 # legitimately a mix of vintages: incremental regeneration restamps only 

125 # the sources that changed. The index header therefore reports the most 

126 # recent generation; each source's own stamp lives in its marker block and 

127 # in the manifest. 

128 newest = max(results, key=lambda result: result.generated_at, default=None) 

129 generated_at = newest.generated_at if newest is not None else "unknown" 

130 source_commit = newest.source_commit if newest is not None else "unknown" 

131 header = _HEADER.replace("__GENERATED_AT__", generated_at).replace( 

132 "__SOURCE_COMMIT__", source_commit 

133 ) 

134 lines = [header.rstrip()] 

135 for top, dir_groups in sections.items(): 

136 lines.append("") 

137 lines.append(f"### `{top}/`") 

138 for dir_path, entries in dir_groups.items(): 

139 lines.append("") 

140 display_dir = dir_path if dir_path else top 

141 lines.append(f"- **`{display_dir}/`**") 

142 for entry in entries: 

143 lines.append(_format_entry(entry, output_dir=output_dir)) 

144 lines.append("") 

145 return "\n".join(lines) 

146 

147 

148def _group_by_toplevel( 

149 results: list[RenderedTarget], 

150) -> dict[str, dict[str, list[RenderedTarget]]]: 

151 """Group results into ``{top_level: {parent_dir: [results]}}``. 

152 

153 ``dict`` preservation of insertion order keeps the README stable: 

154 sort top-level groups alphabetically, then sort inner directory 

155 groups alphabetically, then leave each directory's target list in 

156 its original :data:`TARGETS` order. 

157 """ 

158 grouped: dict[str, dict[str, list[RenderedTarget]]] = defaultdict( 

159 lambda: defaultdict(list), 

160 ) 

161 for result in results: 

162 src = Path(result.target.source) 

163 top = src.parts[0] 

164 parent = str(src.parent) 

165 grouped[top][parent].append(result) 

166 

167 ordered: dict[str, dict[str, list[RenderedTarget]]] = {} 

168 for top in sorted(grouped): 

169 ordered[top] = {k: grouped[top][k] for k in sorted(grouped[top])} 

170 return ordered 

171 

172 

173def _format_entry( 

174 entry: RenderedTarget, 

175 *, 

176 output_dir: Path, 

177) -> str: 

178 """Render a single bullet for one flowchart target. 

179 

180 Uses a 2-space indent on the nested bullet level so the output 

181 passes markdownlint's MD007/ul-indent rule (default expected 

182 indent = 2 spaces). 

183 """ 

184 html_rel = entry.html_path.relative_to(output_dir) 

185 src = entry.target.source 

186 func = entry.target.function 

187 title = entry.target.title or f"`{func}`" 

188 line = f" - {title} &mdash; `{src}::{func}` &mdash; [HTML](./{html_rel.as_posix()})" 

189 if entry.png_path is not None: 

190 png_rel = entry.png_path.relative_to(output_dir) 

191 line += f" · [PNG](./{png_rel.as_posix()})" 

192 return line