relator 0.1.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.
@@ -0,0 +1,608 @@
1
+ Metadata-Version: 2.4
2
+ Name: relator
3
+ Version: 0.1.0
4
+ Summary: Simple markdown/html report templating with loops, tables, lists and media.
5
+ Author: reporting maintainers
6
+ License: MIT
7
+ Project-URL: Homepage, https://example.com/reporting
8
+ Project-URL: Repository, https://example.com/reporting
9
+ Project-URL: Documentation, https://example.com/reporting/docs
10
+ Keywords: report,template,markdown,html
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Operating System :: OS Independent
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: rich
25
+ Requires-Dist: pillow
26
+ Requires-Dist: matplotlib
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # relator
32
+
33
+ `relator` — библиотека шаблонов для автогенерации отчётов в Markdown и HTML.
34
+
35
+ Главная идея:
36
+
37
+ 1. Вы задаёте шаблон.
38
+ 2. По шагам добавляете данные через `template.data([name, value])`.
39
+ 3. Смотрите предпросмотр `template.print()`.
40
+ 4. Компилируете отчёт в файл `template.compile(...)`.
41
+
42
+ ---
43
+
44
+ ## Содержание
45
+
46
+ 1. [Установка](#установка)
47
+ 2. [Быстрый старт](#быстрый-старт)
48
+ 3. [Концепция шаблона](#концепция-шаблона)
49
+ 4. [Служебные плейсхолдеры](#служебные-плейсхолдеры)
50
+ 5. [Пошаговый API](#пошаговый-api)
51
+ 6. [Подробные примеры](#подробные-примеры)
52
+ 7. [Тяжёлые сценарии](#тяжёлые-сценарии)
53
+ 8. [Media: PIL и matplotlib](#media-pil-и-matplotlib)
54
+ 9. [CLI](#cli)
55
+ 10. [Ошибки и отладка](#ошибки-и-отладка)
56
+ 11. [FAQ](#faq)
57
+ 12. [Рекомендации по архитектуре](#рекомендации-по-архитектуре)
58
+ 13. [Кроссплатформенность](#кроссплатформенность)
59
+ 14. [Публикация и релиз](#публикация-и-релиз)
60
+
61
+ ---
62
+
63
+ ## Установка
64
+
65
+ ### Через pip из локального проекта
66
+
67
+ ```bash
68
+ cd reporting
69
+ python -m pip install -e .
70
+ ```
71
+
72
+ ### Через uv из локального проекта
73
+
74
+ ```bash
75
+ cd reporting
76
+ uv pip install -e .
77
+ ```
78
+
79
+ ### Через pip из PyPI
80
+
81
+ ```bash
82
+ python -m pip install relator
83
+ ```
84
+
85
+ ### Через uv из PyPI
86
+
87
+ ```bash
88
+ uv pip install relator
89
+ ```
90
+
91
+ ### Через pip из GitHub
92
+
93
+ ```bash
94
+ python -m pip install "git+https://github.com/threenebula23/Reporting.git"
95
+ ```
96
+
97
+ ### Через uv из GitHub
98
+
99
+ ```bash
100
+ uv pip install "git+https://github.com/threenebula23/Reporting.git"
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Быстрый старт
106
+
107
+ ```python
108
+ from reporting import Template
109
+
110
+ template = Template("template.md")
111
+ template.data(["name", [{"USER": "Ann", "SCORE": 10}, {"USER": "Bob", "SCORE": 20}]])
112
+ template.data(["names", ["Ann", "Bob", "Chris"]])
113
+ template.print()
114
+ template.compile("report.md")
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Концепция шаблона
120
+
121
+ Шаблон — это обычный `.md` или `.html` файл.
122
+
123
+ Внутри него используются:
124
+
125
+ - блоки повторения `%%len=VAR ... %%`
126
+ - плейсхолдеры `[[...]]`
127
+
128
+ Пример:
129
+
130
+ ```text
131
+ %%len=name
132
+ Строка [[CELL.ONE]] из [[CELL.COUNT]]
133
+ |[[name.KEYS]]|
134
+ |[[name.DIVIDER]]|
135
+ |[[name.VALUES]]|
136
+ %%
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Служебные плейсхолдеры
142
+
143
+ Важно: **служебные названия всегда в верхнем регистре**.
144
+
145
+ ### CELL
146
+
147
+ - `[[CELL.INDEX]]` — индекс 0..N-1
148
+ - `[[CELL.ONE]]` — индекс 1..N
149
+ - `[[CELL.COUNT]]` — длина текущего цикла
150
+
151
+ ### ITEM
152
+
153
+ - `[[ITEM]]` — текущий элемент в теле `%%len=VAR%%`
154
+
155
+ ### TABLE
156
+
157
+ - `[[TABLE.VAR]]` — таблица
158
+ - `[[TABLE.VAR.NUMBERED]]` — таблица с колонкой `#`
159
+ - `[[TABLE.VAR.INDEX0]]` — таблица с колонкой index0
160
+
161
+ ### LIST / ENUM
162
+
163
+ - `[[LIST.VAR]]` — маркированный список
164
+ - `[[ENUM.VAR]]` — нумерованный список
165
+
166
+ ### MEDIA
167
+
168
+ - `[[MEDIA.VAR]]` — универсальная вставка по типу шаблона
169
+ - `[[MEDIA.VAR.PATH]]` — путь до файла
170
+ - `[[MEDIA.VAR.MD]]` — принудительный Markdown
171
+ - `[[MEDIA.VAR.HTML]]` — принудительный HTML
172
+
173
+ ### Совместимость
174
+
175
+ - `[[VAR.TABLE]]`
176
+ - `[[VAR.TABLE.NUMBERED]]`
177
+
178
+ ---
179
+
180
+ ## Пошаговый API
181
+
182
+ ### 1) Создать объект
183
+
184
+ ```python
185
+ from reporting import Template
186
+ template = Template("template.md")
187
+ ```
188
+
189
+ ### 2) Добавить данные по одной переменной
190
+
191
+ ```python
192
+ template.data(["name", [{"USER": "Ann", "SCORE": 10}]])
193
+ template.data(["names", ["Ann", "Bob"]])
194
+ ```
195
+
196
+ ### 3) Посмотреть результат в консоли
197
+
198
+ ```python
199
+ template.print(width=100)
200
+ ```
201
+
202
+ ### 4) Скомпилировать в файл
203
+
204
+ ```python
205
+ template.compile("report.md")
206
+ ```
207
+
208
+ ### Функциональный шорткат
209
+
210
+ ```python
211
+ from reporting import compile_template
212
+
213
+ compile_template(
214
+ template_path="template.md",
215
+ context={"name": [{"A": 1}], "names": ["x", "y"]},
216
+ output_path="report.md",
217
+ )
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Подробные примеры
223
+
224
+ ### Пример A: таблица внутри цикла
225
+
226
+ Шаблон:
227
+
228
+ ```text
229
+ %%len=rows
230
+ |[[rows.KEYS]]|
231
+ |[[rows.DIVIDER]]|
232
+ |[[rows.VALUES]]|
233
+ %%
234
+ ```
235
+
236
+ Данные:
237
+
238
+ ```python
239
+ rows = [
240
+ {"CITY": "Paris", "POP": 2148},
241
+ {"CITY": "Berlin", "POP": 3769},
242
+ ]
243
+ ```
244
+
245
+ ### Пример B: таблица целиком
246
+
247
+ Шаблон:
248
+
249
+ ```text
250
+ [[TABLE.rows]]
251
+ [[TABLE.rows.NUMBERED]]
252
+ ```
253
+
254
+ ### Пример C: списки
255
+
256
+ Шаблон:
257
+
258
+ ```text
259
+ [[LIST.names]]
260
+ [[ENUM.names]]
261
+ ```
262
+
263
+ Данные:
264
+
265
+ ```python
266
+ names = ["Ann", "Bob", "Chris", "Dina"]
267
+ ```
268
+
269
+ ### Пример D: вложенный список
270
+
271
+ Шаблон:
272
+
273
+ ```text
274
+ %%len=names
275
+ 1. [[ITEM]]
276
+ - index: [[CELL.INDEX]]
277
+ - one: [[CELL.ONE]]
278
+ %%
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Тяжёлые сценарии
284
+
285
+ Ниже сценарии ближе к реальному продакшену.
286
+
287
+ ### Сценарий 1: еженедельный отчёт команды
288
+
289
+ Цель:
290
+
291
+ - по командам показать KPI
292
+ - дать агрегированную таблицу
293
+ - вставить график
294
+
295
+ Шаблон `weekly.md`:
296
+
297
+ ```text
298
+ # Еженедельный отчёт
299
+
300
+ ## Команды
301
+
302
+ %%len=teams
303
+ ### Команда [[ITEM]]
304
+ Индекс: [[CELL.ONE]] / [[CELL.COUNT]]
305
+ %%
306
+
307
+ ## KPI
308
+ [[TABLE.kpi.NUMBERED]]
309
+
310
+ ## График
311
+ [[MEDIA.kpi_chart]]
312
+ ```
313
+
314
+ Код:
315
+
316
+ ```python
317
+ from reporting import Template
318
+ import matplotlib.pyplot as plt
319
+
320
+ fig = plt.figure(figsize=(6, 3))
321
+ ax = fig.add_subplot(111)
322
+ ax.plot([1, 2, 3, 4], [95, 97, 96, 99], marker="o")
323
+ ax.set_title("KPI динамика")
324
+
325
+ template = Template("weekly.md", assets_dir="weekly_assets")
326
+ template.data(["teams", ["Backend", "Frontend", "QA"]])
327
+ template.data(["kpi", [
328
+ {"METRIC": "Coverage", "VALUE": "82%"},
329
+ {"METRIC": "Incidents", "VALUE": "2"},
330
+ {"METRIC": "Deploys", "VALUE": "14"},
331
+ ]])
332
+ template.data(["kpi_chart", fig])
333
+ template.print()
334
+ template.compile("weekly_report.md")
335
+ ```
336
+
337
+ ### Сценарий 2: HTML-отчёт для менеджмента
338
+
339
+ Шаблон `management.html`:
340
+
341
+ ```html
342
+ <h1>Отчёт менеджмента</h1>
343
+ <h2>Сводная таблица</h2>
344
+ [[TABLE.metrics.HTML]]
345
+ <h2>Визуализация</h2>
346
+ [[MEDIA.chart]]
347
+ ```
348
+
349
+ Код:
350
+
351
+ ```python
352
+ from reporting import Template
353
+ from PIL import Image
354
+
355
+ logo = Image.new("RGB", (160, 60), "navy")
356
+
357
+ template = Template("management.html", assets_dir="html_assets")
358
+ template.data(["metrics", [
359
+ {"NAME": "Revenue", "VALUE": "1.2M"},
360
+ {"NAME": "Cost", "VALUE": "0.7M"},
361
+ ]])
362
+ template.data(["chart", logo])
363
+ template.compile("management_report.html")
364
+ ```
365
+
366
+ ### Сценарий 3: много отчётов в цикле по проектам
367
+
368
+ ```python
369
+ from pathlib import Path
370
+ from reporting import Template
371
+
372
+ projects = {
373
+ "alpha": {"issues": 14, "coverage": "79%"},
374
+ "beta": {"issues": 4, "coverage": "88%"},
375
+ "gamma": {"issues": 0, "coverage": "93%"},
376
+ }
377
+
378
+ for project_name, data in projects.items():
379
+ t = Template("project_template.md", assets_dir=Path("assets") / project_name)
380
+ t.data(["project_name", project_name])
381
+ t.data(["metrics", [{"KEY": "issues", "VALUE": data["issues"]}, {"KEY": "coverage", "VALUE": data["coverage"]}]])
382
+ t.compile(Path("reports") / f"{project_name}.md")
383
+ ```
384
+
385
+ ### Сценарий 4: пакетная генерация + предпросмотр только при ошибках
386
+
387
+ ```python
388
+ from reporting import Template, TemplateError
389
+
390
+ def render_safe(template_path: str, out_path: str, context: dict):
391
+ t = Template(template_path)
392
+ for k, v in context.items():
393
+ t.data([k, v])
394
+ try:
395
+ t.compile(out_path)
396
+ except TemplateError:
397
+ t.print()
398
+ raise
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Media: PIL и matplotlib
404
+
405
+ ### PIL.Image
406
+
407
+ ```python
408
+ from PIL import Image
409
+ from reporting import Template
410
+
411
+ img = Image.new("RGB", (120, 40), "steelblue")
412
+ t = Template("template.md", assets_dir="assets")
413
+ t.data(["logo", img])
414
+ t.compile("report.md")
415
+ ```
416
+
417
+ ### matplotlib.figure.Figure
418
+
419
+ ```python
420
+ import matplotlib.pyplot as plt
421
+ from reporting import Template
422
+
423
+ fig = plt.figure()
424
+ ax = fig.add_subplot(111)
425
+ ax.bar(["A", "B", "C"], [3, 7, 5])
426
+
427
+ t = Template("template.md")
428
+ t.data(["chart", fig])
429
+ t.compile("report.md")
430
+ ```
431
+
432
+ ### Управление вставкой
433
+
434
+ ```text
435
+ [[MEDIA.chart]] # авто (md/html)
436
+ [[MEDIA.chart.PATH]] # только путь
437
+ [[MEDIA.chart.MD]] # строго markdown
438
+ [[MEDIA.chart.HTML]] # строго html
439
+ ```
440
+
441
+ ---
442
+
443
+ ## CLI
444
+
445
+ ### Компиляция в файл
446
+
447
+ ```bash
448
+ relator --template template.md --context context.json --output report.md
449
+ ```
450
+
451
+ ### Только печать
452
+
453
+ ```bash
454
+ relator --template template.md --context context.json --print
455
+ ```
456
+
457
+ ### Пример context.json
458
+
459
+ ```json
460
+ {
461
+ "name": [
462
+ {"USER": "Ann", "SCORE": 10},
463
+ {"USER": "Bob", "SCORE": 20}
464
+ ],
465
+ "names": ["Ann", "Bob", "Chris"]
466
+ }
467
+ ```
468
+
469
+ ---
470
+
471
+ ## Ошибки и отладка
472
+
473
+ ### `TemplateError: Unknown placeholder root ...`
474
+
475
+ Причина:
476
+
477
+ - в шаблоне используется переменная, которую не добавили через `data`.
478
+
479
+ Решение:
480
+
481
+ - проверить имя переменной и регистр.
482
+
483
+ ### `CELL.* is available only inside %%len=VAR%%`
484
+
485
+ Причина:
486
+
487
+ - `CELL` использован вне цикла.
488
+
489
+ Решение:
490
+
491
+ - перенести плейсхолдер в тело `%%len=...%%`.
492
+
493
+ ### `VAR.VALUES requires %%len=VAR%% context`
494
+
495
+ Причина:
496
+
497
+ - `[[VAR.VALUES]]` использован вне своего цикла.
498
+
499
+ Решение:
500
+
501
+ - использовать `[[TABLE.VAR]]` или обернуть в `%%len=VAR%%`.
502
+
503
+ ---
504
+
505
+ ## FAQ
506
+
507
+ ### Можно ли использовать lowercase имена переменных?
508
+
509
+ Да. Например `name`, `rows`, `items`.
510
+
511
+ ### Почему служебные плейсхолдеры uppercase?
512
+
513
+ Чтобы отделять пользовательские данные от системных токенов.
514
+
515
+ ### Можно ли использовать библиотеку без Rich?
516
+
517
+ Да, но метод `print()` требует `rich`.
518
+
519
+ ### Можно ли рендерить только в строку без файла?
520
+
521
+ Да, через `template.render()`.
522
+
523
+ ### Поддерживается ли Windows?
524
+
525
+ Да, библиотека использует `pathlib` и UTF-8.
526
+
527
+ ---
528
+
529
+ ## Рекомендации по архитектуре
530
+
531
+ 1. Храните шаблоны в `templates/`.
532
+ 2. Храните generated отчёты в `reports/`.
533
+ 3. Для медиа используйте отдельную папку `assets/`.
534
+ 4. Не смешивайте бизнес-логику и шаблонную логику.
535
+ 5. Добавьте golden-тесты для ключевых шаблонов.
536
+
537
+ Пример структуры:
538
+
539
+ ```text
540
+ project/
541
+ templates/
542
+ weekly.md
543
+ management.html
544
+ reports/
545
+ assets/
546
+ scripts/
547
+ build_reports.py
548
+ ```
549
+
550
+ ---
551
+
552
+ ## Кроссплатформенность
553
+
554
+ - операции с путями через `pathlib.Path`
555
+ - чтение/запись через UTF-8
556
+ - CI-матрица: Linux, macOS, Windows
557
+ - тестовая матрица Python: 3.9–3.13
558
+
559
+ ---
560
+
561
+ ## Публикация и релиз
562
+
563
+ ### Локальная сборка
564
+
565
+ ```bash
566
+ cd reporting
567
+ python -m pip install --upgrade build
568
+ python -m build
569
+ ```
570
+
571
+ ### Публикация в TestPyPI
572
+
573
+ ```bash
574
+ python -m pip install --upgrade twine
575
+ python -m twine upload --repository testpypi dist/*
576
+ ```
577
+
578
+ ### Публикация в PyPI
579
+
580
+ ```bash
581
+ python -m twine upload dist/*
582
+ ```
583
+
584
+ ### Проверка установки (smoke test)
585
+
586
+ ```bash
587
+ python -m pip install "git+https://github.com/threenebula23/Reporting.git"
588
+ python -c "from reporting import Template; print(Template)"
589
+ ```
590
+
591
+ ---
592
+
593
+ ## Минимальный чеклист перед релизом
594
+
595
+ - [ ] Все тесты зелёные
596
+ - [ ] README актуален
597
+ - [ ] Версия обновлена
598
+ - [ ] CHANGELOG заполнен
599
+ - [ ] Пакет собирается (`sdist` + `wheel`)
600
+ - [ ] Проверена установка через `pip`
601
+ - [ ] Проверена установка через `uv`
602
+
603
+ ---
604
+
605
+ ## Лицензия
606
+
607
+ MIT.
608
+
@@ -0,0 +1,15 @@
1
+ relator-0.1.0.dist-info/licenses/LICENSE,sha256=b9T0kEVk60_9TWL3dlEZfRoRKfGQX_VSIZz3Wuw8S3k,1078
2
+ reporting/__init__.py,sha256=pbXkLtap_5V-oI2_-HOzMYqSFg2HKW51B1FaZbYtmt4,190
3
+ reporting/builtins.py,sha256=eyUu7D5JHvIf4918TNbwt5l7MHFxUzODHTlbOquxGUU,7832
4
+ reporting/cli.py,sha256=lbXraIAYiPZJVFymN76w9r28lwqVnMDPr83RN_Qjhxs,1532
5
+ reporting/engine.py,sha256=UrHPEey97MbS3pQ4WvH1PExGmnRI-S-rLKdqZTF7B_s,5242
6
+ reporting/errors.py,sha256=dW4QcYHqblpD9Wdgx89RH1_kTHcq5hsovUtQrn_xvcw,298
7
+ reporting/media.py,sha256=6A_US84mCcYiovU-PEGL7nyoX6-TLCetWQ5FHlq9sBw,3155
8
+ reporting/parser.py,sha256=3S6POZs__-QvhXF9dk6KXjfpnMmmJ9413o-3aJ3aQQg,2057
9
+ reporting/resolver.py,sha256=JZxczHi2Tpg0HdmuU1DviP_1PEB0X9zsr3RviVwV0N4,1796
10
+ reporting/template.py,sha256=XMheByNSGKCSJQAgknlPdpXAq553a-mW5u6zOeIvB7k,3750
11
+ relator-0.1.0.dist-info/METADATA,sha256=lzEP4t_Yu2OhSNf_3KF2t3ccVXjy6-93PJKaatj9xgQ,13844
12
+ relator-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
13
+ relator-0.1.0.dist-info/entry_points.txt,sha256=j7vxAyLr2h-8GtDunCChkcxewhNw4tAqfnfpS687EcM,47
14
+ relator-0.1.0.dist-info/top_level.txt,sha256=4KHGYNPUQIGgNtRdbExkO0ueGRwX45dWfTannK_mf1c,10
15
+ relator-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ relator = reporting.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 reporting maintainers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ reporting
reporting/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Public exports for reporting package."""
2
+
3
+ from .errors import TemplateError
4
+ from .template import Template, compile_template
5
+
6
+ __all__ = ["Template", "TemplateError", "compile_template"]
7
+