panelbox 0.2.0__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.
- panelbox/__init__.py +67 -0
- panelbox/__version__.py +14 -0
- panelbox/cli/__init__.py +0 -0
- panelbox/cli/{commands}/__init__.py +0 -0
- panelbox/core/__init__.py +0 -0
- panelbox/core/base_model.py +164 -0
- panelbox/core/formula_parser.py +318 -0
- panelbox/core/panel_data.py +387 -0
- panelbox/core/results.py +366 -0
- panelbox/datasets/__init__.py +0 -0
- panelbox/datasets/{data}/__init__.py +0 -0
- panelbox/gmm/__init__.py +65 -0
- panelbox/gmm/difference_gmm.py +645 -0
- panelbox/gmm/estimator.py +562 -0
- panelbox/gmm/instruments.py +580 -0
- panelbox/gmm/results.py +550 -0
- panelbox/gmm/system_gmm.py +621 -0
- panelbox/gmm/tests.py +535 -0
- panelbox/models/__init__.py +11 -0
- panelbox/models/dynamic/__init__.py +0 -0
- panelbox/models/iv/__init__.py +0 -0
- panelbox/models/static/__init__.py +13 -0
- panelbox/models/static/fixed_effects.py +516 -0
- panelbox/models/static/pooled_ols.py +298 -0
- panelbox/models/static/random_effects.py +512 -0
- panelbox/report/__init__.py +61 -0
- panelbox/report/asset_manager.py +410 -0
- panelbox/report/css_manager.py +472 -0
- panelbox/report/exporters/__init__.py +15 -0
- panelbox/report/exporters/html_exporter.py +440 -0
- panelbox/report/exporters/latex_exporter.py +510 -0
- panelbox/report/exporters/markdown_exporter.py +446 -0
- panelbox/report/renderers/__init__.py +11 -0
- panelbox/report/renderers/static/__init__.py +0 -0
- panelbox/report/renderers/static_validation_renderer.py +341 -0
- panelbox/report/report_manager.py +502 -0
- panelbox/report/template_manager.py +337 -0
- panelbox/report/transformers/__init__.py +0 -0
- panelbox/report/transformers/static/__init__.py +0 -0
- panelbox/report/validation_transformer.py +449 -0
- panelbox/standard_errors/__init__.py +0 -0
- panelbox/templates/__init__.py +0 -0
- panelbox/templates/assets/css/base_styles.css +382 -0
- panelbox/templates/assets/css/report_components.css +747 -0
- panelbox/templates/assets/js/tab-navigation.js +161 -0
- panelbox/templates/assets/js/utils.js +276 -0
- panelbox/templates/common/footer.html +24 -0
- panelbox/templates/common/header.html +44 -0
- panelbox/templates/common/meta.html +5 -0
- panelbox/templates/validation/interactive/index.html +272 -0
- panelbox/templates/validation/interactive/partials/charts.html +58 -0
- panelbox/templates/validation/interactive/partials/methodology.html +201 -0
- panelbox/templates/validation/interactive/partials/overview.html +146 -0
- panelbox/templates/validation/interactive/partials/recommendations.html +101 -0
- panelbox/templates/validation/interactive/partials/test_results.html +231 -0
- panelbox/utils/__init__.py +0 -0
- panelbox/utils/formatting.py +172 -0
- panelbox/utils/matrix_ops.py +233 -0
- panelbox/utils/statistical.py +173 -0
- panelbox/validation/__init__.py +58 -0
- panelbox/validation/base.py +175 -0
- panelbox/validation/cointegration/__init__.py +0 -0
- panelbox/validation/cross_sectional_dependence/__init__.py +13 -0
- panelbox/validation/cross_sectional_dependence/breusch_pagan_lm.py +222 -0
- panelbox/validation/cross_sectional_dependence/frees.py +297 -0
- panelbox/validation/cross_sectional_dependence/pesaran_cd.py +188 -0
- panelbox/validation/heteroskedasticity/__init__.py +13 -0
- panelbox/validation/heteroskedasticity/breusch_pagan.py +222 -0
- panelbox/validation/heteroskedasticity/modified_wald.py +172 -0
- panelbox/validation/heteroskedasticity/white.py +208 -0
- panelbox/validation/instruments/__init__.py +0 -0
- panelbox/validation/robustness/__init__.py +0 -0
- panelbox/validation/serial_correlation/__init__.py +13 -0
- panelbox/validation/serial_correlation/baltagi_wu.py +220 -0
- panelbox/validation/serial_correlation/breusch_godfrey.py +260 -0
- panelbox/validation/serial_correlation/wooldridge_ar.py +200 -0
- panelbox/validation/specification/__init__.py +16 -0
- panelbox/validation/specification/chow.py +273 -0
- panelbox/validation/specification/hausman.py +264 -0
- panelbox/validation/specification/mundlak.py +331 -0
- panelbox/validation/specification/reset.py +273 -0
- panelbox/validation/unit_root/__init__.py +0 -0
- panelbox/validation/validation_report.py +257 -0
- panelbox/validation/validation_suite.py +401 -0
- panelbox-0.2.0.dist-info/METADATA +337 -0
- panelbox-0.2.0.dist-info/RECORD +90 -0
- panelbox-0.2.0.dist-info/WHEEL +5 -0
- panelbox-0.2.0.dist-info/entry_points.txt +2 -0
- panelbox-0.2.0.dist-info/licenses/LICENSE +21 -0
- panelbox-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Markdown Exporter for PanelBox Reports.
|
|
3
|
+
|
|
4
|
+
Exports validation and regression results to Markdown format.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any, List, Optional, Union
|
|
9
|
+
import datetime
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MarkdownExporter:
|
|
13
|
+
"""
|
|
14
|
+
Exports PanelBox reports to Markdown format.
|
|
15
|
+
|
|
16
|
+
Creates GitHub-flavored Markdown reports suitable for documentation,
|
|
17
|
+
README files, and issue tracking.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
include_toc : bool, default=True
|
|
22
|
+
Include table of contents
|
|
23
|
+
github_flavor : bool, default=True
|
|
24
|
+
Use GitHub-flavored Markdown extensions
|
|
25
|
+
|
|
26
|
+
Examples
|
|
27
|
+
--------
|
|
28
|
+
>>> from panelbox.report.exporters import MarkdownExporter
|
|
29
|
+
>>>
|
|
30
|
+
>>> exporter = MarkdownExporter()
|
|
31
|
+
>>> md = exporter.export_validation_report(validation_data)
|
|
32
|
+
>>> exporter.save(md, 'validation_report.md')
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
include_toc: bool = True,
|
|
38
|
+
github_flavor: bool = True
|
|
39
|
+
):
|
|
40
|
+
"""Initialize Markdown Exporter."""
|
|
41
|
+
self.include_toc = include_toc
|
|
42
|
+
self.github_flavor = github_flavor
|
|
43
|
+
|
|
44
|
+
def export_validation_report(
|
|
45
|
+
self,
|
|
46
|
+
validation_data: Dict[str, Any],
|
|
47
|
+
title: str = "Validation Report"
|
|
48
|
+
) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Export complete validation report to Markdown.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
validation_data : dict
|
|
55
|
+
Validation data from ValidationTransformer
|
|
56
|
+
title : str, default="Validation Report"
|
|
57
|
+
Report title
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
str
|
|
62
|
+
Markdown content
|
|
63
|
+
|
|
64
|
+
Examples
|
|
65
|
+
--------
|
|
66
|
+
>>> md = exporter.export_validation_report(
|
|
67
|
+
... validation_data,
|
|
68
|
+
... title="Panel Data Validation"
|
|
69
|
+
... )
|
|
70
|
+
"""
|
|
71
|
+
lines = []
|
|
72
|
+
|
|
73
|
+
# Title
|
|
74
|
+
lines.append(f"# {title}")
|
|
75
|
+
lines.append("")
|
|
76
|
+
|
|
77
|
+
# Metadata
|
|
78
|
+
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
79
|
+
lines.append(f"**Generated:** {timestamp}")
|
|
80
|
+
lines.append("")
|
|
81
|
+
|
|
82
|
+
# Summary
|
|
83
|
+
summary = validation_data.get('summary', {})
|
|
84
|
+
lines.append("## Summary")
|
|
85
|
+
lines.append("")
|
|
86
|
+
lines.append(f"- **Total Tests:** {summary.get('total_tests', 0)}")
|
|
87
|
+
lines.append(f"- **Passed:** {summary.get('total_passed', 0)} ✅")
|
|
88
|
+
lines.append(f"- **Failed:** {summary.get('total_failed', 0)} ❌")
|
|
89
|
+
lines.append(f"- **Pass Rate:** {summary.get('pass_rate_formatted', '0%')}")
|
|
90
|
+
lines.append("")
|
|
91
|
+
|
|
92
|
+
# Status indicator
|
|
93
|
+
if summary.get('has_issues', False):
|
|
94
|
+
lines.append(f"> ⚠️ **{summary.get('status_message', 'Issues detected')}**")
|
|
95
|
+
else:
|
|
96
|
+
lines.append(f"> ✅ **{summary.get('status_message', 'All tests passed')}**")
|
|
97
|
+
lines.append("")
|
|
98
|
+
|
|
99
|
+
# TOC
|
|
100
|
+
if self.include_toc:
|
|
101
|
+
lines.append("## Table of Contents")
|
|
102
|
+
lines.append("")
|
|
103
|
+
lines.append("- [Model Information](#model-information)")
|
|
104
|
+
lines.append("- [Test Results](#test-results)")
|
|
105
|
+
if validation_data.get('recommendations'):
|
|
106
|
+
lines.append("- [Recommendations](#recommendations)")
|
|
107
|
+
lines.append("")
|
|
108
|
+
|
|
109
|
+
# Model Information
|
|
110
|
+
model_info = validation_data.get('model_info', {})
|
|
111
|
+
lines.append("## Model Information")
|
|
112
|
+
lines.append("")
|
|
113
|
+
lines.append(f"- **Model Type:** {model_info.get('model_type', 'Unknown')}")
|
|
114
|
+
if 'formula' in model_info:
|
|
115
|
+
lines.append(f"- **Formula:** `{model_info['formula']}`")
|
|
116
|
+
lines.append(f"- **Observations:** {model_info.get('nobs_formatted', model_info.get('nobs', 'N/A'))}")
|
|
117
|
+
if 'n_entities' in model_info:
|
|
118
|
+
lines.append(f"- **Entities:** {model_info.get('n_entities_formatted', model_info.get('n_entities'))}")
|
|
119
|
+
if 'n_periods' in model_info:
|
|
120
|
+
lines.append(f"- **Time Periods:** {model_info.get('n_periods_formatted', model_info.get('n_periods'))}")
|
|
121
|
+
lines.append("")
|
|
122
|
+
|
|
123
|
+
# Test Results
|
|
124
|
+
tests = validation_data.get('tests', [])
|
|
125
|
+
if tests:
|
|
126
|
+
lines.append("## Test Results")
|
|
127
|
+
lines.append("")
|
|
128
|
+
|
|
129
|
+
# Group by category
|
|
130
|
+
categories = {}
|
|
131
|
+
for test in tests:
|
|
132
|
+
cat = test['category']
|
|
133
|
+
if cat not in categories:
|
|
134
|
+
categories[cat] = []
|
|
135
|
+
categories[cat].append(test)
|
|
136
|
+
|
|
137
|
+
# Export each category
|
|
138
|
+
for category, cat_tests in categories.items():
|
|
139
|
+
lines.append(f"### {category}")
|
|
140
|
+
lines.append("")
|
|
141
|
+
|
|
142
|
+
# Table header
|
|
143
|
+
lines.append("| Test | Statistic | P-value | Result |")
|
|
144
|
+
lines.append("|------|-----------|---------|--------|")
|
|
145
|
+
|
|
146
|
+
# Table rows
|
|
147
|
+
for test in cat_tests:
|
|
148
|
+
name = test['name']
|
|
149
|
+
stat = test['statistic_formatted']
|
|
150
|
+
pval = test['pvalue_formatted']
|
|
151
|
+
sig = test.get('significance', '')
|
|
152
|
+
result = test['result']
|
|
153
|
+
|
|
154
|
+
# Emoji indicator
|
|
155
|
+
if result == 'REJECT':
|
|
156
|
+
result_emoji = "❌ REJECT"
|
|
157
|
+
else:
|
|
158
|
+
result_emoji = "✅ ACCEPT"
|
|
159
|
+
|
|
160
|
+
lines.append(f"| {name} | {stat} | {pval}{sig} | {result_emoji} |")
|
|
161
|
+
|
|
162
|
+
lines.append("")
|
|
163
|
+
|
|
164
|
+
# Recommendations
|
|
165
|
+
recommendations = validation_data.get('recommendations', [])
|
|
166
|
+
if recommendations:
|
|
167
|
+
lines.append("## Recommendations")
|
|
168
|
+
lines.append("")
|
|
169
|
+
|
|
170
|
+
for i, rec in enumerate(recommendations, 1):
|
|
171
|
+
severity = rec['severity'].upper()
|
|
172
|
+
category = rec['category']
|
|
173
|
+
issue = rec['issue']
|
|
174
|
+
|
|
175
|
+
# Severity emoji
|
|
176
|
+
if severity == 'CRITICAL':
|
|
177
|
+
emoji = "🔴"
|
|
178
|
+
elif severity == 'HIGH':
|
|
179
|
+
emoji = "🟠"
|
|
180
|
+
elif severity == 'MEDIUM':
|
|
181
|
+
emoji = "🟡"
|
|
182
|
+
else:
|
|
183
|
+
emoji = "🔵"
|
|
184
|
+
|
|
185
|
+
lines.append(f"### {i}. {emoji} {category} ({severity})")
|
|
186
|
+
lines.append("")
|
|
187
|
+
lines.append(f"**Issue:** {issue}")
|
|
188
|
+
lines.append("")
|
|
189
|
+
|
|
190
|
+
# Failed tests
|
|
191
|
+
if rec.get('tests'):
|
|
192
|
+
lines.append("**Failed Tests:**")
|
|
193
|
+
for test in rec['tests']:
|
|
194
|
+
lines.append(f"- {test}")
|
|
195
|
+
lines.append("")
|
|
196
|
+
|
|
197
|
+
# Suggestions
|
|
198
|
+
if rec.get('suggestions'):
|
|
199
|
+
lines.append("**Suggested Actions:**")
|
|
200
|
+
for suggestion in rec['suggestions']:
|
|
201
|
+
lines.append(f"1. {suggestion}")
|
|
202
|
+
lines.append("")
|
|
203
|
+
|
|
204
|
+
# Footer
|
|
205
|
+
lines.append("---")
|
|
206
|
+
lines.append("")
|
|
207
|
+
lines.append("*Generated with [PanelBox](https://github.com/panelbox/panelbox)*")
|
|
208
|
+
lines.append("")
|
|
209
|
+
|
|
210
|
+
return "\n".join(lines)
|
|
211
|
+
|
|
212
|
+
def export_validation_tests(
|
|
213
|
+
self,
|
|
214
|
+
tests: List[Dict[str, Any]]
|
|
215
|
+
) -> str:
|
|
216
|
+
"""
|
|
217
|
+
Export validation tests as Markdown table.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
tests : list of dict
|
|
222
|
+
List of test results
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
str
|
|
227
|
+
Markdown table
|
|
228
|
+
|
|
229
|
+
Examples
|
|
230
|
+
--------
|
|
231
|
+
>>> md_table = exporter.export_validation_tests(tests)
|
|
232
|
+
"""
|
|
233
|
+
lines = []
|
|
234
|
+
|
|
235
|
+
# Table header
|
|
236
|
+
lines.append("| Category | Test | Statistic | P-value | DF | Result |")
|
|
237
|
+
lines.append("|----------|------|-----------|---------|----|----|")
|
|
238
|
+
|
|
239
|
+
# Group by category
|
|
240
|
+
categories = {}
|
|
241
|
+
for test in tests:
|
|
242
|
+
cat = test['category']
|
|
243
|
+
if cat not in categories:
|
|
244
|
+
categories[cat] = []
|
|
245
|
+
categories[cat].append(test)
|
|
246
|
+
|
|
247
|
+
# Table rows
|
|
248
|
+
for category, cat_tests in categories.items():
|
|
249
|
+
for i, test in enumerate(cat_tests):
|
|
250
|
+
# Show category only for first test in group
|
|
251
|
+
cat_display = category if i == 0 else ""
|
|
252
|
+
|
|
253
|
+
name = test['name']
|
|
254
|
+
stat = test['statistic_formatted']
|
|
255
|
+
pval = test['pvalue_formatted']
|
|
256
|
+
sig = test.get('significance', '')
|
|
257
|
+
df = test.get('df', 'N/A')
|
|
258
|
+
result = test['result']
|
|
259
|
+
|
|
260
|
+
# Result emoji
|
|
261
|
+
result_emoji = "❌" if result == 'REJECT' else "✅"
|
|
262
|
+
|
|
263
|
+
lines.append(
|
|
264
|
+
f"| {cat_display} | {name} | {stat} | {pval}{sig} | {df} | {result_emoji} {result} |"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return "\n".join(lines)
|
|
268
|
+
|
|
269
|
+
def export_regression_table(
|
|
270
|
+
self,
|
|
271
|
+
coefficients: List[Dict[str, Any]],
|
|
272
|
+
model_info: Dict[str, Any],
|
|
273
|
+
title: str = "Regression Results"
|
|
274
|
+
) -> str:
|
|
275
|
+
"""
|
|
276
|
+
Export regression results as Markdown.
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
coefficients : list of dict
|
|
281
|
+
Coefficient results
|
|
282
|
+
model_info : dict
|
|
283
|
+
Model information
|
|
284
|
+
title : str, default="Regression Results"
|
|
285
|
+
Table title
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
str
|
|
290
|
+
Markdown table
|
|
291
|
+
|
|
292
|
+
Examples
|
|
293
|
+
--------
|
|
294
|
+
>>> md = exporter.export_regression_table(coefs, info)
|
|
295
|
+
"""
|
|
296
|
+
lines = []
|
|
297
|
+
|
|
298
|
+
lines.append(f"## {title}")
|
|
299
|
+
lines.append("")
|
|
300
|
+
|
|
301
|
+
# Table header
|
|
302
|
+
lines.append("| Variable | Coefficient | Std. Error | t-statistic | P-value |")
|
|
303
|
+
lines.append("|----------|-------------|------------|-------------|---------|")
|
|
304
|
+
|
|
305
|
+
# Coefficient rows
|
|
306
|
+
for coef in coefficients:
|
|
307
|
+
var = coef['variable']
|
|
308
|
+
beta = f"{coef['coefficient']:.4f}"
|
|
309
|
+
se = f"{coef['std_error']:.4f}"
|
|
310
|
+
tstat = f"{coef['t_statistic']:.3f}"
|
|
311
|
+
pval = f"{coef['pvalue']:.4f}"
|
|
312
|
+
|
|
313
|
+
# Significance stars
|
|
314
|
+
if coef['pvalue'] < 0.001:
|
|
315
|
+
stars = "***"
|
|
316
|
+
elif coef['pvalue'] < 0.01:
|
|
317
|
+
stars = "**"
|
|
318
|
+
elif coef['pvalue'] < 0.05:
|
|
319
|
+
stars = "*"
|
|
320
|
+
else:
|
|
321
|
+
stars = ""
|
|
322
|
+
|
|
323
|
+
lines.append(
|
|
324
|
+
f"| {var} | {beta}{stars} | ({se}) | {tstat} | {pval} |"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
lines.append("")
|
|
328
|
+
|
|
329
|
+
# Model statistics
|
|
330
|
+
lines.append("**Model Statistics:**")
|
|
331
|
+
lines.append("")
|
|
332
|
+
if 'r_squared' in model_info:
|
|
333
|
+
lines.append(f"- R²: {model_info['r_squared']:.4f}")
|
|
334
|
+
if 'nobs' in model_info:
|
|
335
|
+
lines.append(f"- Observations: {model_info['nobs']}")
|
|
336
|
+
if 'n_entities' in model_info:
|
|
337
|
+
lines.append(f"- Entities: {model_info['n_entities']}")
|
|
338
|
+
lines.append("")
|
|
339
|
+
|
|
340
|
+
# Note
|
|
341
|
+
lines.append("*Note:* Standard errors in parentheses. Significance: *** p<0.001, ** p<0.01, * p<0.05")
|
|
342
|
+
lines.append("")
|
|
343
|
+
|
|
344
|
+
return "\n".join(lines)
|
|
345
|
+
|
|
346
|
+
def export_summary_stats(
|
|
347
|
+
self,
|
|
348
|
+
stats: List[Dict[str, Any]],
|
|
349
|
+
title: str = "Summary Statistics"
|
|
350
|
+
) -> str:
|
|
351
|
+
"""
|
|
352
|
+
Export summary statistics as Markdown.
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
stats : list of dict
|
|
357
|
+
Variable statistics
|
|
358
|
+
title : str, default="Summary Statistics"
|
|
359
|
+
Table title
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
str
|
|
364
|
+
Markdown table
|
|
365
|
+
|
|
366
|
+
Examples
|
|
367
|
+
--------
|
|
368
|
+
>>> md = exporter.export_summary_stats(stats)
|
|
369
|
+
"""
|
|
370
|
+
lines = []
|
|
371
|
+
|
|
372
|
+
lines.append(f"## {title}")
|
|
373
|
+
lines.append("")
|
|
374
|
+
|
|
375
|
+
# Table header
|
|
376
|
+
lines.append("| Variable | N | Mean | Std. Dev. | Min | Max |")
|
|
377
|
+
lines.append("|----------|---|------|-----------|-----|-----|")
|
|
378
|
+
|
|
379
|
+
# Data rows
|
|
380
|
+
for stat in stats:
|
|
381
|
+
var = stat['variable']
|
|
382
|
+
n = stat['count']
|
|
383
|
+
mean = f"{stat['mean']:.3f}"
|
|
384
|
+
std = f"{stat['std']:.3f}"
|
|
385
|
+
min_val = f"{stat['min']:.3f}"
|
|
386
|
+
max_val = f"{stat['max']:.3f}"
|
|
387
|
+
|
|
388
|
+
lines.append(
|
|
389
|
+
f"| {var} | {n} | {mean} | {std} | {min_val} | {max_val} |"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
lines.append("")
|
|
393
|
+
|
|
394
|
+
return "\n".join(lines)
|
|
395
|
+
|
|
396
|
+
def save(
|
|
397
|
+
self,
|
|
398
|
+
markdown_content: str,
|
|
399
|
+
output_path: Union[str, Path],
|
|
400
|
+
overwrite: bool = False
|
|
401
|
+
) -> Path:
|
|
402
|
+
"""
|
|
403
|
+
Save Markdown content to file.
|
|
404
|
+
|
|
405
|
+
Parameters
|
|
406
|
+
----------
|
|
407
|
+
markdown_content : str
|
|
408
|
+
Markdown content
|
|
409
|
+
output_path : str or Path
|
|
410
|
+
Output file path
|
|
411
|
+
overwrite : bool, default=False
|
|
412
|
+
Overwrite existing file
|
|
413
|
+
|
|
414
|
+
Returns
|
|
415
|
+
-------
|
|
416
|
+
Path
|
|
417
|
+
Path to saved file
|
|
418
|
+
|
|
419
|
+
Examples
|
|
420
|
+
--------
|
|
421
|
+
>>> exporter.save(md, 'report.md')
|
|
422
|
+
"""
|
|
423
|
+
output_path = Path(output_path)
|
|
424
|
+
|
|
425
|
+
# Check if file exists
|
|
426
|
+
if output_path.exists() and not overwrite:
|
|
427
|
+
raise FileExistsError(
|
|
428
|
+
f"File already exists: {output_path}. "
|
|
429
|
+
"Use overwrite=True to replace."
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Create parent directories
|
|
433
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
434
|
+
|
|
435
|
+
# Write file
|
|
436
|
+
output_path.write_text(markdown_content, encoding='utf-8')
|
|
437
|
+
|
|
438
|
+
return output_path
|
|
439
|
+
|
|
440
|
+
def __repr__(self) -> str:
|
|
441
|
+
"""String representation."""
|
|
442
|
+
return (
|
|
443
|
+
f"MarkdownExporter("
|
|
444
|
+
f"include_toc={self.include_toc}, "
|
|
445
|
+
f"github_flavor={self.github_flavor})"
|
|
446
|
+
)
|
|
File without changes
|