Cards - Visualizing results
Metaflow Cards allows you to produce human readable reports in workflows and observe their behavior live. Use the following APIs to enable, customize, and access cards:
- Enable cards by adding the
@card
decorator in any step. - Specify card contents with card components.
- Populate card components with the
current
object. - Retrieve cards with the
get_cards
method or on the command line with thecard
commands. - Create fully custom, shareable cards with custom
MetaflowCard
classes.
Retrieving cards
To retrieve a card after a run has finished, use the get_cards
function e.g. in a notebook or the card get
command on the CLI.
Since a task can contain multiple cards get_cards
returns a container object, CardContainer
, which holds Card
objects corresponding to individual cards. Notably both CardContainer
and Card
objects contain a function that allow them to visualize cards in the notebook output cell automatically, so a single get_cards
call can be used to show all cards of a step in a notebook.
from metaflow.cards import get_cards
Get cards related to a Task
.
Note that get_cards
resolves the cards contained by the task, but it doesn't actually
retrieve them from the datastore. Actual card contents are retrieved lazily either when
the card is rendered in a notebook to when you call Card.get
. This means that
get_cards
is a fast call even when individual cards contain a lot of data.
task: str or Task
A Task
object or pathspec {flow_name}/{run_id}/{step_name}/{task_id}
that
uniquely identifies a task.
id: str, optional
The ID of card to retrieve if multiple cards are present.
type: str, optional
The type of card to retrieve if multiple cards are present.
follow_resumed: bool, default: True
If the task has been resumed, then setting this flag will resolve the card for the origin task.
CardContainer
A list-like object that holds Card
objects.
CardContainer
is an immutable list-like object, returned by get_cards
,
which contains individual Card
s.
Notably, CardContainer
contains a special
_repr_html_
function which renders cards automatically in an output
cell of a notebook.
The following operations are supported:
cards = get_cards(MyTask)
# retrieve by index
first_card = cards[0]
# check length
if len(cards) > 1:
print('many cards present!')
# iteration
list_of_cards = list(cards)
Card
represents an individual Metaflow Card, a single HTML file, produced by
the card @card
decorator. Card
s are contained by CardContainer
, returned by
get_cards
.
Note that the contents of the card, an HTML file, is retrieved lazily when you call
Card.get
for the first time or when the card is rendered in a notebook.
Retrieves the HTML contents of the card from the Metaflow datastore.
str
HTML contents of the card.
Opens the card in a local web browser.
This call uses Python's built-in webbrowser
module to open the card.
Card components
You can customize the contents of a card easily using card components, a set of visual elements included in Metaflow which are documented below. See Easy Custom Reports with Card Components for instructions.
The components are added to cards in @step
methods (or functions called from steps), using the current.card
object.
Markdown
from metaflow.cards import Markdown
A block of text formatted in Markdown.
Example:
current.card.append(
Markdown("# This is a header appended from `@step` code")
)
text: str
Text formatted in Markdown.
Update the markdown content of this component. Use this to refresh content of a dynamic card.
text: str
Text formatted in Markdown.
Image
from metaflow.cards import Image
An image.
Images can be created directly from PNG/JPG/GIF
bytes,
PIL.Image`s,
or Matplotlib figures. Note that the image data is embedded in the card,
so no external files are required to show the image.
Example: Create an Image
from bytes:
current.card.append(
Image(
requests.get("https://www.gif-vif.com/hacker-cat.gif").content,
"Image From Bytes"
)
)
Example: Create an Image
from a Matplotlib figure
import pandas as pd
import numpy as np
current.card.append(
Image.from_matplotlib(
pandas.DataFrame(
np.random.randint(0, 100, size=(15, 4)),
columns=list("ABCD"),
).plot()
)
)
Example: Create an Image
from a PIL Image
from PIL import Image as PILImage
current.card.append(
Image.from_pil_image(
PILImage.fromarray(np.random.randn(1024, 768), "RGB"),
"From PIL Image"
)
)
src: bytes
The image data in bytes
.
label: str
Optional label for the image.
component_id
Create an Image
from a Matplotlib plot.
plot: matplotlib.figure.Figure or matplotlib.axes.Axes or matplotlib.axes._subplots.AxesSubplot
a PIL axes (plot) object.
label: str, optional
Optional label for the image.
Create an Image
from a PIL image.
pilimage: PIL.Image
a PIL image object.
label: str, optional
Optional label for the image.
Artifact
from metaflow.cards import Artifact
A pretty-printed version of any Python object.
Large objects are truncated using Python's built-in reprlib
.
Example:
from datetime import datetime
current.card.append(Artifact({'now': datetime.utcnow()}))
artifact: object
Any Python object.
name: str, optional
Optional label for the object.
compressed: bool, default: True
Use a truncated representation.
Table
from metaflow.cards import Table
A table.
The contents of the table can be text or numerical data, a Pandas dataframe,
or other card components: Artifact
, Image
, Markdown
objects.
Example: Text and artifacts
from metaflow.cards import Table, Artifact
current.card.append(
Table([
['first row', Artifact({'a': 2})],
['second row', Artifact(3)]
])
)
Example: Table from a Pandas dataframe
from metaflow.cards import Table
import pandas as pd
import numpy as np
current.card.append(
Table.from_dataframe(
pd.DataFrame(
np.random.randint(0, 100, size=(15, 4)),
columns=list("ABCD")
)
)
)
data: List[List[str or MetaflowCardComponent]], optional
List (rows) of lists (columns). Each item can be a string or a MetaflowCardComponent
.
headers: List[str], optional
Optional header row for the table.
component_id
Create a Table
based on a Pandas dataframe.
dataframe: Optional[pandas.DataFrame]
Pandas dataframe.
truncate: bool, default: True
Truncate large dataframe instead of showing all rows (default: True).
VegaChart
from metaflow.cards import VegaChart
Create a chart based on a Vega Lite specification.
spec: dict
Vega Lite chart specification as a dictionary.
show_controls: bool, optional
Show Vega controls for downloading the chart image etc.
Create a chart based on an Altair Chart object.
chart: altair.Chart
An Altair Chart object.
Update the chart specification and data. Use this to refresh content of a dynamic card.
spec: dict
The updated chart Vega Lite specifcation
ProgressBar
from metaflow.cards import ProgressBar
A Progress bar for tracking progress of any task.
Example:
progress_bar = ProgressBar(
max=100,
label="Progress Bar",
value=0,
unit="%",
metadata="0.1 items/s"
)
current.card.append(
progress_bar
)
for i in range(100):
progress_bar.update(i, metadata="%s items/s" % i)
max: int
The maximum value of the progress bar.
label: str, optional
Optional label for the progress bar.
value: int, optional
Optional initial value of the progress bar.
unit: str, optional
Optional unit for the progress bar.
metadata: str, optional
Optional additional information to show on the progress bar.
component_id
Update the progress bar status. Use this to refresh content of a dynamic card.
new_value: int
Updated value of the progress bar
metadata: str
Updated additional information
Defining a custom card
You can define custom cards types (T
in @card(type=T)
) by creating a Python package that includes a class that derives from MetaflowCard
, documented below. Read more in Advanced, Shareable Cards with Card Templates.
Find detailed instructions, a starter template, and an example of a simple static custom card and an example of a dynamic card.
from metaflow.cards import MetaflowCard
Metaflow cards derive from this base class.
Subclasses of this class are called card types. The desired card
type T
is defined in the @card
decorator as @card(type=T)
.
After a task with @card(type=T, options=S)
finishes executing, Metaflow instantiates
a subclass C
of MetaflowCard
that has its type
attribute set to T
, i.e. C.type=T
.
The constructor is given the options dictionary S
that contains arbitrary
JSON-encodable data that is passed to the instance, parametrizing the card. The subclass
may override the constructor to capture and process the options.
The subclass needs to implement a render(task)
method that produces the card
contents in HTML, given the finished task that is represented by a Task
object.
options: Dict
JSON-encodable dictionary containing user-definable options for the class.
type: str
Card type string. Note that this should be a globally unique name, similar to a Python package name, to avoid name clashes between different custom cards.
Produce custom card contents in HTML. The HTML returned by this method represents the final card contents.
Subclasses override this method to customize the card contents.
Note that dynamic cards can also access an attribute
self.runtime_data
inside this method to retrieve the latest user-defined
runtime data, i.e. the latest data
object passed in to render_runtime
or refresh
.
task: Task
A Task
object that allows you to access data from the finished task and tasks
preceding it.
str
Card contents as an HTML string.
Produce intermediate custom card contents in HTML. The HTML returned by this method represents a runtime snapshot of a dynamic card.
Subclasses may override this method to customize the card contents while a task is executing, making the card dynamic.
Note that in contrast to render
, the task
object
passed to this method does not allow you to access artifacts from the
currently running task, since they are not yet available. Instead, you
can use the data
dictionary to retrieve runtime data passed
in the user-facing refresh
method.
task: Task
A Task
object that allows you to access data from tasks
preceding it the currently running task.
data: dict
A dictionary containing user-defined runtime data.
str
Card contents as an HTML string.
Produce a runtime data JSON that is passed to the card Javascript on the client-side.
Subclasses may override this method to customize the card contents while a task is executing, making the card dynamic.
A JSON-encoded dictionary returned by this method is passed to the
metaflow_card_update
Javascript method on the client-side card to
update its contents live. You can perform arbitrary preprocessing
and refinement of the data passed to Javascript in this method. Or,
you may simply return the user-defined data
as-is.
Note that in contrast to render
, the task
object
passed to this method does not allow you to access artifacts from the
currently running task, since they are not yet available. Instead, you
can use the data
dictionary to retrieve runtime data passed
in the user-facing refresh
method.
task: Task
A Task
object that allows you to access data from tasks
preceding it the currently running task.
data: dict
A dictionary containing user-defined runtime data.
dict
Dictionary passed to the card on the client-side