Simple markdown/html report templating with loops, tables, lists and media.
Project description
relator
relator — библиотека шаблонов для автогенерации отчётов в Markdown и HTML.
Главная идея:
- Вы задаёте шаблон.
- По шагам добавляете данные через
template.data([name, value]). - Смотрите предпросмотр
template.print(). - Компилируете отчёт в файл
template.compile(...).
Содержание
- Установка
- Быстрый старт
- Концепция шаблона
- Служебные плейсхолдеры
- Пошаговый API
- Подробные примеры
- Тяжёлые сценарии
- Media: PIL и matplotlib
- CLI
- Ошибки и отладка
- FAQ
- Рекомендации по архитектуре
- Кроссплатформенность
- Публикация и релиз
Установка
Через pip из локального проекта
cd reporting
python -m pip install -e .
Через uv из локального проекта
cd reporting
uv pip install -e .
Через pip из PyPI
python -m pip install relator
Через uv из PyPI
uv pip install relator
Через pip из GitHub
python -m pip install "git+https://github.com/threenebula23/Reporting.git"
Через uv из GitHub
uv pip install "git+https://github.com/threenebula23/Reporting.git"
Быстрый старт
from reporting import Template
template = Template("template.md")
template.data(["name", [{"USER": "Ann", "SCORE": 10}, {"USER": "Bob", "SCORE": 20}]])
template.data(["names", ["Ann", "Bob", "Chris"]])
template.print()
template.compile("report.md")
Концепция шаблона
Шаблон — это обычный .md или .html файл.
Внутри него используются:
- блоки повторения
%%len=VAR ... %% - плейсхолдеры
[[...]]
Пример:
%%len=name
Строка [[CELL.ONE]] из [[CELL.COUNT]]
|[[name.KEYS]]|
|[[name.DIVIDER]]|
|[[name.VALUES]]|
%%
Служебные плейсхолдеры
Важно: служебные названия всегда в верхнем регистре.
CELL
[[CELL.INDEX]]— индекс 0..N-1[[CELL.ONE]]— индекс 1..N[[CELL.COUNT]]— длина текущего цикла
ITEM
[[ITEM]]— текущий элемент в теле%%len=VAR%%
TABLE
[[TABLE.VAR]]— таблица[[TABLE.VAR.NUMBERED]]— таблица с колонкой#[[TABLE.VAR.INDEX0]]— таблица с колонкой index0
LIST / ENUM
[[LIST.VAR]]— маркированный список[[ENUM.VAR]]— нумерованный список
MEDIA
[[MEDIA.VAR]]— универсальная вставка по типу шаблона[[MEDIA.VAR.PATH]]— путь до файла[[MEDIA.VAR.MD]]— принудительный Markdown[[MEDIA.VAR.HTML]]— принудительный HTML
Совместимость
[[VAR.TABLE]][[VAR.TABLE.NUMBERED]]
Пошаговый API
1) Создать объект
from reporting import Template
template = Template("template.md")
2) Добавить данные по одной переменной
template.data(["name", [{"USER": "Ann", "SCORE": 10}]])
template.data(["names", ["Ann", "Bob"]])
3) Посмотреть результат в консоли
template.print(width=100)
4) Скомпилировать в файл
template.compile("report.md")
Функциональный шорткат
from reporting import compile_template
compile_template(
template_path="template.md",
context={"name": [{"A": 1}], "names": ["x", "y"]},
output_path="report.md",
)
Подробные примеры
Пример A: таблица внутри цикла
Шаблон:
%%len=rows
|[[rows.KEYS]]|
|[[rows.DIVIDER]]|
|[[rows.VALUES]]|
%%
Данные:
rows = [
{"CITY": "Paris", "POP": 2148},
{"CITY": "Berlin", "POP": 3769},
]
Пример B: таблица целиком
Шаблон:
[[TABLE.rows]]
[[TABLE.rows.NUMBERED]]
Пример C: списки
Шаблон:
[[LIST.names]]
[[ENUM.names]]
Данные:
names = ["Ann", "Bob", "Chris", "Dina"]
Пример D: вложенный список
Шаблон:
%%len=names
1. [[ITEM]]
- index: [[CELL.INDEX]]
- one: [[CELL.ONE]]
%%
Тяжёлые сценарии
Ниже сценарии ближе к реальному продакшену.
Сценарий 1: еженедельный отчёт команды
Цель:
- по командам показать KPI
- дать агрегированную таблицу
- вставить график
Шаблон weekly.md:
# Еженедельный отчёт
## Команды
%%len=teams
### Команда [[ITEM]]
Индекс: [[CELL.ONE]] / [[CELL.COUNT]]
%%
## KPI
[[TABLE.kpi.NUMBERED]]
## График
[[MEDIA.kpi_chart]]
Код:
from reporting import Template
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6, 3))
ax = fig.add_subplot(111)
ax.plot([1, 2, 3, 4], [95, 97, 96, 99], marker="o")
ax.set_title("KPI динамика")
template = Template("weekly.md", assets_dir="weekly_assets")
template.data(["teams", ["Backend", "Frontend", "QA"]])
template.data(["kpi", [
{"METRIC": "Coverage", "VALUE": "82%"},
{"METRIC": "Incidents", "VALUE": "2"},
{"METRIC": "Deploys", "VALUE": "14"},
]])
template.data(["kpi_chart", fig])
template.print()
template.compile("weekly_report.md")
Сценарий 2: HTML-отчёт для менеджмента
Шаблон management.html:
<h1>Отчёт менеджмента</h1>
<h2>Сводная таблица</h2>
[[TABLE.metrics.HTML]]
<h2>Визуализация</h2>
[[MEDIA.chart]]
Код:
from reporting import Template
from PIL import Image
logo = Image.new("RGB", (160, 60), "navy")
template = Template("management.html", assets_dir="html_assets")
template.data(["metrics", [
{"NAME": "Revenue", "VALUE": "1.2M"},
{"NAME": "Cost", "VALUE": "0.7M"},
]])
template.data(["chart", logo])
template.compile("management_report.html")
Сценарий 3: много отчётов в цикле по проектам
from pathlib import Path
from reporting import Template
projects = {
"alpha": {"issues": 14, "coverage": "79%"},
"beta": {"issues": 4, "coverage": "88%"},
"gamma": {"issues": 0, "coverage": "93%"},
}
for project_name, data in projects.items():
t = Template("project_template.md", assets_dir=Path("assets") / project_name)
t.data(["project_name", project_name])
t.data(["metrics", [{"KEY": "issues", "VALUE": data["issues"]}, {"KEY": "coverage", "VALUE": data["coverage"]}]])
t.compile(Path("reports") / f"{project_name}.md")
Сценарий 4: пакетная генерация + предпросмотр только при ошибках
from reporting import Template, TemplateError
def render_safe(template_path: str, out_path: str, context: dict):
t = Template(template_path)
for k, v in context.items():
t.data([k, v])
try:
t.compile(out_path)
except TemplateError:
t.print()
raise
Media: PIL и matplotlib
PIL.Image
from PIL import Image
from reporting import Template
img = Image.new("RGB", (120, 40), "steelblue")
t = Template("template.md", assets_dir="assets")
t.data(["logo", img])
t.compile("report.md")
matplotlib.figure.Figure
import matplotlib.pyplot as plt
from reporting import Template
fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(["A", "B", "C"], [3, 7, 5])
t = Template("template.md")
t.data(["chart", fig])
t.compile("report.md")
Управление вставкой
[[MEDIA.chart]] # авто (md/html)
[[MEDIA.chart.PATH]] # только путь
[[MEDIA.chart.MD]] # строго markdown
[[MEDIA.chart.HTML]] # строго html
CLI
Компиляция в файл
relator --template template.md --context context.json --output report.md
Только печать
relator --template template.md --context context.json --print
Пример context.json
{
"name": [
{"USER": "Ann", "SCORE": 10},
{"USER": "Bob", "SCORE": 20}
],
"names": ["Ann", "Bob", "Chris"]
}
Ошибки и отладка
TemplateError: Unknown placeholder root ...
Причина:
- в шаблоне используется переменная, которую не добавили через
data.
Решение:
- проверить имя переменной и регистр.
CELL.* is available only inside %%len=VAR%%
Причина:
CELLиспользован вне цикла.
Решение:
- перенести плейсхолдер в тело
%%len=...%%.
VAR.VALUES requires %%len=VAR%% context
Причина:
[[VAR.VALUES]]использован вне своего цикла.
Решение:
- использовать
[[TABLE.VAR]]или обернуть в%%len=VAR%%.
FAQ
Можно ли использовать lowercase имена переменных?
Да. Например name, rows, items.
Почему служебные плейсхолдеры uppercase?
Чтобы отделять пользовательские данные от системных токенов.
Можно ли использовать библиотеку без Rich?
Да, но метод print() требует rich.
Можно ли рендерить только в строку без файла?
Да, через template.render().
Поддерживается ли Windows?
Да, библиотека использует pathlib и UTF-8.
Рекомендации по архитектуре
- Храните шаблоны в
templates/. - Храните generated отчёты в
reports/. - Для медиа используйте отдельную папку
assets/. - Не смешивайте бизнес-логику и шаблонную логику.
- Добавьте golden-тесты для ключевых шаблонов.
Пример структуры:
project/
templates/
weekly.md
management.html
reports/
assets/
scripts/
build_reports.py
Кроссплатформенность
- операции с путями через
pathlib.Path - чтение/запись через UTF-8
- CI-матрица: Linux, macOS, Windows
- тестовая матрица Python: 3.9–3.13
Публикация и релиз
Локальная сборка
cd reporting
python -m pip install --upgrade build
python -m build
Публикация в TestPyPI
python -m pip install --upgrade twine
python -m twine upload --repository testpypi dist/*
Публикация в PyPI
python -m twine upload dist/*
Проверка установки (smoke test)
python -m pip install "git+https://github.com/threenebula23/Reporting.git"
python -c "from reporting import Template; print(Template)"
Минимальный чеклист перед релизом
- Все тесты зелёные
- README актуален
- Версия обновлена
- CHANGELOG заполнен
- Пакет собирается (
sdist+wheel) - Проверена установка через
pip - Проверена установка через
uv
Лицензия
MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file relator-0.1.0.tar.gz.
File metadata
- Download URL: relator-0.1.0.tar.gz
- Upload date:
- Size: 14.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c985960536fa3e49f24ae6cd6c9cc1d3f1a0bd1a6f1312d7c98bc3305a3b99c8
|
|
| MD5 |
913bf1d944b9afd0c9a5d02692ce897f
|
|
| BLAKE2b-256 |
a773d9f36eff2d8fe1ad27f888a3ee039f09092d396b7cb2724692a3d06e291e
|
File details
Details for the file relator-0.1.0-py3-none-any.whl.
File metadata
- Download URL: relator-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd556a9ae21335c9671250e997c99f2cd5501f0b9c91d595b49bd0b3a4f00dc2
|
|
| MD5 |
de8375b3fb2283d346fe162e090ef586
|
|
| BLAKE2b-256 |
3fa42e547e1b7c63a3fa16585b94d198ceba4fb4146af91b0a83775a0a2d0fdc
|