Skip to main content

datadocs

Generate documents from structured data.

datadocs generates Markdown, HTML, or text documents from structured data using Jinja templates.

The philosophy is simple:

Bring your own data. Bring your own template. Generate documents deterministically.

What it is

datadocs is a small Python package for rendering documents from data you already prepared.

Templates receive exactly two variables:

data
params

What it is not

datadocs is not a workflow engine, reporting platform, data transformation framework, query builder, or AI framework.

Installation

pip install datadocs

Optional integrations:

pip install "datadocs[http]"
pip install "datadocs[bigquery]"

CLI

Render from JSON, YAML, or NDJSON:

datadocs render \
  --template report.md.j2 \
  --data report.json

Write to a file:

datadocs render \
  --template report.md.j2 \
  --data report.yaml \
  --output report.md

Pass template parameters:

datadocs render \
  --template report.md.j2 \
  --data report.ndjson \
  --param period="Q2 2026"

SDK

from datadocs import render

markdown = render(
    template_path="templates/report.md.j2",
    data={"epics": [{"name": "Search"}]},
    params={"period": "Q2 2026"},
)

Use Document with data:

from datadocs import Document

doc = Document(
    template_path="templates/report.md.j2",
    data={"epics": [{"name": "Search"}]},
    params={"period": "Q2 2026"},
)

doc.render_to("report.md")

Use Document with a source:

doc = Document(
    template_path="templates/report.md.j2",
    source=my_source,
)

A source only needs to provide:

def load_data(self):
    ...

Architecture

datadocs has one deterministic rendering path:

Source or prepared data -> Document -> render() -> text

The renderer only knows about templates and data. It does not know whether data came from a file, HTTP API, BigQuery query, or a custom source.

Rendering

render() is the core function:

render(template_path, data, params=None)

It loads a Jinja template from disk and renders it with exactly two template variables:

data
params

This keeps templates predictable and avoids hidden context.

Documents

Document is a small wrapper around the rendering contract. It accepts either prepared data or a source, never both:

Document(template_path="report.md.j2", data=data)
Document(template_path="report.md.j2", source=source)

doc.render() returns text. doc.render_to(path) writes that text to disk.

Sources

A source is any object with:

def load_data(self):
    ...

Sources are responsible only for loading ordinary Python data. They should not render templates, transform data, build queries, or manage workflows.

Built-in sources follow that rule:

Source Responsibility
HttpSource Make one HTTP request and return the parsed JSON response.
BigQuerySource Execute one supplied SQL file and return query rows as dictionaries.

Custom sources can be added without changing the renderer:

class MySource:
    def load_data(self):
        return {"epics": [{"name": "Search"}]}


doc = Document(template_path="report.md.j2", source=MySource())

Loaders

File loaders are intentionally separate from sources. The CLI uses loaders for JSON, YAML, and NDJSON files, while integrations use sources.

HTTP

HTTP auth is passed through headers. datadocs does not manage credentials.

datadocs http render \
  --url https://example.com/api \
  --header "Authorization=Bearer $TOKEN" \
  --query-param period="Q2 2026" \
  --template report.md.j2

POST JSON from a file:

datadocs http render \
  --url https://example.com/api \
  --method POST \
  --json-body request.json \
  --template report.md.j2

The parsed JSON response is used directly as data.

The SDK HTTP source currently supports JSON responses. Use transform to adapt API-specific response envelopes before they reach a Document:

from datadocs import Document
from datadocs.http import HttpSource

source = HttpSource(
    url="https://api.example.com/report",
    headers={"Authorization": "Bearer token"},
    response_format="json",
    transform=lambda data: data["data"]["items"],
)

doc = Document(template_path="report.md.j2", source=source)

response_format="json" is the only implemented response format for now. The argument is explicit so future formats such as text or bytes can be added without changing the default behavior.

BigQuery

BigQuery authentication uses Google Application Default Credentials. datadocs does not expose credential flags.

datadocs bigquery render \
  --query queries/report.sql \
  --template report.md.j2 \
  --project runtime-project \
  --billing-project billing-project \
  --location EU

Typed query parameters use name:TYPE=value:

datadocs bigquery render \
  --query queries/report.sql \
  --template report.md.j2 \
  --query-param period:STRING="Q2 2026"

The SQL file is executed as supplied. datadocs does not build or transform queries.

Template example

# Delivery Report

Period: {{ params.period }}

{% for epic in data.epics %}
## {{ epic.name }}
{% endfor %}

Smoke tests

Opt-in smoke tests and demos live under smoke/. They are intentionally kept outside the normal tests/ suite because they use real external services.

The countries smoke test fetches nested JSON from REST Countries, loads it into BigQuery with a rendered SQL template, queries it back, and renders a Markdown report.

Install the optional integration dependencies:

uv sync --extra dev --extra http --extra bigquery

Authenticate with Google Application Default Credentials:

gcloud auth application-default login

Run the smoke test:

DATADOCS_BQ_PROJECT=bigquery-sandbox-cpvuft6h \
DATADOCS_BQ_DATASET=test_datadocs \
DATADOCS_BQ_LOCATION=EU \
DATADOCS_COUNTRIES_API_TOKEN=your-rest-countries-token \
uv run python smoke/countries/run.py

The script prints only the generated report to stdout. If the configured dataset already exists, the smoke test reuses it and does not delete it. If the script creates the dataset, it deletes it by default unless DATADOCS_SMOKE_KEEP_DATASET=1 is set. See smoke/countries/README.md for sample output and cleanup controls.

Manual PyPI publishing

Publishing is currently manual. PyPI versions are immutable, so verify the package before uploading a release.

Build and validate the distribution artifacts:

uv build
uv run python -m twine check dist/*

Upload to PyPI:

uv run python -m twine upload dist/*

Use a real PyPI API token when prompted. If you use ~/.pypirc, keep it outside the repository and restrict its permissions:

chmod 600 ~/.pypirc

Recommended release hygiene:

git status
uv run python -m pytest
uv run ruff check .
uv run python -m mypy
git tag v0.1.0
git push origin v0.1.0

Tagging is not required for manual upload, but it records the exact source used for the published version.

Future integrations

Future integrations such as datadocs.langchain should be optional layers on top of the deterministic rendering pipeline.

Metadata

Release files for datadocs 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for datadocs 0.1.0
File Size Uploaded Provenance Actions
datadocs-0.1.0.tar.gz 7.8 kB Without provenance Details for datadocs-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for datadocs 0.1.0
File Interpreter ABI Platform Actions
datadocs-0.1.0-py3-none-any.whl Python 3 none any Details for datadocs-0.1.0-py3-none-any.whl

Total release size: 20.4 kB

Release files / datadocs-0.1.0.tar.gz

Download URL datadocs-0.1.0.tar.gz
Size 7.8 kB
Tags Source
SHA-256 checksum
How to use checksums
2abd761f83ba894fb10fc0be23271590803e31b97b6c0ddaea5996d6d244159c
BLAKE2b-256 checksum
How to use checksums
29ecfa57d17e8a623c9f49f6462a93f91e9f9d9f19119503e4d1dab5691e1587
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / datadocs-0.1.0-py3-none-any.whl

Download URL datadocs-0.1.0-py3-none-any.whl
Size 12.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
28619bcb81c533b3fd6a198867442f4cde244455b1c6930ab13616cf79abd500
BLAKE2b-256 checksum
How to use checksums
52070c457b98a887c618479f443f71f7b64fd7aff985e94c2354fbd3404b99d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Security for datadocs 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page