Custom Rendering in the UI
When working with Flyte, you may need to display rich, interactive representations of your custom data types directly within the Flyte UI. The flyte-sdk provides a mechanism for this through the Renderable protocol and several built-in renderers for common data structures like Markdown, pandas DataFrames, and Python source code.
Implementing Custom Renderers with Renderable
To provide a custom HTML representation for your own Python objects in the Flyte UI, implement the flyte.types.Renderable protocol. This protocol requires a single method, to_html, which takes the Python object as input and returns an HTML string.
The Renderable protocol is defined as:
@runtime_checkable
class Renderable(Protocol):
def to_html(self, python_value: Any) -> str:
"""Convert an object(markdown, pandas.dataframe) to HTML and return HTML as a unicode string.
Returns: An HTML document as a string.
"""
raise NotImplementedError
Here's an example of a custom class, MyCustomRenderer, that implements the Renderable protocol to display a simple message:
from typing import Any, Protocol, runtime_checkable
from flyte.types import Renderable
class MyCustomObject:
def __init__(self, message: str):
self.message = message
class MyCustomRenderer(Renderable):
def to_html(self, python_value: Any) -> str:
# Assuming python_value will be MyCustomObject at runtime
return f"<h1>Custom Object Display</h1><p>Message: {python_value.message}</p>"
To use this custom renderer with your tasks, annotate the output type of your task with typing.Annotated and an instance of your renderer.
from flytekit import task, workflow
from typing import Annotated, Any
from flyte.types import Renderable
# Re-defining MyCustomObject and MyCustomRenderer for self-contained example
class MyCustomObject:
def __init__(self, message: str):
self.message = message
class MyCustomRenderer(Renderable):
def to_html(self, python_value: Any) -> str:
return f"<h1>Custom Object Display</h1><p>Message: {python_value.message}</p>"
@task
def create_custom_object(message: str) -> Annotated[MyCustomObject, MyCustomRenderer()]:
return MyCustomObject(message)
@workflow
def custom_rendering_workflow(message: str) -> Annotated[MyCustomObject, MyCustomRenderer()]:
return create_custom_object(message=message)
Rendering Markdown with MarkdownRenderer
The flyte.types._renderer.MarkdownRenderer class converts Markdown strings into HTML for display in the Flyte UI. This is useful for providing rich text descriptions or formatted content.
The MarkdownRenderer is defined as:
class MarkdownRenderer:
"""Convert a markdown string to HTML and return HTML as a unicode string."""
def to_html(self, text: str) -> str:
return MarkdownIt().render(text)
To render a Markdown string, annotate your task's output with an instance of MarkdownRenderer:
This example could not be verified against this version of the codebase and may not work as shown. Validator finding: unterminated triple-quoted string literal (detected at line 16)
from flytekit import task, workflow
from typing import Annotated
from flyte.types._renderer import MarkdownRenderer
@task
def generate_markdown_report(name: str) -> Annotated[str, MarkdownRenderer()]:
markdown_content = """
# Report for {name}
This is a **sample report** generated by a Flyte task.
- Item 1
- Item 2
```python
print("Hello, Flyte!")
""".format(name=name) return markdown_content
@workflow def markdown_workflow(name: str) -> Annotated[str, MarkdownRenderer()]: return generate_markdown_report(name=name)
### Rendering Pandas DataFrames with `TopFrameRenderer`
For displaying `pandas.DataFrame` objects, `flyte-sdk` provides the `flyte.types._renderer.TopFrameRenderer`. This renderer converts a DataFrame into an HTML table, allowing you to inspect its contents directly in the Flyte UI.
The `TopFrameRenderer` is defined as:
```python
class TopFrameRenderer:
"""
Render a DataFrame as an HTML table.
"""
def __init__(self, max_rows: int = 10, max_cols: int = 100):
self._max_rows = max_rows
self._max_cols = max_cols
def to_html(self, df: "pandas.DataFrame") -> str:
assert isinstance(df, pandas.DataFrame)
return df.to_html(max_rows=self._max_rows, max_cols=self._max_cols)
You can configure the maximum number of rows and columns to display using the max_rows and max_cols parameters during initialization. By default, it displays up to 10 rows and 100 columns.
Here's how to use TopFrameRenderer with a pandas.DataFrame:
import pandas as pd
from flytekit import task, workflow
from typing import Annotated
from flyte.types._renderer import TopFrameRenderer
@task
def create_dataframe(num_rows: int) -> Annotated[pd.DataFrame, TopFrameRenderer(max_rows=5)]:
data = {"col1": range(num_rows), "col2": [f"value_{i}" for i in range(num_rows)]}
df = pd.DataFrame(data)
return df
@workflow
def dataframe_workflow(num_rows: int) -> Annotated[pd.DataFrame, TopFrameRenderer(max_rows=5)]:
return create_dataframe(num_rows=num_rows)
Rendering Python Source Code with SourceCodeRenderer
The flyte.types._renderer.SourceCodeRenderer is designed to convert Python source code into syntax-highlighted HTML. This is particularly useful for displaying code snippets, task definitions, or other Python code within the Flyte UI.
The SourceCodeRenderer is defined as:
class SourceCodeRenderer:
"""
Convert Python source code to HTML, and return HTML as a unicode string.
"""
def __init__(self, title: str = "Source Code"):
self._title = title
def to_html(self, source_code: str) -> str:
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers.python import PythonLexer
formatter = HtmlFormatter(style="colorful")
css = formatter.get_style_defs(".highlight").replace("#fff0f0", "#ffffff")
html = highlight(source_code, PythonLexer(), formatter)
return f"<style>{css}</style>{html}"
You can set a custom title for the rendered code block.
To render Python source code, annotate the string output of your task with an instance of SourceCodeRenderer:
from flytekit import task, workflow
from typing import Annotated
from flyte.types._renderer import SourceCodeRenderer
@task
def get_task_source_code() -> Annotated[str, SourceCodeRenderer(title="My Task Definition")]:
# In a real scenario, you might read this from a file or inspect a function object
source_code = '''
def my_example_task(a: int, b: int) -> int:
"""
This is an example task.
"""
result = a + b
return result
'''
return source_code
@workflow
def source_code_workflow() -> Annotated[str, SourceCodeRenderer(title="My Task Definition")]:
return get_task_source_code()
Considerations
The flyte.types._renderer package is primarily intended for internal use within the flyte-sdk. While you can directly import and use these renderers, be aware that their internal structure or location might change in future versions.
The SourceCodeRenderer dynamically imports the pygments library within its to_html method. This means that pygments is a runtime dependency for this specific renderer. If you use SourceCodeRenderer, ensure pygments is available in your execution environment.
The SourceCodeRenderer also applies a specific CSS modification, replacing the color "#fff0f0" with "#ffffff" in the generated stylesheet. This is an internal detail that might affect how the highlighted code appears if you have conflicting CSS rules.