Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/basic_flights_xorq.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


def main():
flights_tbl = xo.read_parquet(f"{BASE_URL}/flights.parquet")
flights_tbl = xo.deferred_read_parquet(f"{BASE_URL}/flights.parquet")

flights = (
to_semantic_table(flights_tbl, name="flights")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies = [
"pyyaml>=6.0",
"returns>=0.26.0",
"toolz>=1.0.0",
"xorq>=0.3.19",
"xorq>=0.3.25",
]
urls = { Homepage = "https://github.com/boringdata/boring-semantic-layer/tree/main" }
license = "MIT"
Expand Down
9 changes: 7 additions & 2 deletions src/boring_semantic_layer/serialization/tag_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,14 @@ def reemit(tag_node, rebuild_subexpr):

Re-stamping uses ``hashing_tag`` (not ``tag``) so the rebuilt expression
keeps the same hash-contribution guarantee as ``to_tagged`` — see #263.

Precondition: ``tag_node`` is a BSL-tagged xorq ``HashingTag`` op (BSL
only ever emits ``HashingTag`` — see ``to_tagged`` and the re-stamp
below). xorq's dispatch only routes here when
``tag_node.metadata["tag"]`` resolves to this handler, and xorq's op
definition declares ``parent: Relation`` (non-null) — so by
construction ``tag_node.parent`` is always a valid relation.
"""
if tag_node.parent is None:
raise ValueError("tag_node has no parent; cannot rebuild a root tag node")
new_source = rebuild_subexpr(tag_node.parent.to_expr())
meta = dict(tag_node.metadata)
tag_name = meta.pop("tag")
Expand Down
2 changes: 1 addition & 1 deletion src/boring_semantic_layer/tests/test_xorq_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def test_read_write_operations(self):
df.to_parquet(temp_path)

# Read back with xorq
read_back = xo.read_parquet(temp_path)
read_back = xo.deferred_read_parquet(temp_path)
df_back = xo.execute(read_back)

assert len(df_back) == 3
Expand Down
38 changes: 18 additions & 20 deletions src/boring_semantic_layer/tests/test_xorq_rebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ def _tag_node(tagged_expr):
return tagged_expr.op()


@pytest.fixture(autouse=True)
def _git_identity(monkeypatch):
# xorq>=0.3.24's Replayer rewrites no-op commits via ``git rebase --onto``,
# which fails on CI runners that have no global git user.email/user.name.
# Set GIT_*_NAME/EMAIL env vars (they take precedence over git config).
for var in ("GIT_AUTHOR_NAME", "GIT_COMMITTER_NAME"):
monkeypatch.setenv(var, "bsl-test")
for var in ("GIT_AUTHOR_EMAIL", "GIT_COMMITTER_EMAIL"):
monkeypatch.setenv(var, "bsl-test@example.invalid")


# ---------------------------------------------------------------------------
# Phase 2: reemit registration
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -112,7 +123,10 @@ def test_reemit_query_chain_with_source_transform(simple_model):
original_meta = dict(_tag_node(tagged).metadata)

def add_column(expr):
return expr.mutate(extra=ibis.literal(1))
# ``expr`` is in xorq.vendor.ibis space; pass a raw scalar so mutate
# infers the literal in the same flavor (xorq>=0.3.24 rejects
# cross-package ``ibis.literal`` here).
return expr.mutate(extra=1)

rebuilt = reemit(_tag_node(tagged), rebuild_subexpr=add_column)
rebuilt_meta = dict(_tag_node(rebuilt).metadata)
Expand Down Expand Up @@ -140,7 +154,9 @@ def test_get_rebuild_dispatch_invokes_handler_reemit(simple_model):

tagged = to_tagged(simple_model)
dispatch = get_rebuild_dispatch(_tag_node(tagged))
result = dispatch(lambda e: e)
# xorq>=0.3.20 widened the dispatch callable signature to
# (rebuild_subexpr, remap, to_catalog).
result = dispatch(lambda e: e, None, None)
assert result is not None
rebuilt_meta = dict(_tag_node(result).metadata)
original_meta = dict(_tag_node(tagged).metadata)
Expand Down Expand Up @@ -293,21 +309,3 @@ def test_catalog_rebuild_base_model_executes(catalog_with_base_model, tmpdir):
entry = target.get_catalog_entry("city-stats", maybe_alias=True)
result = entry.lazy_expr.execute()
assert len(result) == 2


# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------


@requires_reemit
def test_reemit_raises_on_missing_parent(simple_model):
tagged = to_tagged(simple_model)
node = _tag_node(tagged)
original_parent = node.parent
try:
node.parent = None
with pytest.raises(ValueError, match="no parent"):
reemit(node, rebuild_subexpr=lambda e: e)
finally:
node.parent = original_parent
31 changes: 14 additions & 17 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading