######################################################################
# Steps to generate YAML configuration from Python dashboard:
# 1. Insert your dashboard configuration in block below.
# 2. Click "Click me" and follow instructions there.
######################################################################
import vizro.actions as va
import vizro.models as vm
import vizro.plotly.express as px
from vizro import Vizro
from vizro.tables import dash_ag_grid
tips = px.data.tips()
page = vm.Page(
title="Cross-filter from table to graph",
components=[
vm.AgGrid(
title="Click on a row to use that row's sex to filter graph",
figure=dash_ag_grid(tips),
actions=va.set_control(control="sex_filter", value="sex"),
),
vm.Graph(id="tips_graph", figure=px.histogram(tips, x="tip")),
],
controls=[vm.Filter(id="sex_filter", column="sex", targets=["tips_graph"])],
)
dashboard = vm.Dashboard(pages=[page])
# DON'T PUT Vizro().build(dashboard).run() here or the app won't work.
# The last line above should just define dashboardvariable.
######################################################################
import pyaml
import json
import vizro.tables as vt
import vizro.tables as vf
import vizro.plotly.express as px
# All this bit written by ChatGPT but seems to work well...
def serialize(obj):
if isinstance(obj, vm.types.CapturedCallable):
# return a dict representation
name = obj._function.__name__
if not hasattr(px, name) and not hasattr(vt, name) and not hasattr(vf, name):
# Hack that assumes captured callable is written in the app.py file if it's not one of the
# built in functions.
name = f"__main__.{name}"
arguments = dict(obj._arguments)
if "data_frame" in arguments:
arguments["data_frame"] = "<REPLACE ME>"
return {"_target_": name, **arguments}
raise TypeError(f"Type {type(obj)} not serializable")
def insert_type(base, override):
if isinstance(base, dict) and isinstance(override, dict):
# First, recurse into all keys in base
result = {}
for k, v in base.items():
result[k] = insert_type(v, override.get(k, {}))
# Then, insert "type" from override if present at this level
if "type" in override:
result["type"] = override["type"]
return result
elif isinstance(base, list) and isinstance(override, list):
# recurse element-wise for lists (assumes matching lengths)
return [insert_type(b, o) for b, o in zip(base, override)]
else:
return base # leave scalars / non-dict / non-list as-is
def dashboard_to_yaml(dashboard):
# We want the dumped version to exclude unset fields but include `type`. There doesn't seem to be an easy way to do this
# with pydantic (maybe possible using Field-level include though, not sure?). So we just manually generate both and then
# take the type key out of the second one and insert into the first.
# UPDATE: in recent pydantic there's exclude_if as a field which would probably do this for us more cleanly.
dict1 = dashboard.model_dump(exclude_unset=True)
dict2 = dashboard.model_dump()
result = insert_type(dict1, dict2)
# If we built this into Vizro then this serialization should be built into CapturedCallable itself rather than needing to
# use it in json.dumps.
json_str = json.dumps(result, default=serialize)
# Overall we do dashboard model -> dict -> json string -> dict -> yaml string.
# If we built serialization into model_dump itself then this could just be done as dashboard model -> dict -> yaml string.
# There are other ways to directly do dashboard model -> yaml string (e.g. pydantic-yaml, yaml.add_representer) but probably not worth
# pursuing - we should instead keep JSON/dict as our "source of truth" rather than using specific techniques.
# Format using pretty yaml seems more similar to our preferred using prettier.
return pyaml.dump(json.loads(json_str), vspacing=dict(split_lines=1000))
requirements = """vizro
pyyaml
"""
app_py = """######################################################################
# Steps to check your YAML configuration from Python dashboard:
# 1. In dashboard.yaml, replace <REPLACE ME> with references to data sources.
# 2. In app.py, make sure data sources are registered in data manager.
# 3. In app.py, define any custom charts/figures/tables/actions.
# 4. Make sure dashboard works as expected.
######################################################################
from pathlib import Path
import vizro.plotly.express as px
import yaml
from vizro import Vizro
from vizro.managers import data_manager
from vizro.models import Dashboard
from vizro.models.types import capture
data_manager["gapminder"] = px.data.gapminder()
data_manager["tips"] = px.data.tips()
data_manager["iris"] = px.data.iris()
dashboard = yaml.safe_load(Path("dashboard.yaml").read_text(encoding="utf-8"))
dashboard = Dashboard(**dashboard)
Vizro().build(dashboard).run()
"""
import base64
import gzip
import io
import json
from urllib.parse import quote, urlencode
PYCAFE_URL = "https://py.cafe"
def create_pycafe_url(dashboard_yaml: str) -> str:
"""Create a PyCafe URL for a given Python code."""
# Create JSON object for py.cafe
json_object = {
"code": app_py,
"requirements": requirements,
"files": [{"name": "dashboard.yaml", "content": dashboard_yaml}],
}
# Convert to compressed base64 URL
json_text = json.dumps(json_object)
compressed_json_text = gzip.compress(json_text.encode("utf8"))
base64_text = base64.b64encode(compressed_json_text).decode("utf8")
query = urlencode({"c": base64_text}, quote_via=quote)
pycafe_url = f"{PYCAFE_URL}/snippet/vizro/v1?{query}"
return pycafe_url
import vizro.models as vm
from vizro import Vizro
Vizro._reset()
page = vm.Page(
title="YAML dashboard",
components=[
vm.Card(
href=create_pycafe_url(dashboard_to_yaml(dashboard)),
text="Click me to generate and check your YAML configuration")
],
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()