Skip to main content

Simple markdown/html report templating with loops, tables, lists and media.

Project description

relator

relator — библиотека шаблонов для автогенерации отчётов в Markdown и HTML.

Главная идея:

  1. Вы задаёте шаблон.
  2. По шагам добавляете данные через template.data([name, value]).
  3. Смотрите предпросмотр template.print().
  4. Компилируете отчёт в файл template.compile(...).

Содержание

  1. Установка
  2. Быстрый старт
  3. Концепция шаблона
  4. Служебные плейсхолдеры
  5. Пошаговый API
  6. Подробные примеры
  7. Тяжёлые сценарии
  8. Media: PIL и matplotlib
  9. CLI
  10. Ошибки и отладка
  11. FAQ
  12. Рекомендации по архитектуре
  13. Кроссплатформенность
  14. Публикация и релиз

Установка

Через 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.


Рекомендации по архитектуре

  1. Храните шаблоны в templates/.
  2. Храните generated отчёты в reports/.
  3. Для медиа используйте отдельную папку assets/.
  4. Не смешивайте бизнес-логику и шаблонную логику.
  5. Добавьте 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

relator-0.1.0.tar.gz (14.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

relator-0.1.0-py3-none-any.whl (16.6 kB view details)

Uploaded Python 3

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

Hashes for relator-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c985960536fa3e49f24ae6cd6c9cc1d3f1a0bd1a6f1312d7c98bc3305a3b99c8
MD5 913bf1d944b9afd0c9a5d02692ce897f
BLAKE2b-256 a773d9f36eff2d8fe1ad27f888a3ee039f09092d396b7cb2724692a3d06e291e

See more details on using hashes here.

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

Hashes for relator-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd556a9ae21335c9671250e997c99f2cd5501f0b9c91d595b49bd0b3a4f00dc2
MD5 de8375b3fb2283d346fe162e090ef586
BLAKE2b-256 3fa42e547e1b7c63a3fa16585b94d198ceba4fb4146af91b0a83775a0a2d0fdc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page