From 47f84d5f2b15dfea21445fa8f78dc186668cc967 Mon Sep 17 00:00:00 2001 From: Myles Scolnick Date: Tue, 28 Oct 2025 15:09:17 -0400 Subject: [PATCH 1/2] fix: fallback for chart.to_dict() and don't mutate charts This change make it so `mo.ui.altair_chart` no longer mutates the given chart. This also provides a fallback for when our changes to the width affect the change in such a way that `chart.to_dict()` starts to fail. --- marimo/_plugins/ui/_impl/altair_chart.py | 12 ++++- marimo/_smoke_tests/altair_examples/upset.py | 50 ++++++++++++++++++++ tests/_plugins/ui/_impl/test_altair_chart.py | 17 +++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 marimo/_smoke_tests/altair_examples/upset.py diff --git a/marimo/_plugins/ui/_impl/altair_chart.py b/marimo/_plugins/ui/_impl/altair_chart.py index 347f6e40b2b..15e233d5158 100644 --- a/marimo/_plugins/ui/_impl/altair_chart.py +++ b/marimo/_plugins/ui/_impl/altair_chart.py @@ -327,6 +327,9 @@ def __init__( register_transformers() + # Make a copy + original_chart = chart + chart = chart.copy() self._chart = chart if not isinstance(chart, (alt.TopLevelMixin)): @@ -345,7 +348,14 @@ def __init__( if chart.autosize is alt.Undefined: chart.autosize = "fit-x" - vega_spec = _parse_spec(chart) + try: + vega_spec = _parse_spec(chart) + except Exception: + # Sometimes the changes to width and autosize (above) can cause `.to_dict()` to throw an error + # similarly to the issue described in https://github.com/marimo-team/marimo/issues/6244 + # so we fallback to the original chart. + LOGGER.info("Failed to parse spec, using original chart") + vega_spec = _parse_spec(original_chart) if label: vega_spec["title"] = label diff --git a/marimo/_smoke_tests/altair_examples/upset.py b/marimo/_smoke_tests/altair_examples/upset.py new file mode 100644 index 00000000000..309832040f7 --- /dev/null +++ b/marimo/_smoke_tests/altair_examples/upset.py @@ -0,0 +1,50 @@ +# /// script +# requires-python = ">=3.13" +# dependencies = [ +# "altair-upset==0.4.0", +# "pandas==2.3.2", +# "pyarrow==22.0.0", +# ] +# /// + +import marimo + +__generated_with = "0.17.2" +app = marimo.App(width="medium", sql_output="polars") + + +@app.cell +def _(): + import marimo as mo + import pandas as pd + import pyarrow + from altair_upset import UpSetAltair + return UpSetAltair, mo, pd + + +@app.cell +def _(pd): + df = pd.DataFrame({"a": [1, 1, 1, 1, 1, 0], "b": [1, 1, 1, 0, 0, 0]}) + return (df,) + + +@app.cell +def _(UpSetAltair, df): + chart = UpSetAltair(df, sorted(df.columns)).chart + return (chart,) + + +@app.cell +def _(chart): + chart + return + + +@app.cell +def _(chart, mo): + mo.ui.altair_chart(chart.copy()) + return + + +if __name__ == "__main__": + app.run() diff --git a/tests/_plugins/ui/_impl/test_altair_chart.py b/tests/_plugins/ui/_impl/test_altair_chart.py index e20759426a5..6d70f337287 100644 --- a/tests/_plugins/ui/_impl/test_altair_chart.py +++ b/tests/_plugins/ui/_impl/test_altair_chart.py @@ -628,6 +628,23 @@ def test_does_not_modify_original() -> None: assert combined1 == combined2._chart +@pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed") +def test_creating_altair_chart_does_not_mutate_original() -> None: + import altair as alt + + data = {"values": [1, 2, 3]} + original_chart = alt.Chart(data).mark_point().encode(x="values:Q") + + # Store the original spec + original_spec = original_chart.to_dict() + + # Create marimo altair_chart wrapper + _ = altair_chart(original_chart) + + # Verify the original chart hasn't been mutated + assert original_chart.to_dict() == original_spec + + @pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed") def test_get_dataframe() -> None: import altair as alt From 623fa5a770a6f5d4daebf18347b0027ee6c180de Mon Sep 17 00:00:00 2001 From: Myles Scolnick Date: Tue, 28 Oct 2025 21:26:10 -0400 Subject: [PATCH 2/2] fix test --- marimo/_plugins/ui/_impl/altair_chart.py | 2 +- tests/_plugins/ui/_impl/test_altair_chart.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/marimo/_plugins/ui/_impl/altair_chart.py b/marimo/_plugins/ui/_impl/altair_chart.py index 15e233d5158..7ca28c4fde7 100644 --- a/marimo/_plugins/ui/_impl/altair_chart.py +++ b/marimo/_plugins/ui/_impl/altair_chart.py @@ -330,7 +330,7 @@ def __init__( # Make a copy original_chart = chart chart = chart.copy() - self._chart = chart + self._chart = original_chart if not isinstance(chart, (alt.TopLevelMixin)): raise ValueError( diff --git a/tests/_plugins/ui/_impl/test_altair_chart.py b/tests/_plugins/ui/_impl/test_altair_chart.py index 6d70f337287..a6e5ba1e0ef 100644 --- a/tests/_plugins/ui/_impl/test_altair_chart.py +++ b/tests/_plugins/ui/_impl/test_altair_chart.py @@ -625,7 +625,7 @@ def test_does_not_modify_original() -> None: combined1 = alt1 | alt2 combined2 = altair_chart(alt1) | altair_chart(alt2) - assert combined1 == combined2._chart + assert combined1.to_dict() == combined2._chart.to_dict() @pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed")