Share Great Expectations Data Docs
Great Expectations gives you the right artifact and the wrong distribution.
build_data_docs() renders a clean static site — suites, validation
results, which expectation failed on which column, with sample unexpected
values. Then it hands you a folder and the hosting problem.
The standard answer is a bucket. That answer comes with a bucket policy, a decision about whether validation results containing sample row values should be world-readable (they should not), a signed-URL or CDN layer to avoid that, and a cleanup job for the runs nobody will read again. For a team that just wants the analyst to see why last night's load failed, it's a lot of infrastructure between the failure and the person who understands it.
The pattern
Run the checkpoint, then publish the run's HTML:
import pathlib, requests, os
result = context.run_checkpoint(checkpoint_name="nightly_orders")
context.build_data_docs()
html = pathlib.Path(
"great_expectations/uncommitted/data_docs/local_site/index.html"
).read_text()
requests.patch(
f"https://commareports.com/api/v1/reports/{os.environ['REPORT_ID']}",
headers={"Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}"},
json={"title": f"Orders data quality — {result.run_id.run_time:%Y-%m-%d}",
"html": html},
timeout=30,
).raise_for_status()
Create the report once, keep its id in your secret store next to the
scoped token (reports:write is enough), and PATCH
it every run. One URL per suite; a revision per run; any two runs
diffable.
What renders, honestly
Comma stores report HTML verbatim and renders it inside an iframe with
sandbox="allow-scripts" and no allow-same-origin, so a report's own
JavaScript runs. The constraint that bites here is different: GX's
generated site is a multi-file, multi-page artifact — collapsible
sections, inter-suite navigation, and the bundled CSS and JS all live in
sibling files, and a revision holds one HTML document.
Upload those sibling files as assets alongside the HTML and
relative src/href references are rewritten to the uploaded copies. But
for a per-run validation report, a single self-contained page is the
better artifact — it's what a reviewer reads, and it diffs cleanly:
- Build a digest from the validation result JSON, not from the
generated site.
result.list_validation_results()gives you everything a reviewer needs: suite name, batch identifier, each expectation, its status, observed value, and unexpected-value samples. A short template turns that into one HTML table. - Inline the CSS you need, or upload
static/as assets. A relative link tostatic/styles/data_docs_custom_styles.cssresolves only if that file was uploaded with the report. - Attach the full site. Zip
local_site/and add it as an asset — 25 MB per file, 250 MB per report — for anyone who wants to click through the original. - Watch the row samples. Unexpected-value samples are real customer data. That is an argument for a private or team-visible report, and against a public bucket — see the sharing model.
The part a static site can't do
A failed expectation is the start of a conversation, not the end of one.
expect_column_values_to_not_be_null failing on customer_id for 0.4% of
rows is either a pipeline bug or a known upstream quirk, and the person who
knows which one is usually not the person who read the report.
On a published run, the analyst highlights that row and pins a thread to it. The answer lands on the expectation, survives the next thirty nightly runs as revisions accumulate, and is one click away the next time the same check goes red — instead of being a Slack message from March.
The same threads are readable by agents through Comma's MCP server, so a data agent that owns the suite can pick up "this one is expected until the CRM migration lands" without anyone re-explaining it.
Scheduling it
- From your orchestrator — an Airflow task, a dbt post-hook, or a CI job. It's plain HTTPS.
- Or from a routine — a scheduled report keeps the cadence next to the output.
- Announce failures — a webhook on
revision.createdposts the run into Slack, so a red night is a notification rather than a discovery.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history. Publish tonight's checkpoint and send the link.
Related
- Share dbt docs — the neighbouring problem in the same stack
- Share an EDA report — profiling output, same publish pattern
- Comment on an HTML report — how anchored threads work