Skip to content

feat(deps): optional xorq#278

Merged
hachej merged 23 commits into
mainfrom
hussain/optional-xorq
Jun 30, 2026
Merged

feat(deps): optional xorq#278
hachej merged 23 commits into
mainfrom
hussain/optional-xorq

Conversation

@hussainsultan

Copy link
Copy Markdown
Collaborator

No description provided.

deepyaman and others added 19 commits June 26, 2026 09:34
xorq is removed from core dependencies and moved to an optional
`[xorq]` extra. `pip install boring-semantic-layer` now installs and
works without xorq; core querying, YAML round-trips, and named
profiles all function against plain ibis backends.

Key changes:
- `_xorq.py`: dual-source shim — tries xorq, falls back to plain
  ibis equivalents; exposes `HAS_XORQ` flag; provides pure-Python
  fallbacks for `to_node`, `walk_nodes`, `replace_nodes`, `from_ibis`
  (identity), and a `map_ibis` stub
- `profile.py`: gates XorqProfile path on `HAS_XORQ`; skips xorq_dir
  discovery when xorq absent
- `serialization/__init__.py`: guards `from_tagged` with the same
  `try_import_xorq()` + ImportError pattern already used in `to_tagged`
- `serialization/tag_handler.py`: guards module-level TagHandler usage
  with `HAS_XORQ`; `bsl_tag_handler = None` when xorq absent
- `pyproject.toml`: xorq moved to `[xorq]` optional extra; `dev` gains
  `xorq`; new `test-core` extra for xorq-free CI
- CI: adds `test-no-xorq` job that installs without xorq and asserts
  the suite passes (xorq tests skip, not error)
- Tests: `importorskip("xorq")` added to 5 files with top-level xorq
  imports; fixed xorq-availability detection in two roundtrip test
  files; `test_xorq_rebuild.py` importorskip moved before tag_handler
  import; `test_dependency_groups.py` updated for new extra structure

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Gate deploy-docs and release-pypi on test-no-xorq so a
  xorq-free regression cannot slip through to a PyPI release
- Align examples extra floor with the new xorq extra (>=0.3.25)
  and drop the redundant bare "xorq" pin
- Remove explicit pyarrow from test-core; it arrives transitively
  via ibis-framework[duckdb] and BSL has no direct pyarrow import

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch from `uv pip install --system ".[test-core,mcp]"` to
  `uv sync --all-extras --no-extra xorq --no-extra examples --no-extra dev`
  so the no-xorq environment mirrors the full dev install, minus the
  three extras that directly depend on xorq as a package.
- Use `uv run python` / `uv run pytest` consistently (no bare executables).
- Drop the hardcoded test path and `-x -q` flags; let pytest discover
  naturally (same as `make test`) and just ignore the integration suite
  which requires external services not set up in this job.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The rolling-window regression test used xorq.api.window, causing a
module-level import of xorq and a collection error in the test-no-xorq
CI job. ibis.window accepts the same rows=/order_by= arguments, so
there is no reason to depend on xorq here.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Optional extras (xorq, mcp, agent, viz-*) are now installed via
--all-extras in CI rather than a boring-semantic-layer[...] bundle
in the dev extra, keeping dev as pure developer tooling.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Use --no-extra xorq --no-extra examples so the test-no-xorq job
validates core BSL without those optional dependencies.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add pytest.importorskip("xorq") at module level so these test files
are skipped cleanly in environments without the xorq extra.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
xorq.vendor.ibis is no longer available; use the standard ibis import
directly in test_date_filter_fix and demo_bsl_v2.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- test_dependency_groups: assert dev has tooling (not a self-referential
  bundle) and that all optional extras exist as top-level keys
- test_measure_reference_styles: skip serialization roundtrip test when
  xorq is not installed

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
os.path.expandvars silently leaves undefined ${VAR} references in place, so
without xorq the plain-ibis connection path turned `database: ${MISSING}` into
a DuckDB file literally named "${MISSING}" instead of failing. Add
_expand_env_vars(), which raises ProfileError on any undefined variable
(matching xorq's strict behavior). Coupled to this PR: the bug only manifests
on the new no-xorq _connect_plain_ibis() path.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Two code paths behaved differently when xorq was absent, because the shim's
plain-ibis fallbacks don't replicate xorq's runtime semantics:

- _rebind_join_backends / _rebind_to_canonical_backend: the try/except
  ImportError guard never fires now that the shim always provides
  walk_nodes/relations, so the plain-ibis path rebuilt an equal-but-distinct
  expression for no benefit. Without xorq there is only one backend, so return
  the inputs unchanged.

- The dimension-only shortcut engaged or not depending on whether xorq was
  installed: with xorq, from_ibis(proxy) pollutes the tracking proxy so static
  column extraction fails (shortcut disabled); without xorq it succeeds
  (shortcut enabled), yielding a different result set for the same query.
  Dict/string filters resolve through the backend (deferred) and aren't
  statically introspectable, so they now explicitly disable the shortcut in
  both environments (tagged __bsl_deferred_resolution__ in query.py).

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
BSL's to_untagged() returns xorq-vendored ibis when xorq is installed, and
those expressions reject a plain ibis.window (LegacyWindowBuilder) and can't be
compiled by plain ibis.to_sql. Build windows / call to_sql via the established
shim idiom `from boring_semantic_layer._xorq import ibis as xibis` (as in
expr.py), which is plain ibis without xorq and vendored ibis with it — so the
no-xorq job exercises plain ibis and the full job exercises xorq. xorq.window
is not functionally different; it's the same ibis code under xorq's namespace,
needed only for type identity.

Also parametrize the two SQL-generation tests and the two filter-value
conversion tests, and assert flavor-agnostically via result.type().is_timestamp().

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
…fail

Convert the three remaining xorq-only test files from the manual
try/except + @skipif pattern to module-level pytest.importorskip("xorq"),
matching the eleven files that already use it (e.g. test_calc_analyzer.py).
test_dependency_groups.py is intentionally left as-is since it tests the
dependency-group mechanism itself.

Also clarify the projection-pushdown xfail comments: the marker is unconditional
(pushdown is disabled for all backends, not just xorq); the few xpasses under
plain ibis are incidental SQL matches, not the feature working.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
This PR introduced a second, near-duplicate test job (test-no-xorq). Collapse
both into one matrix over the install extras (full / no-xorq), each running the
pytest suite (the no-xorq leg ignores integration, as before). Extract the
xorq-only build steps (examples + docs-build + skills-check) into a dedicated
build job, since the no-xorq leg can't run them. Update deploy-docs and
release-pypi gates to needs: [test, build]. Coverage is preserved — the steps
move, nothing is dropped.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Relock from main's pins so the lockfile diff reflects only the extras
restructure (no incidental version bumps), and resync requirements-dev.txt
(which was already stale vs main's lock — xorq 0.3.5 listed, 0.3.25 locked).

Add a comment on the test-core extra clarifying it is the no-xorq CI leg's
baseline test environment, pulled in via 'uv sync --all-extras' to provide a
duckdb backend (and pyarrow) without xorq or the examples extra.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
The no-xorq walk_nodes fallback re-implemented a depth-first traversal that
already exists, nearly verbatim, in graph_utils.walk_nodes — and was never
reached without xorq (its only callers are HAS_XORQ-short-circuited or
xorq-gated). Replace it with a raising stub. replace_nodes stays a real
delegation to ibis Node.replace since graph_utils.replace_nodes reaches it.

Also trim three verbose backend-rebinding comments: the two HAS_XORQ
short-circuits duplicated each other (now one points to the other), and the
deferred-filter comment dropped the stale tracking-proxy aside.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
The integration tests load BSL query modules that import xorq directly, so
the whole directory needs xorq. Move that gate into the test tree
(collect_ignore_glob in the integration conftest) instead of a --ignore CLI
flag, so both CI legs run a plain `pytest` and the condition lives with the
tests that require it.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Use the dominant 'xorq-vendored ibis' compound-adjective form (lowercase, per
codebase convention) in the projection-pushdown test comments. Reword the
no-xorq walk_nodes stub comment to explain why it exists (symbol-surface
symmetry for the gated function-local imports) rather than just that it never
runs.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
xorq 0.3.31 drops cityhash, dask, envyaml, geoarrow-types, git-annex,
and pythran from its dependency tree and adds xorq-dasher, which slims
the lockfile considerably. Update the optional-dependency error-message
tests to assert the install extra (boring-semantic-layer[xorq]) and add
a subprocess test that exercises the tagged APIs with xorq blocked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hachej hachej marked this pull request as draft June 26, 2026 13:39
@hussainsultan hussainsultan force-pushed the hussain/optional-xorq branch from e3bb0ac to e4cbff5 Compare June 26, 2026 13:45
@hussainsultan hussainsultan marked this pull request as ready for review June 29, 2026 11:38
* test: prefer public ibis APIs in examples

* fix: resolve YAML calculated measures against measures first

---------

Co-authored-by: boringdata <boringdata@users.noreply.github.com>
Co-authored-by: boringdata <boringdata@users.noreply.github.com>
@hachej hachej merged commit bbf9c63 into main Jun 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants