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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.