Coverage for src / tracekit / reporting / templates / definition.py: 100%

55 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 23:04 +0000

1"""Template definition format for reports. 

2 

3Defines YAML-based template format for report structure with Jinja2 

4templating for variables, conditionals, and loops. 

5 

6 

7References: 

8""" 

9 

10from __future__ import annotations 

11 

12from dataclasses import dataclass, field 

13from pathlib import Path 

14from typing import Any 

15 

16try: 

17 import yaml 

18 

19 YAML_AVAILABLE = True 

20except ImportError: 

21 YAML_AVAILABLE = False 

22 

23 

24@dataclass 

25class TemplateSection: 

26 """Template section definition. 

27 

28 Attributes: 

29 title: Section title. 

30 content_type: Type of content (text, table, plot, jinja2). 

31 content: Content template string (may include Jinja2 variables). 

32 condition: Optional Jinja2 condition for inclusion. 

33 order: Display order. 

34 

35 References: 

36 REPORT-007: Template Definition Format 

37 """ 

38 

39 title: str 

40 content_type: str = "text" 

41 content: str = "" 

42 condition: str | None = None 

43 order: int = 0 

44 

45 

46@dataclass 

47class TemplateDefinition: 

48 """Report template definition. 

49 

50 Attributes: 

51 name: Template name. 

52 version: Template version. 

53 author: Template author. 

54 description: Template description. 

55 tags: Template tags. 

56 sections: Ordered list of sections. 

57 variables: Template variables with defaults. 

58 extends: Base template to extend (optional). 

59 

60 References: 

61 REPORT-007: Template Definition Format 

62 """ 

63 

64 name: str 

65 version: str = "1.0" 

66 author: str = "" 

67 description: str = "" 

68 tags: list[str] = field(default_factory=list) 

69 sections: list[TemplateSection] = field(default_factory=list) 

70 variables: dict[str, Any] = field(default_factory=dict) 

71 extends: str | None = None 

72 

73 

74def load_template(path: str | Path) -> TemplateDefinition: 

75 """Load template from YAML file. 

76 

77 Args: 

78 path: Path to YAML template file. 

79 

80 Returns: 

81 TemplateDefinition object. 

82 

83 Raises: 

84 ImportError: If PyYAML is not installed. 

85 FileNotFoundError: If template file not found. 

86 

87 Example: 

88 >>> template = load_template("templates/compliance.yaml") 

89 >>> print(template.name) 

90 

91 References: 

92 REPORT-007: Template Definition Format 

93 """ 

94 if not YAML_AVAILABLE: 

95 raise ImportError( 

96 "PyYAML is required for template loading. Install with: pip install pyyaml" 

97 ) 

98 

99 path = Path(path) 

100 if not path.exists(): 

101 raise FileNotFoundError(f"Template not found: {path}") 

102 

103 with open(path) as f: 

104 data = yaml.safe_load(f) 

105 

106 # Parse template metadata 

107 template = TemplateDefinition( 

108 name=data.get("name", path.stem), 

109 version=data.get("version", "1.0"), 

110 author=data.get("author", ""), 

111 description=data.get("description", ""), 

112 tags=data.get("tags", []), 

113 extends=data.get("extends"), 

114 ) 

115 

116 # Parse sections 

117 for i, section_data in enumerate(data.get("sections", [])): 

118 section = TemplateSection( 

119 title=section_data.get("title", f"Section {i + 1}"), 

120 content_type=section_data.get("content_type", "text"), 

121 content=section_data.get("content", ""), 

122 condition=section_data.get("condition"), 

123 order=section_data.get("order", i), 

124 ) 

125 template.sections.append(section) 

126 

127 # Parse variables 

128 template.variables = data.get("variables", {}) 

129 

130 return template 

131 

132 

133def validate_template(template: TemplateDefinition) -> tuple[bool, list[str]]: 

134 """Validate template definition. 

135 

136 Args: 

137 template: Template to validate. 

138 

139 Returns: 

140 Tuple of (is_valid, list_of_errors). 

141 

142 Example: 

143 >>> template = TemplateDefinition(name="test") 

144 >>> valid, errors = validate_template(template) 

145 >>> print(valid) 

146 True 

147 

148 References: 

149 REPORT-007: Template Definition Format 

150 """ 

151 errors = [] 

152 

153 # Check required fields 

154 if not template.name: 

155 errors.append("Template name is required") 

156 

157 if not template.version: 

158 errors.append("Template version is required") 

159 

160 # Check sections 

161 if not template.sections: 

162 errors.append("Template must have at least one section") 

163 

164 for i, section in enumerate(template.sections): 

165 if not section.title: 

166 errors.append(f"Section {i} missing title") 

167 

168 if section.content_type not in ["text", "table", "plot", "jinja2", "markdown"]: 

169 errors.append(f"Section {i} has invalid content_type: {section.content_type}") 

170 

171 return len(errors) == 0, errors 

172 

173 

174def list_builtin_templates() -> list[str]: 

175 """List available built-in templates. 

176 

177 Returns: 

178 List of built-in template names. 

179 

180 Example: 

181 >>> templates = list_builtin_templates() 

182 >>> "compliance" in templates 

183 True 

184 

185 References: 

186 REPORT-007: Template Definition Format 

187 """ 

188 return [ 

189 "default", 

190 "compliance", 

191 "characterization", 

192 "debug", 

193 "production", 

194 "comparison", 

195 "batch_summary", 

196 ] 

197 

198 

199__all__ = [ 

200 "TemplateDefinition", 

201 "TemplateSection", 

202 "list_builtin_templates", 

203 "load_template", 

204 "validate_template", 

205]