Skip to main content

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

Project description

relator

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

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

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

Полная документация (установка, все плейсхолдеры, интеграции, примеры): каталог docs/.

См. также: поток данных, концепции, примеры вывода Markdown, витрина в prototyping/example/00_showcase, агенты и LLM, обзор для промпта, cookbook.


Содержание

  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

Дополнительно: Pydantic и SQLAlchemy

python -m pip install "relator[pydantic]" "relator[sqlalchemy]"
# или одной группой:
python -m pip install "relator[integrations]"

Быстрый старт

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]]

Слоты @@имя@@

Именованные вставки заполняются после подстановки [[...]] и циклов %%len%% (сырой текст/Markdown/HTML).

  • В шаблоне: @@title_suffix@@
  • В коде: template.slot("title_suffix", " — v2") или render(extra={"__slot__title_suffix": " — v2"})

PYDANTIC / SCHEMA

Нужен пакет: pip install relator[pydantic]. В контексте — подкласс BaseModel, инстанс или TypeAdapter.

  • [[PYDANTIC.VAR]] — краткое описание и таблица полей
  • [[PYDANTIC.VAR.TABLE]] — только таблица полей
  • [[PYDANTIC.VAR.JSON_SCHEMA]] — JSON Schema в блоке кода
  • [[PYDANTIC.VAR.EXAMPLE]] — пример JSON-объекта по полям JSON Schema (эвристика / examples в полях)
  • [[SCHEMA.VAR]] — синоним PYDANTIC

SQL / ORM

  • [[SQL.VAR]] — значение str или SQLAlchemy Executable; вывод в fenced sql. Для компиляции выражений: pip install relator[sqlalchemy] и Template(..., sql_dialect="sqlite") (также postgresql, mysql).
  • [[ORM.VAR.TABLE]] (или .COLUMNS) — таблица колонок для sqlalchemy.Table или mapped-класса с __table__
  • [[ORM.VAR.NAME]] — имя таблицы
  • [[ORM.VAR.DDL]]CREATE TABLE в fenced sql (диалект из sql_dialect)

Доступ к данным из Python (не для шаблона)

  • template.get("name") / get("name", default)
  • template.table_keys("rows") — порядок колонок как у [[TABLE.rows]]
  • template.pick("rows", 0, "USER") — ячейка строки

Пошаговый API

1) Создать объект

from reporting import Template

template = Template("template.md", sql_dialect="sqlite")  # sql_dialect опционален
template.slot("intro", "_Черновик._")

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"], "__slot__intro": "…"},
    output_path="report.md",
)

Ключи __slot__* в context задают слоты @@intro@@ и не используются как переменные для [[...]].


Подробные примеры

Пример 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

Манифест шаблона (JSON)

relator --template template.md --inspect

Вывод — список ожидаемых ключей контекста, слотов @@...@@ и сырых плейсхолдеров (удобно для промпта агента и CI).

Компиляция в файл

relator --template template.md --context context.json --output report.md

Только печать

relator --template template.md --context context.json --print

Пример context.json со слотами

Ключи __slot__имя задают слоты @@имя@@ (не попадают в контекст [[...]]):

{
  "rows": [{"A": 1}],
  "__slot__note": "_Черновик._"
}

Пример 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

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-1.1.0.tar.gz (20.2 kB view details)

Uploaded Source

Built Distribution

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

relator-1.1.0-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

File details

Details for the file relator-1.1.0.tar.gz.

File metadata

  • Download URL: relator-1.1.0.tar.gz
  • Upload date:
  • Size: 20.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for relator-1.1.0.tar.gz
Algorithm Hash digest
SHA256 386248c7801486c08d82a7d9d4593361947c32cecba4697e8f107228c9129869
MD5 bbc83b24eb60d9b19ab746f115d96df2
BLAKE2b-256 df1b7b8f4f283ed2d07e40047976266d90f5b5cb1fcb9a9e930cf5ca7e4370cd

See more details on using hashes here.

File details

Details for the file relator-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: relator-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for relator-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c239c343d6e2aa7327fd91428252137033745e1764c29b5b2669ef9d8976b9a9
MD5 89cadefa98ba10fab6fa012f702ca727
BLAKE2b-256 2abe2fff8675295dd77f23e4a9ec63ec25378c4a80339c93b5af7faa4da19245

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