Share an HTML report from Prefect

Prefect makes the orchestration part pleasant: the flow is Python, the retries are declarative, the run is observable. Then a task writes report.html inside a container from an ephemeral work pool, and the last mile is the same one every pipeline has.

  • The container is temporary. The file is gone with the infrastructure that ran the flow, usually within minutes.
  • Artifacts are summaries. Markdown, tables and links attached to a run — useful, and not the place for a charted document.
  • The UI is behind a login. Prefect Cloud or a self-hosted server, either way scoped to the people who operate pipelines rather than the people who read their output.
  • A bucket relocates the problem. Writing to S3 means the reader now needs bucket credentials to see a report.

Publish from the flow

import json, os, urllib.request
from prefect import flow
from prefect.artifacts import create_link_artifact

REPORT_ID = "rep_…"  # created once; PATCHed from every run

@flow
def daily_quality_report():
    html = build_report_html()
    body = json.dumps({"title": "Daily data quality", "html": html}).encode()
    req = urllib.request.Request(
        f"https://commareports.com/api/v1/reports/{REPORT_ID}",
        data=body,
        method="PATCH",
        headers={
            "Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}",
            "Content-Type": "application/json",
        },
    )
    urllib.request.urlopen(req).read()
    create_link_artifact(
        key="quality-report",
        link=f"https://commareports.com/p/{REPORT_ID}",
        description="Rendered report",
    )

The link artifact is the part that makes this discoverable: the run page in Prefect now points at the document, and the document is readable by people who have never seen a Prefect run page.

Three details that matter:

  • PATCH a saved id, don't POST. POSTing every run scatters orphan links; one id gives the flow a permanent URL and a revision per run.
  • Publish unconditionally. An on_failure hook on the flow publishes the digest for the run that broke, which is the one somebody is asking about.
  • Scope the token to reports:write and keep it in a Secret block — see API tokens.

What the reader gets

  • A URL and nothing to install. No Prefect account, no bucket credentials.
  • The document rendered faithfully — a Plotly figure stays interactive, a pandas table stays a table. See sharing a Plotly chart and sharing a pandas DataFrame.
  • Comments anchored to the failing row — see commenting on HTML.
  • A revision history answering "when did this start drifting", without anyone keeping dated copies in a folder.

Worth knowing

  • Subflows and mapped tasks. Publish once from the parent rather than once per mapped run; the rate limit is 60 requests/minute per token, and 300 separate links help nobody.
  • HTML body: 5 MB. Aggregate inline, extract attached.
  • Multi-file output needs the folder treatment — see sharing an HTML folder.
  • Nothing re-executes on Comma. A published report is a snapshot; if you want a refresh without a scheduler at all, that is a routine.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Create your first report →

Related