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
4 changes: 4 additions & 0 deletions .github/workflows/ci-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ jobs:
- name: Run tests
run: uv run pytest

- name: Run no-xorq-compatible examples
if: matrix.name == 'no-xorq'
run: make examples-core

# Build examples, docs, and skills (require the full env incl. Node).
build:
name: Build (examples + docs + skills)
Expand Down
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ help:
@echo " make test IBIS_VERSION=all - Run tests with all ibis versions (9.5.0, 10.8.0, 11.0.0)"
@echo " make test IBIS_VERSION=10.8.0 - Run tests with specific ibis version"
@echo " make examples - Run all example scripts"
@echo " make examples-core - Run no-xorq-compatible examples"
@echo " make examples IBIS_VERSION=all - Run examples with all ibis versions"
@echo " make examples IBIS_VERSION=10.8.0 - Run examples with specific ibis version"
@echo " make docs-build - Build documentation"
Expand Down Expand Up @@ -105,6 +106,19 @@ else
@echo "✓ All examples passed!"
endif

examples-core:
@echo "Running no-xorq-compatible examples..."
@for file in \
scripts/demo_bsl_v2.py \
examples/quickstart.py \
examples/window_functions.py \
examples/bucketing_with_other.py \
examples/sessionized_data.py; do \
echo "Running $$file..."; \
uv run --with . "$$file" || exit 1; \
done
@echo "✓ No-xorq-compatible examples passed!"

# Build docs
docs-build:
@echo "Building documentation..."
Expand Down
10 changes: 7 additions & 3 deletions examples/bucketing_with_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import ibis
from ibis import _

# CI runs this example with and without xorq. xibis matches BSL's active ibis
# flavor in both modes; users who are not using xorq can simply use `import ibis`.
from boring_semantic_layer._xorq import ibis as xibis

from boring_semantic_layer import from_yaml


Expand All @@ -28,13 +32,13 @@ def main():
nest={"data": lambda t: t.group_by(["code", "elevation"])},
)
.mutate(
rank=lambda t: ibis.row_number().over(
ibis.window(order_by=ibis.desc(t.avg_elevation)),
rank=lambda t: xibis.row_number().over(
xibis.window(order_by=xibis.desc(t.avg_elevation)),
),
)
.mutate(
is_other=lambda t: t.rank > 4,
state_grouped=lambda t: ibis.ifelse(t.rank > 4, "OTHER", t.state),
state_grouped=lambda t: xibis.ifelse(t.rank > 4, "OTHER", t.state),
)
.group_by("state_grouped")
.aggregate(
Expand Down
6 changes: 5 additions & 1 deletion examples/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
import ibis
import pandas as pd

# CI runs this example with and without xorq. xibis matches BSL's active ibis
# flavor in both modes; users who are not using xorq can simply use `import ibis`.
from boring_semantic_layer._xorq import ibis as xibis

from boring_semantic_layer import to_semantic_table


Expand Down Expand Up @@ -111,7 +115,7 @@ def main():
.with_measures(sum_val=lambda t: t.value.sum())
)

rolling_window = ibis.window(order_by="date", rows=(1, 1))
rolling_window = xibis.window(order_by="date", rows=(1, 1))
expr4 = (
ts_st.group_by("date")
.aggregate("sum_val")
Expand Down
8 changes: 6 additions & 2 deletions examples/sessionized_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
import ibis
import pandas as pd

# CI runs this example with and without xorq. xibis matches BSL's active ibis
# flavor in both modes; users who are not using xorq can simply use `import ibis`.
from boring_semantic_layer._xorq import ibis as xibis

from boring_semantic_layer import from_yaml, to_untagged

# Show all columns in output
Expand All @@ -31,7 +35,7 @@ def main():

# Filter for carrier WN on 2002-03-03 and add flight_date column
filtered_flights = flights.filter(
lambda t: (t.carrier == "WN") & (t.dep_time.date() == ibis.date(2002, 3, 3)),
lambda t: (t.carrier == "WN") & (t.dep_time.date() == xibis.date(2002, 3, 3)),
).mutate(flight_date=lambda t: t.dep_time.date())

# Create sessions with nested flight legs
Expand All @@ -52,7 +56,7 @@ def main():
]),
},
)
.mutate(session_id=ibis.row_number().over(ibis.window()))
.mutate(session_id=xibis.row_number().over(xibis.window()))
.order_by("session_id")
)

Expand Down
16 changes: 10 additions & 6 deletions examples/window_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import ibis
from ibis import _

# CI runs this example with and without xorq. xibis matches BSL's active ibis
# flavor in both modes; users who are not using xorq can simply use `import ibis`.
from boring_semantic_layer._xorq import ibis as xibis

from boring_semantic_layer import to_semantic_table

BASE_URL = "https://pub-a45a6a332b4646f2a6f44775695c64df.r2.dev"
Expand Down Expand Up @@ -31,13 +35,13 @@ def main():
result = (
daily_stats.mutate(
rolling_avg=lambda t: t.flight_count.mean().over(
ibis.window(order_by=t.flight_date, preceding=6, following=0),
xibis.window(order_by=t.flight_date, preceding=6, following=0),
),
rank=lambda t: ibis.dense_rank().over(
ibis.window(order_by=ibis.desc(t.flight_count)),
rank=lambda t: xibis.dense_rank().over(
xibis.window(order_by=xibis.desc(t.flight_count)),
),
running_total=lambda t: t.flight_count.sum().over(
ibis.window(order_by=t.flight_date),
xibis.window(order_by=t.flight_date),
),
)
.order_by("flight_date")
Expand All @@ -64,8 +68,8 @@ def main():

result = (
carrier_stats.mutate(
total_flights=lambda t: t.flight_count.sum().over(ibis.window()),
percent_manual=lambda t: (t.flight_count / t.flight_count.sum().over(ibis.window()))
total_flights=lambda t: t.flight_count.sum().over(xibis.window()),
percent_manual=lambda t: (t.flight_count / t.flight_count.sum().over(xibis.window()))
* 100,
)
.order_by(_.percent_manual.desc())
Expand Down
6 changes: 5 additions & 1 deletion scripts/demo_bsl_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

from boring_semantic_layer import to_semantic_table

# CI runs this demo with and without xorq. xibis matches BSL's active ibis
# flavor in both modes; users who are not using xorq can simply use `import ibis`.
from boring_semantic_layer._xorq import ibis as xibis


def main():
# Setup in-memory DuckDB
Expand Down Expand Up @@ -116,7 +120,7 @@ def main():
.with_measures(sum_val=lambda t: t.value.sum())
)

rolling_window = ibis.window(order_by="date", preceding=1, following=1)
rolling_window = xibis.window(order_by="date", preceding=1, following=1)
expr4 = (
ts_st.group_by("date")
.aggregate("sum_val")
Expand Down
8 changes: 4 additions & 4 deletions src/boring_semantic_layer/chart/tests/test_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

from boring_semantic_layer import to_semantic_table

# BSL's to_untagged() returns xorq-vendored ibis when xorq is installed, and
# those expressions reject a plain ibis.window (LegacyWindowBuilder). Build the
# window from the matching flavor via the shim (plain ibis when xorq is absent).
# Match BSL's active ibis flavor: plain ibis without xorq, xorq-vendored ibis
# with xorq installed. Plain-ibis users can write `import ibis` directly.
from boring_semantic_layer._xorq import ibis as xibis


@pytest.fixture(scope="module")
Expand Down Expand Up @@ -390,7 +390,7 @@ def test_chart_with_dynamic_dimension_and_rolling_window(self, flights_model):
.order_by("flight_week")
.mutate(
rolling_avg=lambda t: t.flight_count.mean().over(
ibis.window(rows=(-2, 0), order_by="flight_week")
xibis.window(rows=(-2, 0), order_by="flight_week")
)
)
)
Expand Down
7 changes: 6 additions & 1 deletion src/boring_semantic_layer/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,12 @@ def _classify_measure(
if prefer_known is None:
prefer_known = getattr(scope, "_prefer_known", ())
prefer_known_set = frozenset(prefer_known)
expr_prefer_known = getattr(expr, "__bsl_prefer_known__", ())
try:
# Use object.__getattribute__ so ibis Deferred.__getattr__ does not
# synthesize a resolver for this private marker.
expr_prefer_known = object.__getattribute__(expr, "__bsl_prefer_known__")
except AttributeError:
expr_prefer_known = ()
if expr_prefer_known is True:
prefer_known_set = prefer_known_set | known_set
else:
Expand Down