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:
- 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>")
- Template Substitution: It reads the template file specified by
self.template_path(which defaults toflyte/report/_template.html) into astring.Templateand substitutes$NAV_HTMLand$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: Whenipython_check()evaluates toTrueandIPythonis installed. Returning anHTMLobject 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:
- Initialization: In
flyte/_internal/runtime/taskrunner.py, the task runner initializes aReportwith the task action name and attaches it toTaskContext:
tctx = TaskContext(
action=action,
report=flyte.report.Report(name=action.name),
)
- Logging: Inside the task body, calls to
flyte.report.log(...),flyte.report.replace(...), orflyte.report.get_tab(...)populate the report attached to the task context. - Flushing: Upon successful task completion, the runtime invokes
await flyte.report.flush.aio(). Theflush()function inflyte/report/_report.pycompiles the HTML viaget_final_report()and streams the payload to the task's output directory usingio.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()andTab.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 fallbackReport("dummy"). Logs written to this dummy instance are not uploaded to remote storage unless you manually compile and export them withget_final_report(). - IPython Execution in Task Flush: In
flyte/report/_report.py,flush()contains an explicitassert isinstance(report_html, str). Ifflush()is invoked in an environment whereipython_check()returnsTrue,get_final_report()returns anIPython.core.display.HTMLinstance, which will cause the type assertion inflush()to fail.