From 7de6e3354cb052bc93a088ad0f3c0def2f1cd4d8 Mon Sep 17 00:00:00 2001 From: Hussain Sultan Date: Sun, 3 May 2026 07:56:49 -0400 Subject: [PATCH] fix: roundtrip model-level description through to_tagged/from_tagged SemanticTableOp.description was dropped during to_tagged/from_tagged because the extractor only emitted `name` and the reconstructor only forwarded `name=`. Dimension and Measure descriptions already roundtripped; this restores symmetry for the model-level field. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../serialization/extract.py | 2 ++ .../serialization/reconstruct.py | 1 + .../tests/test_xorq_convert.py | 24 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/boring_semantic_layer/serialization/extract.py b/src/boring_semantic_layer/serialization/extract.py index 8d547dda..52c9f493 100644 --- a/src/boring_semantic_layer/serialization/extract.py +++ b/src/boring_semantic_layer/serialization/extract.py @@ -101,6 +101,8 @@ def _extract_semantic_table(op, context: BSLSerializationContext) -> dict[str, A metadata["calc_measures"] = calc_data if op.name: metadata["name"] = op.name + if op.description: + metadata["description"] = op.description return metadata diff --git a/src/boring_semantic_layer/serialization/reconstruct.py b/src/boring_semantic_layer/serialization/reconstruct.py index 782e5001..8e0e271b 100644 --- a/src/boring_semantic_layer/serialization/reconstruct.py +++ b/src/boring_semantic_layer/serialization/reconstruct.py @@ -141,6 +141,7 @@ def _reconstruct_table(): measures=measures, calc_measures=calc_measures, name=metadata.get("name"), + description=metadata.get("description"), ) diff --git a/src/boring_semantic_layer/tests/test_xorq_convert.py b/src/boring_semantic_layer/tests/test_xorq_convert.py index 642175f0..0917e2ec 100644 --- a/src/boring_semantic_layer/tests/test_xorq_convert.py +++ b/src/boring_semantic_layer/tests/test_xorq_convert.py @@ -167,6 +167,30 @@ def test_to_xorq_returns_xorq_expr(): assert hasattr(tagged_expr, "op") +@pytest.mark.skipif(not xorq, reason="xorq not available") +def test_descriptions_roundtrip_to_tagged_from_tagged(): + """Model, dimension, and measure descriptions survive to_tagged → from_tagged.""" + import ibis + + from boring_semantic_layer import SemanticModel + from boring_semantic_layer.ops import Dimension, Measure + + table = ibis.memtable({"a": [1, 2, 3], "b": [4, 5, 6]}) + model = SemanticModel( + table=table, + dimensions={"a": Dimension(expr=lambda t: t.a, description="The A dim")}, + measures={"sum_b": Measure(expr=lambda t: t.b.sum(), description="Sum of B")}, + name="m", + description="Top-level model description", + ) + + restored = from_tagged(to_tagged(model)) + + assert restored.description == "Top-level model description" + assert restored.get_dimensions()["a"].description == "The A dim" + assert restored.get_measures()["sum_b"].description == "Sum of B" + + @pytest.mark.skipif(not xorq, reason="xorq not available") def test_from_xorq_returns_bsl_expr(): from xorq.api import memtable