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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Template definition format for reports.
3Defines YAML-based template format for report structure with Jinja2
4templating for variables, conditionals, and loops.
7References:
8"""
10from __future__ import annotations
12from dataclasses import dataclass, field
13from pathlib import Path
14from typing import Any
16try:
17 import yaml
19 YAML_AVAILABLE = True
20except ImportError:
21 YAML_AVAILABLE = False
24@dataclass
25class TemplateSection:
26 """Template section definition.
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.
35 References:
36 REPORT-007: Template Definition Format
37 """
39 title: str
40 content_type: str = "text"
41 content: str = ""
42 condition: str | None = None
43 order: int = 0
46@dataclass
47class TemplateDefinition:
48 """Report template definition.
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).
60 References:
61 REPORT-007: Template Definition Format
62 """
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
74def load_template(path: str | Path) -> TemplateDefinition:
75 """Load template from YAML file.
77 Args:
78 path: Path to YAML template file.
80 Returns:
81 TemplateDefinition object.
83 Raises:
84 ImportError: If PyYAML is not installed.
85 FileNotFoundError: If template file not found.
87 Example:
88 >>> template = load_template("templates/compliance.yaml")
89 >>> print(template.name)
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 )
99 path = Path(path)
100 if not path.exists():
101 raise FileNotFoundError(f"Template not found: {path}")
103 with open(path) as f:
104 data = yaml.safe_load(f)
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 )
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)
127 # Parse variables
128 template.variables = data.get("variables", {})
130 return template
133def validate_template(template: TemplateDefinition) -> tuple[bool, list[str]]:
134 """Validate template definition.
136 Args:
137 template: Template to validate.
139 Returns:
140 Tuple of (is_valid, list_of_errors).
142 Example:
143 >>> template = TemplateDefinition(name="test")
144 >>> valid, errors = validate_template(template)
145 >>> print(valid)
146 True
148 References:
149 REPORT-007: Template Definition Format
150 """
151 errors = []
153 # Check required fields
154 if not template.name:
155 errors.append("Template name is required")
157 if not template.version:
158 errors.append("Template version is required")
160 # Check sections
161 if not template.sections:
162 errors.append("Template must have at least one section")
164 for i, section in enumerate(template.sections):
165 if not section.title:
166 errors.append(f"Section {i} missing title")
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}")
171 return len(errors) == 0, errors
174def list_builtin_templates() -> list[str]:
175 """List available built-in templates.
177 Returns:
178 List of built-in template names.
180 Example:
181 >>> templates = list_builtin_templates()
182 >>> "compliance" in templates
183 True
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 ]
199__all__ = [
200 "TemplateDefinition",
201 "TemplateSection",
202 "list_builtin_templates",
203 "load_template",
204 "validate_template",
205]