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: Union[str, Task
]
A Task
object or pathspec {flow_name}/{run_id}/{step_name}/{task_id}
that
uniquely identifies a task.
id: str, optional, default None
The ID of card to retrieve if multiple cards are present.
type: str, optional, default None
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.
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
Update the chart.
spec: dict or altair.Chart
The updated chart spec or an altair Chart Object.
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
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.
ShowDoc(MetaflowCard.render)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
File ~/mambaforge/envs/docs/lib/python3.11/site-packages/IPython/core/formatters.py:344, in BaseFormatter.__call__(self, obj)
342 method = get_real_method(obj, self.print_method)
343 if method is not None:
--> 344 return method()
345 return None
346 else:
File ~/mambaforge/envs/docs/lib/python3.11/site-packages/nbdoc/showdoc.py:218, in ShowDoc._repr_html_(self)
216 def _repr_html_(self):
217 "This method controls what is displayed in Jupyter Notebooks."
--> 218 return f'<HTMLRemove>\n{self.nbhtml}\n</HTMLRemove>\n{self.jsx}'
File ~/mambaforge/envs/docs/lib/python3.11/site-packages/nbdoc/showdoc.py:253, in ShowDoc.jsx(self)
251 nm = f'<DocSection type="{self.typ}" name="{self.objnm}" module="{self.modnm}" show_import="{self.show_import}" heading_level="{self.hd_lvl}"{self._src_link_attr}>'
252 spoof = '...' if self.decorator else self.spoofstr
--> 253 sp = get_sig_section(self.obj, spoofstr=spoof)
254 return f'{nm}\n{sp}\n' + self.npdocs + '\n</DocSection>'
File ~/mambaforge/envs/docs/lib/python3.11/site-packages/nbdoc/showdoc.py:115, in get_sig_section(obj, spoofstr)
113 return ''
114 params = sig.parameters.items()
--> 115 jsx_params = [fmt_sig_param(p) for _, p in params]
116 else:
117 jsx_params = [f'<SigArg name="{spoofstr}" />']
File ~/mambaforge/envs/docs/lib/python3.11/site-packages/nbdoc/showdoc.py:115, in <listcomp>(.0)
113 return ''
114 params = sig.parameters.items()
--> 115 jsx_params = [fmt_sig_param(p) for _, p in params]
116 else:
117 jsx_params = [f'<SigArg name="{spoofstr}" />']
File ~/mambaforge/envs/docs/lib/python3.11/site-packages/nbdoc/showdoc.py:99, in fmt_sig_param(p)
96 prefix = f'<SigArg name="{name}" '
98 if p.annotation != inspect._empty:
---> 99 prefix += f'type="{p.annotation.__name__}" '
100 if p.default != inspect._empty:
101 prefix += f'default="{p.default}" '
AttributeError: 'str' object has no attribute '__name__'
<nbdoc.showdoc.ShowDoc at 0x11365a650>