Skip to main content

Generating and Displaying the Report

When a task produces visual metrics, summaries, or debugging dashboards across multiple tabs, you need a way to compile those tab contents into a cohesive HTML document and either render it immediately or persist it for downstream inspection. In flyte-sdk, the Report dataclass handles the compilation and environment-aware rendering of task reports via its get_final_report() method.

Compiling Multi-Tab Reports with get_final_report()

The Report class manages one or more Tab objects keyed by tab name. By default, every Report initializes with a "main" tab. You can append or replace HTML fragments across any tab before compiling the entire report.

To compile the report into its finalized presentation format, call get_final_report() on the Report instance:

from flyte.report import Report

# Create a standalone report and add tab content
report = Report(name="experiment_summary")
report.get_tab("main").log("<h2>Overview</h2><p>Training complete.</p>")

metrics_tab = report.get_tab("Metrics")
metrics_tab.log("<p>Accuracy: 0.96</p>")

# Compile the final report
output = report.get_final_report()

Template Compilation Mechanism

Under the hood in flyte/report/_report.py, Report.get_final_report() iterates over all registered tabs in self.tabs and performs two-step template substitution:

  1. Navigation and Body Construction: It collects the HTML string from each tab by calling tab.get_html(), which joins the tab's logged fragments with newline characters (\n). Navigation list items are created with escaped names (html.escape(key)), while tab bodies are enclosed in <div> elements without escaping:
tabs = {n: t.get_html() for n, t in self.tabs.items()}
nav_htmls = []
body_htmls = []

for key, value in tabs.items():
nav_htmls.append(f'<li onclick="handleLinkClick(this)">{html.escape(key)}</li>')
body_htmls.append(f"<div>{value}</div>")
  1. Template Substitution: It reads the template file specified by self.template_path (which defaults to flyte/report/_template.html) into a string.Template and substitutes $NAV_HTML and $BODY_HTML:
template = string.Template(self.template_path.open("r").read())
raw_html = template.substitute(NAV_HTML="".join(nav_htmls), BODY_HTML="".join(body_htmls))

Output Formats: HTML String vs. IPython Display Object

get_final_report() detects whether execution is occurring in an interactive IPython/Jupyter notebook environment using ipython_check() from flyte._tools.

The method returns one of two types:

  • IPython.core.display.HTML: When ipython_check() evaluates to True and IPython is installed. Returning an HTML object allows Jupyter and IPython frontends to automatically render the tabbed report in the cell output area.
  • str: In standard Python runtimes or batch task execution environments, returning the raw HTML string for file export or cloud storage upload.
                    ┌─────────────────────────┐
│ Report.get_final_report │
└────────────┬────────────┘

template.substitute(...)


ipython_check()?
/ \
Yes / \ No / ImportError
/ \
▼ ▼
IPython.core.display.HTML str (raw HTML)

Viewing in Notebooks vs. Saving to File

Depending on your execution environment, you can display the report interactively or save the compiled HTML string directly to disk.

Notebook Display

When running inside Jupyter or IPython, calling get_final_report() directly renders the interactive tabs in cell output:

from flyte.report import current_report, get_tab

# In an interactive notebook cell:
tab = get_tab("Visualizations")
tab.log("<svg width='100' height='100'><circle cx='50' cy='50' r='40' fill='green'/></svg>")

# Display the interactive HTML widget in notebook cell output
current_report().get_final_report()

Saving to a Local File

In standalone Python scripts or custom pipelines where get_final_report() returns a raw string (or when converted to string), write the content to an .html file:

from flyte.report import Report

report = Report(name="benchmark_run")
report.get_tab("main").log("<h1>Benchmark Results</h1>")
report.get_tab("Details").log("<pre>latency: 12ms\nthroughput: 1500 rps</pre>")

final_html = report.get_final_report()

with open("report.html", "w", encoding="utf-8") as f:
if isinstance(final_html, str):
f.write(final_html)
else:
# If running in IPython, extract data attribute or convert to str
f.write(final_html.data)

Automated Task Lifecycle and Storage Flushing

When task reports are enabled using @task(report=True) on a Flyte task, the runtime manages the report lifecycle automatically:

  1. Initialization: In flyte/_internal/runtime/taskrunner.py, the task runner initializes a Report with the task action name and attaches it to TaskContext:
tctx = TaskContext(
action=action,
report=flyte.report.Report(name=action.name),
)
  1. Logging: Inside the task body, calls to flyte.report.log(...), flyte.report.replace(...), or flyte.report.get_tab(...) populate the report attached to the task context.
  2. Flushing: Upon successful task completion, the runtime invokes await flyte.report.flush.aio(). The flush() function in flyte/report/_report.py compiles the HTML via get_final_report() and streams the payload to the task's output directory using io.report_path(task_context.output_path) (report.html):
report_html = report.get_final_report()
assert report_html is not None
assert isinstance(report_html, str)
report_path = io.report_path(internal_ctx().data.task_context.output_path)
content_types = {
"Content-Type": "text/html", # For s3
"content_type": "text/html", # For gcs
}
final_path = await storage.put_stream(
report_html.encode("utf-8"),
to_path=report_path,
attributes=content_types,
)

You can also trigger an intermediate flush manually at any time by calling flyte.report.flush() (or passing do_flush=True to log() and replace()).

Important Considerations

  • HTML Fragments vs Full Documents: Tab.log() and Tab.replace() expect valid HTML fragments (e.g., <div>...</div>, <p>...</p>), not complete documents with <!DOCTYPE html>, <html>, or <body> tags. Each tab's content is injected directly into a <div> element within the report template.
  • HTML Escaping: Tab names displayed in the navigation header are automatically sanitized via html.escape(). However, tab body content is inserted raw without escaping so that visual markup and scripts render properly. Ensure any untrusted input added to a tab is sanitized before logging.
  • Dummy Report Outside Task Context: Calling flyte.report.current_report() outside an active Flyte task context does not raise an exception; it returns an unmanaged fallback Report("dummy"). Logs written to this dummy instance are not uploaded to remote storage unless you manually compile and export them with get_final_report().
  • IPython Execution in Task Flush: In flyte/report/_report.py, flush() contains an explicit assert isinstance(report_html, str). If flush() is invoked in an environment where ipython_check() returns True, get_final_report() returns an IPython.core.display.HTML instance, which will cause the type assertion in flush() to fail.