gitflow-analytics 1.0.3__py3-none-any.whl → 1.3.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitflow_analytics/_version.py +1 -1
- gitflow_analytics/classification/__init__.py +31 -0
- gitflow_analytics/classification/batch_classifier.py +752 -0
- gitflow_analytics/classification/classifier.py +464 -0
- gitflow_analytics/classification/feature_extractor.py +725 -0
- gitflow_analytics/classification/linguist_analyzer.py +574 -0
- gitflow_analytics/classification/model.py +455 -0
- gitflow_analytics/cli.py +4158 -350
- gitflow_analytics/cli_rich.py +198 -48
- gitflow_analytics/config/__init__.py +43 -0
- gitflow_analytics/config/errors.py +261 -0
- gitflow_analytics/config/loader.py +905 -0
- gitflow_analytics/config/profiles.py +264 -0
- gitflow_analytics/config/repository.py +124 -0
- gitflow_analytics/config/schema.py +444 -0
- gitflow_analytics/config/validator.py +154 -0
- gitflow_analytics/config.py +44 -508
- gitflow_analytics/core/analyzer.py +1209 -98
- gitflow_analytics/core/cache.py +1337 -29
- gitflow_analytics/core/data_fetcher.py +1285 -0
- gitflow_analytics/core/identity.py +363 -14
- gitflow_analytics/core/metrics_storage.py +526 -0
- gitflow_analytics/core/progress.py +372 -0
- gitflow_analytics/core/schema_version.py +269 -0
- gitflow_analytics/extractors/ml_tickets.py +1100 -0
- gitflow_analytics/extractors/story_points.py +8 -1
- gitflow_analytics/extractors/tickets.py +749 -11
- gitflow_analytics/identity_llm/__init__.py +6 -0
- gitflow_analytics/identity_llm/analysis_pass.py +231 -0
- gitflow_analytics/identity_llm/analyzer.py +464 -0
- gitflow_analytics/identity_llm/models.py +76 -0
- gitflow_analytics/integrations/github_integration.py +175 -11
- gitflow_analytics/integrations/jira_integration.py +461 -24
- gitflow_analytics/integrations/orchestrator.py +124 -1
- gitflow_analytics/metrics/activity_scoring.py +322 -0
- gitflow_analytics/metrics/branch_health.py +470 -0
- gitflow_analytics/metrics/dora.py +379 -20
- gitflow_analytics/models/database.py +843 -53
- gitflow_analytics/pm_framework/__init__.py +115 -0
- gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
- gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
- gitflow_analytics/pm_framework/base.py +406 -0
- gitflow_analytics/pm_framework/models.py +211 -0
- gitflow_analytics/pm_framework/orchestrator.py +652 -0
- gitflow_analytics/pm_framework/registry.py +333 -0
- gitflow_analytics/qualitative/__init__.py +9 -10
- gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
- gitflow_analytics/qualitative/classifiers/__init__.py +3 -3
- gitflow_analytics/qualitative/classifiers/change_type.py +518 -244
- gitflow_analytics/qualitative/classifiers/domain_classifier.py +272 -165
- gitflow_analytics/qualitative/classifiers/intent_analyzer.py +321 -222
- gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
- gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
- gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
- gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
- gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
- gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
- gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
- gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
- gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
- gitflow_analytics/qualitative/classifiers/risk_analyzer.py +215 -189
- gitflow_analytics/qualitative/core/__init__.py +4 -4
- gitflow_analytics/qualitative/core/llm_fallback.py +239 -235
- gitflow_analytics/qualitative/core/nlp_engine.py +157 -148
- gitflow_analytics/qualitative/core/pattern_cache.py +214 -192
- gitflow_analytics/qualitative/core/processor.py +381 -248
- gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
- gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
- gitflow_analytics/qualitative/models/__init__.py +7 -7
- gitflow_analytics/qualitative/models/schemas.py +155 -121
- gitflow_analytics/qualitative/utils/__init__.py +4 -4
- gitflow_analytics/qualitative/utils/batch_processor.py +136 -123
- gitflow_analytics/qualitative/utils/cost_tracker.py +142 -140
- gitflow_analytics/qualitative/utils/metrics.py +172 -158
- gitflow_analytics/qualitative/utils/text_processing.py +146 -104
- gitflow_analytics/reports/__init__.py +100 -0
- gitflow_analytics/reports/analytics_writer.py +539 -14
- gitflow_analytics/reports/base.py +648 -0
- gitflow_analytics/reports/branch_health_writer.py +322 -0
- gitflow_analytics/reports/classification_writer.py +924 -0
- gitflow_analytics/reports/cli_integration.py +427 -0
- gitflow_analytics/reports/csv_writer.py +1676 -212
- gitflow_analytics/reports/data_models.py +504 -0
- gitflow_analytics/reports/database_report_generator.py +427 -0
- gitflow_analytics/reports/example_usage.py +344 -0
- gitflow_analytics/reports/factory.py +499 -0
- gitflow_analytics/reports/formatters.py +698 -0
- gitflow_analytics/reports/html_generator.py +1116 -0
- gitflow_analytics/reports/interfaces.py +489 -0
- gitflow_analytics/reports/json_exporter.py +2770 -0
- gitflow_analytics/reports/narrative_writer.py +2287 -158
- gitflow_analytics/reports/story_point_correlation.py +1144 -0
- gitflow_analytics/reports/weekly_trends_writer.py +389 -0
- gitflow_analytics/training/__init__.py +5 -0
- gitflow_analytics/training/model_loader.py +377 -0
- gitflow_analytics/training/pipeline.py +550 -0
- gitflow_analytics/tui/__init__.py +1 -1
- gitflow_analytics/tui/app.py +129 -126
- gitflow_analytics/tui/screens/__init__.py +3 -3
- gitflow_analytics/tui/screens/analysis_progress_screen.py +188 -179
- gitflow_analytics/tui/screens/configuration_screen.py +154 -178
- gitflow_analytics/tui/screens/loading_screen.py +100 -110
- gitflow_analytics/tui/screens/main_screen.py +89 -72
- gitflow_analytics/tui/screens/results_screen.py +305 -281
- gitflow_analytics/tui/widgets/__init__.py +2 -2
- gitflow_analytics/tui/widgets/data_table.py +67 -69
- gitflow_analytics/tui/widgets/export_modal.py +76 -76
- gitflow_analytics/tui/widgets/progress_widget.py +41 -46
- gitflow_analytics-1.3.11.dist-info/METADATA +1015 -0
- gitflow_analytics-1.3.11.dist-info/RECORD +122 -0
- gitflow_analytics-1.0.3.dist-info/METADATA +0 -490
- gitflow_analytics-1.0.3.dist-info/RECORD +0 -62
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/WHEEL +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Example usage of the report generation abstraction layer.
|
|
2
|
+
|
|
3
|
+
This module demonstrates how to use the new report abstraction layer
|
|
4
|
+
to generate reports in various formats with a unified interface.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
|
+
|
|
11
|
+
from .base import ReportData, ReportMetadata
|
|
12
|
+
from .factory import ReportBuilder, ReportFactory, create_multiple_reports, create_report
|
|
13
|
+
from .interfaces import ReportFormat, ReportType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def example_basic_usage():
|
|
17
|
+
"""Example of basic report generation using the factory."""
|
|
18
|
+
|
|
19
|
+
# Create sample data
|
|
20
|
+
sample_commits = [
|
|
21
|
+
{
|
|
22
|
+
"hash": "abc123",
|
|
23
|
+
"author_email": "dev@example.com",
|
|
24
|
+
"author_name": "Developer One",
|
|
25
|
+
"timestamp": datetime.now(timezone.utc),
|
|
26
|
+
"message": "feat: Add new feature",
|
|
27
|
+
"insertions": 100,
|
|
28
|
+
"deletions": 20,
|
|
29
|
+
"files_changed": 5
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
sample_developer_stats = [
|
|
34
|
+
{
|
|
35
|
+
"canonical_id": "dev@example.com",
|
|
36
|
+
"primary_email": "dev@example.com",
|
|
37
|
+
"primary_name": "Developer One",
|
|
38
|
+
"total_commits": 50,
|
|
39
|
+
"total_story_points": 25,
|
|
40
|
+
"ticket_coverage_pct": 85.0
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# Create ReportData
|
|
45
|
+
report_data = ReportData(
|
|
46
|
+
commits=sample_commits,
|
|
47
|
+
developer_stats=sample_developer_stats,
|
|
48
|
+
metadata=ReportMetadata(
|
|
49
|
+
analysis_period_weeks=4,
|
|
50
|
+
total_commits=len(sample_commits),
|
|
51
|
+
total_developers=len(sample_developer_stats)
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Method 1: Using the factory directly
|
|
56
|
+
factory = ReportFactory()
|
|
57
|
+
csv_generator = factory.create_generator(ReportType.WEEKLY_METRICS, ReportFormat.CSV)
|
|
58
|
+
output = csv_generator.generate(report_data, Path("weekly_metrics.csv"))
|
|
59
|
+
|
|
60
|
+
if output.success:
|
|
61
|
+
print(f"Report generated: {output.file_path}")
|
|
62
|
+
else:
|
|
63
|
+
print(f"Errors: {output.errors}")
|
|
64
|
+
|
|
65
|
+
# Method 2: Using the convenience function
|
|
66
|
+
output = create_report(
|
|
67
|
+
ReportType.DEVELOPER_STATS,
|
|
68
|
+
ReportFormat.CSV,
|
|
69
|
+
report_data,
|
|
70
|
+
"developer_stats.csv"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Method 3: Using the builder pattern
|
|
74
|
+
builder = ReportBuilder()
|
|
75
|
+
generator = (builder
|
|
76
|
+
.add_report(ReportType.NARRATIVE, ReportFormat.MARKDOWN)
|
|
77
|
+
.with_config(anonymize=False, exclude_authors=["bot@example.com"])
|
|
78
|
+
.with_data(report_data)
|
|
79
|
+
.build())
|
|
80
|
+
|
|
81
|
+
output = generator.generate(report_data, Path("narrative.md"))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def example_composite_reports():
|
|
85
|
+
"""Example of generating multiple report formats at once."""
|
|
86
|
+
|
|
87
|
+
# Create sample data
|
|
88
|
+
report_data = ReportData(
|
|
89
|
+
commits=[{"hash": "test", "author_email": "dev@test.com", "timestamp": datetime.now(timezone.utc)}],
|
|
90
|
+
developer_stats=[{"canonical_id": "dev@test.com", "total_commits": 10}]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Generate multiple reports at once
|
|
94
|
+
outputs = create_multiple_reports(
|
|
95
|
+
[
|
|
96
|
+
(ReportType.WEEKLY_METRICS, ReportFormat.CSV),
|
|
97
|
+
(ReportType.DEVELOPER_STATS, ReportFormat.CSV),
|
|
98
|
+
(ReportType.COMPREHENSIVE, ReportFormat.JSON)
|
|
99
|
+
],
|
|
100
|
+
report_data,
|
|
101
|
+
output_dir="reports/"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
for output in outputs:
|
|
105
|
+
if output.success:
|
|
106
|
+
print(f"Generated: {output.file_path}")
|
|
107
|
+
else:
|
|
108
|
+
print(f"Failed: {output.errors}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def example_custom_generator():
|
|
112
|
+
"""Example of creating a custom report generator."""
|
|
113
|
+
|
|
114
|
+
from .base import BaseReportGenerator, ReportOutput
|
|
115
|
+
|
|
116
|
+
class CustomHTMLGenerator(BaseReportGenerator):
|
|
117
|
+
"""Custom HTML report generator."""
|
|
118
|
+
|
|
119
|
+
def generate(self, data: ReportData, output_path: Path = None) -> ReportOutput:
|
|
120
|
+
"""Generate HTML report."""
|
|
121
|
+
# Pre-process data
|
|
122
|
+
data = self.pre_process(data)
|
|
123
|
+
|
|
124
|
+
# Generate HTML
|
|
125
|
+
html_content = self._generate_html(data)
|
|
126
|
+
|
|
127
|
+
if output_path:
|
|
128
|
+
self.write_to_file(html_content, output_path)
|
|
129
|
+
return ReportOutput(
|
|
130
|
+
success=True,
|
|
131
|
+
file_path=output_path,
|
|
132
|
+
format="html",
|
|
133
|
+
size_bytes=len(html_content)
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
return ReportOutput(
|
|
137
|
+
success=True,
|
|
138
|
+
content=html_content,
|
|
139
|
+
format="html",
|
|
140
|
+
size_bytes=len(html_content)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def get_required_fields(self) -> List[str]:
|
|
144
|
+
return ["commits", "developer_stats"]
|
|
145
|
+
|
|
146
|
+
def get_format_type(self) -> str:
|
|
147
|
+
return "html"
|
|
148
|
+
|
|
149
|
+
def _generate_html(self, data: ReportData) -> str:
|
|
150
|
+
"""Generate HTML content."""
|
|
151
|
+
html = f"""
|
|
152
|
+
<!DOCTYPE html>
|
|
153
|
+
<html>
|
|
154
|
+
<head>
|
|
155
|
+
<title>GitFlow Analytics Report</title>
|
|
156
|
+
<style>
|
|
157
|
+
body {{ font-family: Arial, sans-serif; margin: 20px; }}
|
|
158
|
+
h1 {{ color: #333; }}
|
|
159
|
+
table {{ border-collapse: collapse; width: 100%; }}
|
|
160
|
+
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
|
161
|
+
th {{ background-color: #f2f2f2; }}
|
|
162
|
+
</style>
|
|
163
|
+
</head>
|
|
164
|
+
<body>
|
|
165
|
+
<h1>GitFlow Analytics Report</h1>
|
|
166
|
+
<h2>Summary</h2>
|
|
167
|
+
<p>Total Commits: {len(data.commits)}</p>
|
|
168
|
+
<p>Total Developers: {len(data.developer_stats)}</p>
|
|
169
|
+
|
|
170
|
+
<h2>Top Contributors</h2>
|
|
171
|
+
<table>
|
|
172
|
+
<tr>
|
|
173
|
+
<th>Developer</th>
|
|
174
|
+
<th>Commits</th>
|
|
175
|
+
<th>Story Points</th>
|
|
176
|
+
</tr>
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
for dev in sorted(data.developer_stats,
|
|
180
|
+
key=lambda d: d.get("total_commits", 0),
|
|
181
|
+
reverse=True)[:10]:
|
|
182
|
+
html += f"""
|
|
183
|
+
<tr>
|
|
184
|
+
<td>{dev.get('primary_name', 'Unknown')}</td>
|
|
185
|
+
<td>{dev.get('total_commits', 0)}</td>
|
|
186
|
+
<td>{dev.get('total_story_points', 0)}</td>
|
|
187
|
+
</tr>
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
html += """
|
|
191
|
+
</table>
|
|
192
|
+
</body>
|
|
193
|
+
</html>
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
return html
|
|
197
|
+
|
|
198
|
+
# Register the custom generator
|
|
199
|
+
factory = ReportFactory()
|
|
200
|
+
factory.register_generator(
|
|
201
|
+
ReportType.CUSTOM,
|
|
202
|
+
ReportFormat.HTML,
|
|
203
|
+
CustomHTMLGenerator
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Use the custom generator
|
|
207
|
+
report_data = ReportData(
|
|
208
|
+
commits=[{"hash": "test", "timestamp": datetime.now(timezone.utc)}],
|
|
209
|
+
developer_stats=[{"primary_name": "Test Dev", "total_commits": 10}]
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
generator = factory.create_generator(ReportType.CUSTOM, ReportFormat.HTML)
|
|
213
|
+
output = generator.generate(report_data, Path("custom_report.html"))
|
|
214
|
+
|
|
215
|
+
if output.success:
|
|
216
|
+
print(f"Custom report generated: {output.file_path}")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def example_report_chaining():
|
|
220
|
+
"""Example of chaining report generators."""
|
|
221
|
+
|
|
222
|
+
from .base import ChainedReportGenerator
|
|
223
|
+
|
|
224
|
+
# Create a chain of generators that process data sequentially
|
|
225
|
+
factory = ReportFactory()
|
|
226
|
+
|
|
227
|
+
# First generate CSV, then use that to generate a summary JSON
|
|
228
|
+
csv_gen = factory.create_generator(ReportType.WEEKLY_METRICS, ReportFormat.CSV)
|
|
229
|
+
json_gen = factory.create_generator(ReportType.COMPREHENSIVE, ReportFormat.JSON)
|
|
230
|
+
|
|
231
|
+
chain = ChainedReportGenerator([csv_gen, json_gen])
|
|
232
|
+
|
|
233
|
+
report_data = ReportData(
|
|
234
|
+
commits=[{"hash": "test", "timestamp": datetime.now(timezone.utc)}],
|
|
235
|
+
developer_stats=[{"canonical_id": "dev@test.com", "total_commits": 10}]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
output = chain.generate(report_data, Path("final_output.json"))
|
|
239
|
+
|
|
240
|
+
if output.success:
|
|
241
|
+
print(f"Chained report generated: {output.file_path}")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def example_template_based_generation():
|
|
245
|
+
"""Example of using templates for report generation."""
|
|
246
|
+
|
|
247
|
+
from string import Template
|
|
248
|
+
|
|
249
|
+
class TemplateReportGenerator(BaseReportGenerator):
|
|
250
|
+
"""Template-based report generator."""
|
|
251
|
+
|
|
252
|
+
def __init__(self, template_path: Path = None, **kwargs):
|
|
253
|
+
super().__init__(**kwargs)
|
|
254
|
+
self.template_path = template_path
|
|
255
|
+
|
|
256
|
+
def generate(self, data: ReportData, output_path: Path = None) -> ReportOutput:
|
|
257
|
+
"""Generate report from template."""
|
|
258
|
+
# Pre-process data
|
|
259
|
+
data = self.pre_process(data)
|
|
260
|
+
|
|
261
|
+
# Load template
|
|
262
|
+
if self.template_path and self.template_path.exists():
|
|
263
|
+
template_str = self.template_path.read_text()
|
|
264
|
+
else:
|
|
265
|
+
template_str = self._get_default_template()
|
|
266
|
+
|
|
267
|
+
template = Template(template_str)
|
|
268
|
+
|
|
269
|
+
# Prepare context
|
|
270
|
+
context = {
|
|
271
|
+
"total_commits": len(data.commits),
|
|
272
|
+
"total_developers": len(data.developer_stats),
|
|
273
|
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
|
274
|
+
"top_developer": self._get_top_developer(data.developer_stats)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Render template
|
|
278
|
+
content = template.safe_substitute(context)
|
|
279
|
+
|
|
280
|
+
if output_path:
|
|
281
|
+
self.write_to_file(content, output_path)
|
|
282
|
+
return ReportOutput(success=True, file_path=output_path, format="txt")
|
|
283
|
+
else:
|
|
284
|
+
return ReportOutput(success=True, content=content, format="txt")
|
|
285
|
+
|
|
286
|
+
def get_required_fields(self) -> List[str]:
|
|
287
|
+
return []
|
|
288
|
+
|
|
289
|
+
def get_format_type(self) -> str:
|
|
290
|
+
return "template"
|
|
291
|
+
|
|
292
|
+
def _get_default_template(self) -> str:
|
|
293
|
+
return """
|
|
294
|
+
GitFlow Analytics Report
|
|
295
|
+
========================
|
|
296
|
+
Date: $date
|
|
297
|
+
|
|
298
|
+
Summary:
|
|
299
|
+
- Total Commits: $total_commits
|
|
300
|
+
- Total Developers: $total_developers
|
|
301
|
+
- Top Contributor: $top_developer
|
|
302
|
+
|
|
303
|
+
Generated by GitFlow Analytics
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
def _get_top_developer(self, developers: List[Dict[str, Any]]) -> str:
|
|
307
|
+
if not developers:
|
|
308
|
+
return "N/A"
|
|
309
|
+
|
|
310
|
+
top = max(developers, key=lambda d: d.get("total_commits", 0))
|
|
311
|
+
return f"{top.get('primary_name', 'Unknown')} ({top.get('total_commits', 0)} commits)"
|
|
312
|
+
|
|
313
|
+
# Use the template generator
|
|
314
|
+
report_data = ReportData(
|
|
315
|
+
commits=[{"hash": "test", "timestamp": datetime.now(timezone.utc)}],
|
|
316
|
+
developer_stats=[
|
|
317
|
+
{"primary_name": "Alice", "total_commits": 50},
|
|
318
|
+
{"primary_name": "Bob", "total_commits": 30}
|
|
319
|
+
]
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
generator = TemplateReportGenerator()
|
|
323
|
+
output = generator.generate(report_data, Path("template_report.txt"))
|
|
324
|
+
|
|
325
|
+
if output.success:
|
|
326
|
+
print(f"Template report generated: {output.file_path}")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
if __name__ == "__main__":
|
|
330
|
+
# Run examples
|
|
331
|
+
print("Running basic usage example...")
|
|
332
|
+
example_basic_usage()
|
|
333
|
+
|
|
334
|
+
print("\nRunning composite reports example...")
|
|
335
|
+
example_composite_reports()
|
|
336
|
+
|
|
337
|
+
print("\nRunning custom generator example...")
|
|
338
|
+
example_custom_generator()
|
|
339
|
+
|
|
340
|
+
print("\nRunning report chaining example...")
|
|
341
|
+
example_report_chaining()
|
|
342
|
+
|
|
343
|
+
print("\nRunning template-based generation example...")
|
|
344
|
+
example_template_based_generation()
|