Skip to content

refactor: make xorq an optional (extra) dependency#271

Open
deepyaman wants to merge 18 commits into
boringdata:mainfrom
deepyaman:refactor/optional-xorq
Open

refactor: make xorq an optional (extra) dependency#271
deepyaman wants to merge 18 commits into
boringdata:mainfrom
deepyaman:refactor/optional-xorq

Conversation

@deepyaman

@deepyaman deepyaman commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Resolves #270 (and progresses towards #140)

What this does

Moves xorq from a core dependency to an optional [xorq] extra, so pip install boring-semantic-layer works without it. Core querying, YAML round-trips, and named profiles all continue to function against plain ibis backends.

Key changes

_xorq.py — dual-source shim
Wraps all xorq imports in a try/except ImportError block. When xorq is absent, falls back to the equivalent plain ibis-framework symbols. Exposes a HAS_XORQ flag used by call sites to gate xorq-only code paths. Provides pure-Python fallbacks for to_node, walk_nodes, replace_nodes, from_ibis (identity), and a map_ibis stub that preserves the register() interface without doing anything.

profile.py
Gates the XorqProfile lookup on HAS_XORQ; skips xorq_dir discovery when xorq is absent. Extracts _connect_plain_ibis() to avoid duplicating the connection logic across both the xorq-absent path and the existing xorq-AssertionError fallback. Adds _expand_env_vars(), which raises ProfileError on undefined ${VAR} references — os.path.expandvars silently leaves them in place, so without xorq database: ${MISSING} would create a DuckDB file literally named ${MISSING}. This matches xorq's strict behavior and only manifests on the new plain-ibis path.

ops.py / query.py — no-xorq parity
Short-circuits _rebind_to_canonical_backend and SemanticJoinOp backend rebinding when HAS_XORQ is false: without xorq from_ibis() is an identity, so there is nothing to rebind, and returning the inputs unchanged avoids the shim's plain-ibis walk/replace rebuilding an equal-but-distinct expression. Marks dict/string filters with __bsl_deferred_resolution__ so the dimension-only source-table shortcut opts out of static column introspection consistently — without xorq the tracking proxy would otherwise spuriously appear to succeed.

serialization/__init__.py and serialization/tag_handler.py
Guards from_tagged with the same try_import_xorq() + ImportError pattern already used by to_tagged. Sets bsl_tag_handler = None when xorq is absent (the entry point is only loaded by xorq at runtime, so this is safe).

pyproject.toml
xorq>=0.3.25 moves to an [xorq] optional extra; examples now pulls xorq[duckdb]>=0.3.25. The dev extra drops its self-referential boring-semantic-layer[...] bundle and lists the LLM provider clients and developer tooling directly.

CI
The test job is now a matrix over two legs: full (uv sync --all-extras) and no-xorq (--all-extras --no-extra xorq --no-extra examples, ignoring the integration suite). The no-xorq leg verifies xorq is genuinely optional — its tests skip, not error. Examples, docs, and skills (which need Node) move to a dedicated build job. Release gates (deploy-docs, release-pypi) now require both test and build.

Tests
Xorq-only tests standardize on pytest.importorskip("xorq") / a top-level import xorq so the skip fires before any BSL import. Flavor-sensitive tests and the demo adopt the xibis shim idiom for composed expressions. The projection-pushdown xfail is clarified. test_dependency_groups.py is updated for the new extra layout.

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>
@deepyaman deepyaman force-pushed the refactor/optional-xorq branch from 4a76265 to 7e88f23 Compare June 13, 2026 05:23
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>
@deepyaman deepyaman force-pushed the refactor/optional-xorq branch from 8e24508 to e12ae33 Compare June 13, 2026 07:02

@deepyaman deepyaman left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another round of feedback.

Comment thread scripts/demo_bsl_v2.py Outdated
)

rolling_window = xo.window(order_by="date", preceding=1, following=1)
rolling_window = get_ibis().window(order_by="date", preceding=1, following=1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I suggested this get_ibis(), but what's the point of Xorq here? The demo is otherwise entirely Ibis-based; why would we introduce a Xorq import just for window?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BSL's to_untagged() returns xorq-vendored ibis when xorq is installed, and those expressions reject a plain ibis.window (LegacyWindowBuilder). The demo runs under the [xorq] extra in CI, so plain ibis.window would break there. Switched to the established shim idiom from boring_semantic_layer._xorq import ibis as xibis (as used in expr.py) — plain ibis without xorq, vendored only when present — with an explanatory comment. Also dropped the get_ibis() helper I'd added, since it diverged from that existing idiom. (5d97122)

Comment thread .github/workflows/ci-release.yml Outdated
path: ./docs/web/dist

# Verify xorq is truly optional: install without it and run the core suite
test-no-xorq:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel like we should parametrize this workflow, and move the docs stuff into a separate step. Seems cleaner.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in dd2eb03 — collapsed the two near-duplicate test jobs into a matrix over the xorq extras (full / no-xorq), and split examples + docs-build + skills-check into a dedicated build job. Coverage is preserved; release/deploy gates updated to needs: [test, build].

.mutate(
rolling_avg=lambda t: t.flight_count.mean().over(
xo.window(rows=(-2, 0), order_by="flight_week")
get_ibis().window(rows=(-2, 0), order_by="flight_week")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, is Xorq necessary, or this could be pure Ibis? Does Xorq.window provide anything? Was there any justification for this in the blame; e.g. if Ibis window was missing some functionality that seems to be fixed now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the blame: the xorq-vendored window came in via 6296e56 / 7328d40 because to_untagged() returns xorq-vendored ibis. It's still required today — this PR's own CI proved it: commit 9a61507 (which used ibis.window) failed the with-xorq job with XorqTypeError: Unexpected window type: LegacyWindowBuilder. So ibis.window is not "fixed" for this case. xorq.window isn't functionally different (same ibis code under xorq's vendored namespace); it's needed only for type identity. Now built via the xibis shim so it's plain ibis without xorq. (5d97122)

@@ -222,8 +223,6 @@ class TestSQLGeneration:

def test_trino_sql_generation(self):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the two SQL generation tests be parametrized?

Also, does doing to_sql specifying dialect actually test what we want? I would expect you need a Trino backend to see the true behavior; not sure if Xorq works differently from Ibis in this regard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parametrized (5d97122). On the dialect question: ibis.to_sql(expr, dialect="trino") compiles through the dialect's compiler with no live backend — that's the standard, intended way to test dialect SQL generation. The assertion is on the emitted SQL string (a compilation concern), not execution, so a Trino backend isn't needed; xorq's vendored to_sql uses the same compiler. (This test predates the PR; we only swapped the ibis flavor.)

from xorq.vendor.ibis.expr.types.temporal import TimestampScalar as XTimestampScalar

assert isinstance(result, (ibis.expr.types.temporal.TimestampScalar, XTimestampScalar))
# Flavor-agnostic: a typed timestamp literal regardless of ibis flavor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment necessary?

If it is, I'd expect it for the below test, too. The need for commenting twice is also why I suggested parametrizing the test above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parametrized the two conversion tests into one (5d97122); the single result.type().is_timestamp() assertion is self-explanatory, so the comment is gone.

- viz-plotly: For Plotly visualization (chart with backend="plotly")
- examples: For running examples (includes xorq and duckdb)

Note: xorq is a core dependency (not optional) for window compatibility.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? Maybe should investigate/be very sure of this.

If this was the reason for making Xorq a core dependency, then we have a clear justification for making it optional again, if we can confidently prove whatever window issue is resolved. Please consider this carefully.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated carefully. The window-compat reason was real, but rooted in BSL vendoring (to_untagged() returns xorq-vendored ibis, 7328d40), not a missing ibis feature — and it is not "resolved": plain ibis.window still fails under xorq (verified by this PR's CI, commit 9a61507). It doesn't need to be resolved to make xorq optional, though — the incompatibility only exists when xorq is installed, and the shim selects the right flavor per environment. The dep-group note now describes the extra accurately (serialization/caching; optional).

from boring_semantic_layer.serialization import from_tagged, to_tagged, try_import_xorq
from boring_semantic_layer.serialization import from_tagged, to_tagged

# Check if xorq is available

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is importorskip not more idiomatic? Is there a reason for doing it this way?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — converted the three remaining files (test_malloy_xorq_roundtrip, test_xorq_integration, test_xorq_backends) to module-level pytest.importorskip("xorq"), matching the 11 files that already use it. Left test_dependency_groups.py as-is since its purpose is to test the dependency-group mechanism itself. (05e7b4a)

return _create_connection_from_config(config)


_ENV_VAR_PATTERN = re.compile(r"\$\{(\w+)\}|\$(\w+)")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a separate fix that should, at the very least, be a separated commit. Probably, it could be its own PR, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, maybe the connect plain Ibis and stuff indicate that this is coupled to this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split into its own commit: 1d64bdb.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — it's coupled: the bug only manifests on the new no-xorq _connect_plain_ibis() path (xorq's path handled env-var expansion). Kept in this PR, but as its own commit (1d64bdb).

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>
@deepyaman deepyaman force-pushed the refactor/optional-xorq branch from cd06970 to dd2eb03 Compare June 13, 2026 15:41
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>

@deepyaman deepyaman left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the below comments, want to clarify that should only update Ibis/Xorq capitalization in this PR if it's justifiably consistent with what exists across the codebase. Don't want to just capitalize whatever was introduced in this PR, if it's not in line with the rest of the codebase.

Comment thread .github/workflows/ci-release.yml Outdated
Comment on lines +33 to +36
pytest_args: ""
- name: no-xorq
sync: uv sync --all-extras --no-extra xorq --no-extra examples
pytest_args: --ignore=src/boring_semantic_layer/tests/integration

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is pytest_args the way to go for this? Should it not be markers based on whether Xorq is installed or something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good instinct — markers/conditional skips are the right model, and the xorq-specific unit tests already do exactly that via importorskip/HAS_XORQ guards. This --ignore was narrower: it targets tests/integration/, whose BSL query modules (malloy_data/*.py) import xorq.api directly, so the whole directory genuinely requires xorq.

I moved that gate into the test tree — integration/conftest.py now sets collect_ignore_glob = ["*"] when import xorq fails, so the directory self-skips without xorq. Both CI legs now run a plain uv run pytest; pytest_args is gone. (8d1fbc6)

Comment on lines +52 to +54
- name: Verify import works
run: uv run python -c "import boring_semantic_layer; print('import OK')"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? Was something like this there before?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — it predates this refactor: main's test-no-xorq job had a Verify import works without xorq step. It's a cheap smoke test that separates "the package does not import" from "a test failed" — especially meaningful for the no-xorq leg, whose whole purpose is proving BSL imports cleanly without xorq before the slower suite runs. The matrix just kept it. Happy to gate it to no-xorq only if you'd rather not have the (redundant) run on the full leg.

from boring_semantic_layer import to_semantic_table, to_untagged

# Projection pushdown disabled for xorq compatibility
# Projection pushdown is disabled in BSL (for xorq vendored ibis compatibility),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Xorq-vendored Ibis? Should it not be hyphenated?

Also, in general I'd capitalize Ibis personally... I assume Xorq should be a proper name, the same. At the same time, there's also the consistency with existing comments and stuff, regardless of grammatical correctness, so use your judgement I guess.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyphenated to xorq-vendored ibis — that's already the dominant form in the codebase (ops.py alone uses "xorq-vendored" 6×, plus nested_compile.py, mcp.py, server/api.py, …). Kept both lowercase deliberately: the codebase consistently lowercases xorq/ibis mid-sentence (package names) and only capitalizes at sentence start, so capitalizing here would break consistency rather than improve it. Applied the same fix to the two sibling pushdown test files that share the string. (b649f34)

Comment on lines +163 to +171
def walk_nodes(*args, **kwargs):
# Reachable only with xorq (from_ibis-wrapped graphs); without it the
# plain-ibis paths use graph_utils.walk_nodes and every other caller is
# xorq-gated, so this should never run.
raise ImportError(
"xorq is required for walk_nodes; "
"install with: pip install 'boring-semantic-layer[xorq]'"
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is not reachable, then why is it necessary? Is there a good justification?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It keeps the shim's symbol surface symmetric: every name the xorq branch exports is importable from the no-xorq branch too, so the function-local from ._xorq import ..., walk_nodes at the call sites in ops.py/reconstruct.py still resolves. You're right the body is unreachable without xorq — those callers all bail on HAS_XORQ first — so the raise is defense-in-depth: a clear, actionable error if some future un-gated caller reaches it, instead of a cryptic cannot import name. Reworded the comment to say that rather than just "should never run." (b649f34)

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>
@deepyaman deepyaman marked this pull request as ready for review June 14, 2026 03:41
@deepyaman deepyaman changed the title refactor: make xorq an optional dependency (extra) refactor: make xorq an optional (extra) dependency Jun 19, 2026
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.

Make Xorq optional again

1 participant