Cards - Visualizing results
Metaflow Cards allows you to produce human readable reports in workflows. 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. - Retrive 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.
id
The ID of the card, if specified with @card(id=ID)
.
path
The path of the card in the datastore which uniquely identifies the card.
Returns
str Path to the card
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.
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.
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.
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).
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.
Find detailed instructions, a starter template, and an example of a simple custom card at https://github.com/outerbounds/metaflow-card-html.
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.