Skip to content

Commit 95de6a2

Browse files
build(kernel): add optional [kernel] extra for use_kernel=True (#839)
* build(kernel): add optional [kernel] extra for use_kernel=True databricks-sql-kernel is now published to PyPI, so the kernel backend can ship as an optional dependency instead of a local-dev-only build. - pyproject: declare databricks-sql-kernel as an optional dependency gated to python>=3.10 (the wheel is cp310-abi3, Requires-Python >=3.10), and add the `[kernel]` extra. The extra also lists pyarrow: the kernel result path (backend/kernel/result_set.py) imports it unconditionally to wrap the Arrow batches the kernel returns. pyarrow is already pulled transitively via the kernel wheel's pyarrow>=23.0.1,<24, but naming it makes the connector-side requirement explicit and lets pip co-resolve both constraints at install time. - backend/kernel/_errors.py: update the use_kernel=True ImportError to point at `pip install "databricks-sql-connector[kernel]"` and note the python>=3.10 requirement (was the obsolete "not yet published, build locally" hint). - README: document the [kernel] extra, use_kernel=True usage, and the python>=3.10 / pyarrow notes. On python<3.10 the `[kernel]` extra resolves to nothing and use_kernel=True raises the friendly ImportError at runtime; the connector's own python floor (3.8) is unchanged. Verified locally (kernel served from a locally-built cp310-abi3 wheel, since the published package isn't yet mirrored on the dev proxy): - pip install "databricks-sql-connector[kernel]" -> connector + kernel + pyarrow all install; use_kernel=True runs a live query end-to-end (backend KernelDatabricksClient). - plain install -> use_kernel=True raises the friendly ImportError. NOTE: `poetry lock` still needs to be run to refresh poetry.lock with the databricks-sql-kernel entry; it is intentionally NOT included here because it requires the kernel to be resolvable on the index poetry/CI use (the JFrog db-pypi proxy). Confirm the package resolves there before merging. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com> * fix(kernel): drop pyarrow from the [kernel] extra to unbreak poetry lock Listing bare `pyarrow` in the [kernel] extra forced poetry to co-resolve an unconstrained pyarrow against the kernel's transitive `pyarrow>=23.0.1,<24` across the connector's full 3.8–3.14 matrix. pyarrow 23.x requires Python >=3.10, so the constraint is unsatisfiable on 3.8/3.9 — `poetry lock` failed every CI job with "version solving failed ... pyarrow is forbidden". The kernel wheel already declares `pyarrow>=23.0.1,<24` as a hard runtime dependency, so `pip install databricks-sql-connector[kernel]` still pulls a compatible pyarrow transitively. The databricks-sql-kernel dep stays gated to python>=3.10, which now correctly excludes the whole kernel+pyarrow subtree from the 3.8/3.9 resolution. The kernel's own metadata is the single source of truth for the pyarrow floor. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com> * fix(kernel): cap pyarrow <23 on the sub-3.10 band so poetry lock resolves The kernel's transitive pyarrow>=23.0.1,<24 conflicts with the connector's own pyarrow>=14.0.1 (declared across 3.8–3.13) during `poetry lock`: pyarrow>=23 dropped Python 3.9, so for the 3.8–3.10 slice poetry can't find a pyarrow satisfying both and version solving fails ("pyarrow is forbidden" -> "databricks-sql-kernel is forbidden"). The kernel's python>=3.10 marker doesn't help because poetry unifies the pyarrow constraint across the connector's declared pyarrow band, not the kernel's. Split the connector's pyarrow entry at 3.10 and cap the <3.10 band at <23. This removes no installable version — the newest pyarrow with a Python 3.9 wheel is 21.x — it just makes that physical fact explicit to the solver, so the <3.10 band (capped, kernel absent) and the >=3.10 band (where the kernel can pull pyarrow up to <24) no longer overlap. Verified `poetry lock` resolves the full dependency set with this change. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com> * ci(kernel): add "Unit Tests + Kernel" matrix exercising the real wheel Mirrors the "Unit Tests + PyArrow" matrix but for the [kernel] extra. Until now no CI job exercised the published kernel wheel: the base unit-test matrix installs no extras, and the kernel unit tests use a fake databricks_sql_kernel module injected into sys.modules, so the real wheel was never loaded in CI. The new job (Python 3.10–3.14; the wheel is cp310-abi3 so 3.9 is omitted) installs the [kernel] extra via --all-extras, then: - asserts databricks_sql_kernel imports and has a real __file__ (i.e. the published wheel actually installed, not the test fake), and - imports the use_kernel backend path (KernelDatabricksClient / KernelResultSet) against the real wheel, before running the unit suite. This is the only CI signal that the published [kernel] extra installs and loads end to end on every PR (the live use_kernel=True e2e remains in kernel-e2e.yml, merge-queue gated). Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com> * test(kernel): exercise use_kernel=True through the real wheel; no silent skips Ensure every CI job that's meant to cover the kernel actually drives the use_kernel=True path through the REAL databricks-sql-kernel wheel, and fails loudly if it can't (rather than silently skipping / passing on the Thrift path). Problem this fixes: - The kernel unit tests inject a fake databricks_sql_kernel into sys.modules. In a shared `pytest tests/unit tests/e2e` session (the coverage job, which installs --all-extras so the real wheel IS present) that fake shadowed the real wheel, so the kernel e2e tests silently skipped — the coverage job looked like it exercised the kernel but didn't. Changes: - tests/e2e/test_kernel_backend.py + test_kernel_tls.py: replace the silent `__file__`-based skip with a three-state guard keyed on importlib.metadata (the on-disk dist DB, which a sys.modules stub can't fake): skip only when the wheel is genuinely absent; FAIL LOUDLY when it's installed-but-shadowed. The `conn` fixture now also asserts conn.session.backend is KernelDatabricksClient, so a use_kernel=True connection that fell back to Thrift fails the test. - tests/unit/test_session.py: add TestUseKernelRoutesThroughRealWheel (marked `realkernel`) — a no-network proof that sql.connect(use_kernel=True) instantiates the REAL KernelDatabricksClient (mocks only open_session; does not fake the wheel). Skips if the wheel is absent; fails if it's shadowed. - pyproject.toml: register the `realkernel` marker. Tests so marked need an unpolluted sys.modules and must run in a separate pytest invocation from the fake-injecting unit tests. - tests/unit/test_kernel_client.py: document that its session-global fake mandates the separate-invocation rule for real-wheel tests. - code-quality-checks.yml: the Unit Tests + Kernel matrix now asserts the real wheel, runs `tests/unit -m "not realkernel"`, then runs the real-wheel routing test as its own invocation (`pytest tests/unit/test_session.py -m realkernel`). All three unit matrices gained `-m "not realkernel"`. - code-coverage.yml: --ignore the kernel e2e files and add `-m "not realkernel"` so the shared --all-extras session doesn't trip the new loud guards; the real live kernel e2e stays in kernel-e2e.yml (isolated session, real wheel, live warehouse). Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com> * ci(kernel): install per-tier extras explicitly, not --all-extras The "Unit Tests + PyArrow" job used --all-extras, which predates the [kernel] extra. Now that [kernel] exists, --all-extras silently also installs the kernel wheel — so that tier no longer isolated the "pyarrow present, kernel absent" configuration and overlapped the new "Unit Tests + Kernel" job. - Unit Tests + PyArrow: --extras pyarrow (pyarrow only; no kernel). - Unit Tests + Kernel: --extras kernel (resolves the published databricks-sql-kernel wheel via the [kernel] extra — the exact edge `pip install databricks-sql-connector[kernel]` uses — which transitively brings pyarrow). Each tier now targets its configuration precisely. The kernel install path here (published wheel via the extra) is intentionally distinct from kernel-e2e.yml, which maturin-builds tip-of-tree at KERNEL_REV. Verified against the proxy: --extras pyarrow installs pyarrow and NOT the kernel; --extras kernel installs databricks-sql-kernel 0.1.2. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com> * build(kernel): require databricks-sql-kernel >=0.2.0 Bump the [kernel] extra's floor from ^0.1.0 to ^0.2.0 (>=0.2.0,<0.3.0) now that 0.2.0 is published. The <0.3.0 cap is deliberate: the kernel is pre-1.0, so each 0.x minor may be breaking — we bump this when the kernel ships 0.3.0 rather than auto-adopting a potentially-breaking minor. 0.2.0 keeps the same Requires-Python (>=3.10) and pyarrow (>=23.0.1,<24) pin as 0.1.x, so the python>=3.10 marker and the pyarrow <23 sub-3.10 cap are unchanged. Verified `poetry lock` resolves and locks databricks-sql-kernel 0.2.0. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com> --------- Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
1 parent e742771 commit 95de6a2

9 files changed

Lines changed: 314 additions & 49 deletions

File tree

.github/workflows/code-coverage.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,21 @@ jobs:
6363
install-args: "--all-extras"
6464
- name: Run all tests with coverage
6565
continue-on-error: false
66+
# This job installs --all-extras, so the REAL databricks-sql-kernel
67+
# wheel is present. The unit suite fakes databricks_sql_kernel in
68+
# sys.modules, which would shadow the real wheel in this shared
69+
# session — so the kernel-backed suites that need the real wheel
70+
# are excluded here and covered by the dedicated kernel-e2e.yml
71+
# (isolated session, real wheel, live warehouse):
72+
# - --ignore the kernel e2e files (they assert the real wheel and
73+
# now FAIL LOUDLY rather than silently skip if shadowed), and
74+
# - -m "not realkernel" deselects the no-network real-wheel
75+
# routing test for the same reason.
6676
run: |
6777
poetry run pytest tests/unit tests/e2e \
78+
--ignore=tests/e2e/test_kernel_backend.py \
79+
--ignore=tests/e2e/test_kernel_tls.py \
80+
-m "not realkernel" \
6881
-n 4 \
6982
--dist=loadgroup \
7083
--cov=src \

.github/workflows/code-quality-checks.yml

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
echo "=== Dependency Version: ${{ matrix.dependency-version }} ==="
4949
poetry run pip list
5050
- name: Run tests
51-
run: poetry run python -m pytest tests/unit
51+
run: poetry run python -m pytest tests/unit -m "not realkernel"
5252

5353
run-unit-tests-with-arrow:
5454
runs-on:
@@ -77,7 +77,11 @@ jobs:
7777
uses: ./.github/actions/setup-poetry
7878
with:
7979
python-version: ${{ matrix.python-version }}
80-
install-args: "--all-extras"
80+
# Install ONLY the pyarrow extra (not --all-extras) so this
81+
# tier isolates the "pyarrow present, kernel absent"
82+
# configuration. --all-extras would also pull the kernel wheel,
83+
# making this job redundant with "Unit Tests + Kernel".
84+
install-args: "--extras pyarrow"
8185
cache-suffix: "pyarrow-${{ matrix.dependency-version }}-"
8286
- name: Install Python tools for custom versions
8387
if: matrix.dependency-version != 'default'
@@ -96,7 +100,57 @@ jobs:
96100
echo "=== Dependency Version: ${{ matrix.dependency-version }} with PyArrow ==="
97101
poetry run pip list
98102
- name: Run tests
99-
run: poetry run python -m pytest tests/unit
103+
run: poetry run python -m pytest tests/unit -m "not realkernel"
104+
105+
run-unit-tests-with-kernel:
106+
runs-on:
107+
group: databricks-protected-runner-group
108+
labels: linux-ubuntu-latest
109+
strategy:
110+
matrix:
111+
# Kernel wheel is cp310-abi3 (Requires-Python >=3.10), so this
112+
# matrix omits 3.9 — the [kernel] extra is a no-op there.
113+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
114+
115+
name: "Unit Tests + Kernel (Python ${{ matrix.python-version }})"
116+
117+
steps:
118+
- name: Check out repository
119+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
120+
- name: Install Kerberos system dependencies
121+
run: |
122+
sudo apt-get update
123+
sudo apt-get install -y libkrb5-dev
124+
- name: Setup Poetry
125+
uses: ./.github/actions/setup-poetry
126+
with:
127+
python-version: ${{ matrix.python-version }}
128+
# Install the kernel extra (pulls the published
129+
# databricks-sql-kernel wheel, which transitively brings
130+
# pyarrow). Explicit --extras kernel rather than --all-extras
131+
# so this tier targets the kernel configuration specifically.
132+
install-args: "--extras kernel"
133+
cache-suffix: "kernel-"
134+
- name: Show installed versions
135+
run: |
136+
echo "=== with databricks-sql-kernel ==="
137+
poetry run pip list
138+
- name: Assert the real kernel wheel is installed (not a stub)
139+
run: |
140+
poetry run python -c "import databricks_sql_kernel as k; assert k.__file__, 'kernel wheel missing __file__ — not the real wheel'; print('real kernel wheel:', k.__file__)"
141+
- name: Unit tests (kernel wheel present, realkernel deselected)
142+
# The bulk of tests/unit fakes databricks_sql_kernel in
143+
# sys.modules, so the real-wheel routing test is deselected here
144+
# and run on its own below (a shared session would shadow the
145+
# real wheel — both real-wheel tests fail loudly if that happens).
146+
run: poetry run python -m pytest tests/unit -m "not realkernel"
147+
- name: Drive use_kernel=True through the REAL wheel (routing)
148+
# Separate invocation, explicit file path: never collects the
149+
# fake-module test file, so sys.modules stays unpolluted. This is
150+
# the no-network proof that sql.connect(use_kernel=True) actually
151+
# instantiates the real KernelDatabricksClient (not a stub, not a
152+
# Thrift fallback). Fails loudly if the real wheel is shadowed.
153+
run: poetry run python -m pytest tests/unit/test_session.py -m realkernel -v
100154

101155
check-linting:
102156
runs-on:

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,30 @@ Install using `pip install databricks-sql-connector`
3030
### Installing the core library with PyArrow
3131
Install using `pip install databricks-sql-connector[pyarrow]`
3232

33+
### Installing with the Rust kernel backend (`use_kernel=True`)
34+
Install using `pip install databricks-sql-connector[kernel]`
35+
36+
This adds the optional [`databricks-sql-kernel`](https://pypi.org/project/databricks-sql-kernel/)
37+
extension (a native Rust client core, exposed via PyO3). Pass
38+
`use_kernel=True` to `sql.connect(...)` to route the connection through it
39+
instead of the default Thrift backend:
40+
41+
```python
42+
connection = sql.connect(
43+
server_hostname=host,
44+
http_path=http_path,
45+
access_token=token,
46+
use_kernel=True,
47+
)
48+
```
49+
50+
Notes:
51+
- Requires **Python >= 3.10** (the kernel wheel is published as
52+
`cp310-abi3`). On older interpreters the `[kernel]` extra installs
53+
nothing and `use_kernel=True` raises an `ImportError`.
54+
- The extra also pulls in PyArrow, which the kernel result path requires.
55+
- Authentication supports PAT (`access_token`), OAuth M2M, and OAuth U2M.
56+
3357

3458
```bash
3559
export DATABRICKS_HOST=********.databricks.com

pyproject.toml

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,56 @@ openpyxl = "^3.0.10"
2525
urllib3 = ">=1.26"
2626
python-dateutil = "^2.8.0"
2727
pyarrow = [
28-
{ version = ">=14.0.1", python = ">=3.8,<3.13", optional=true },
28+
# The <3.10 band is capped at <23 because pyarrow>=23 dropped
29+
# Python 3.9 (it requires >=3.10). Without the cap, poetry tries to
30+
# unify this entry with the kernel's transitive pyarrow>=23.0.1,<24
31+
# across the 3.8–3.10 slice and `poetry lock` fails ("pyarrow is
32+
# forbidden"). The cap removes no installable version — the newest
33+
# pyarrow with a 3.9 wheel is 21.x — it just makes that explicit to
34+
# the solver so the optional [kernel] extra (python>=3.10) can
35+
# coexist. See the kernel dep + [kernel] extra below.
36+
{ version = ">=14.0.1,<23", python = ">=3.8,<3.10", optional=true },
37+
{ version = ">=14.0.1", python = ">=3.10,<3.13", optional=true },
2938
{ version = ">=18.0.0", python = ">=3.13,<3.14", optional=true },
3039
{ version = ">=22.0.0", python = ">=3.14", optional=true }
3140
]
3241
pyjwt = "^2.0.0"
3342
pybreaker = "^1.0.0"
3443
requests-kerberos = {version = "^0.15.0", optional = true}
44+
# Optional Rust kernel backend for ``use_kernel=True`` (PyO3 wheel).
45+
# Pulled in only via the ``[kernel]`` extra below. The published wheel
46+
# is ``abi3`` with ``Requires-Python: >=3.10`` (built ``abi3-py310``),
47+
# so the dependency is gated to Python >= 3.10: on 3.8/3.9 the
48+
# ``[kernel]`` extra resolves to nothing and ``use_kernel=True`` raises
49+
# a clear ImportError at runtime (see backend/kernel/_errors.py).
50+
#
51+
# Floor is 0.2.0 (``^0.2.0`` == ``>=0.2.0,<0.3.0``). The kernel is
52+
# pre-1.0, so each 0.x minor may carry breaking changes — the ``<0.3.0``
53+
# cap means we bump this deliberately when the kernel ships 0.3.0 rather
54+
# than letting a potentially-breaking minor flow in automatically. 0.2.0
55+
# keeps the same Requires-Python (>=3.10) and pyarrow (>=23.0.1,<24) pin
56+
# as 0.1.x, so the gating below is unchanged.
57+
databricks-sql-kernel = {version = "^0.2.0", optional = true, python = ">=3.10"}
3558

3659

3760
[tool.poetry.extras]
3861
pyarrow = ["pyarrow"]
39-
# `[kernel]` extra is intentionally not declared here yet.
40-
# `databricks-sql-kernel` is built from the databricks-sql-kernel
41-
# repo and not yet published to PyPI; declaring it as a poetry dep
42-
# breaks `poetry lock` for every CI job. Once the wheel is on PyPI
43-
# the extra will be added back here:
44-
#
45-
# databricks-sql-kernel = {version = "^0.1.0", optional = true}
46-
# [tool.poetry.extras]
47-
# kernel = ["databricks-sql-kernel"]
62+
# ``pip install databricks-sql-connector[kernel]`` adds the Rust kernel
63+
# backend so ``use_kernel=True`` works. No-op on Python < 3.10 (the
64+
# wheel's floor) — those users get a runtime ImportError if they pass
65+
# ``use_kernel=True``.
4866
#
49-
# Until then, the wheel is not on PyPI and the only supported
50-
# install path is local dev:
51-
# cd databricks-sql-kernel/pyo3 && maturin develop --release
52-
# (into the same venv as databricks-sql-connector).
67+
# The kernel result path (``backend/kernel/result_set.py``) needs
68+
# pyarrow, but it is NOT listed in this extra on purpose: the published
69+
# kernel wheel declares ``pyarrow>=23.0.1,<24`` as a hard runtime
70+
# dependency, so ``pip install ...[kernel]`` already pulls a compatible
71+
# pyarrow transitively. Listing bare ``pyarrow`` here additionally
72+
# forces poetry to co-resolve an unconstrained pyarrow against the
73+
# kernel's ``>=23.0.1,<24`` (which itself requires Python >=3.10) across
74+
# the connector's full 3.8–3.14 support matrix, which is unsatisfiable
75+
# on 3.8/3.9 and breaks ``poetry lock``. The kernel's own dependency
76+
# metadata is the single source of truth for the pyarrow floor.
77+
kernel = ["databricks-sql-kernel"]
5378

5479
[tool.poetry.group.dev.dependencies]
5580
pytest = "^7.1.2"
@@ -82,7 +107,8 @@ exclude = '/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck
82107
[tool.pytest.ini_options]
83108
markers = [
84109
"reviewed: Test case has been reviewed by Databricks",
85-
"serial: Tests that must run serially (not parallelized)"
110+
"serial: Tests that must run serially (not parallelized)",
111+
"realkernel: Requires the real databricks-sql-kernel wheel and an unpolluted sys.modules (no fake kernel stub); must run in a separate pytest invocation from tests that fake databricks_sql_kernel (deselect with -m 'not realkernel', run alone with -m realkernel).",
86112
]
87113
minversion = "6.0"
88114
log_cli = "false"

src/databricks/sql/backend/kernel/_errors.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@
5252
import databricks_sql_kernel as _kernel # type: ignore[import-not-found]
5353
except ImportError as exc: # pragma: no cover - same hint as client.py
5454
raise ImportError(
55-
"use_kernel=True requires the databricks-sql-kernel extension, which "
56-
"is not yet published on PyPI. Build and install it locally from the "
57-
"databricks-sql-kernel repo:\n"
58-
" cd databricks-sql-kernel/pyo3 && maturin develop --release\n"
59-
"(into the same venv as databricks-sql-connector)."
55+
"use_kernel=True requires the optional databricks-sql-kernel "
56+
"extension, which is not installed. Install it with:\n"
57+
' pip install "databricks-sql-connector[kernel]"\n'
58+
"The kernel wheel requires Python >= 3.10; on older interpreters "
59+
"use_kernel is unavailable. For local kernel development you can "
60+
"instead build it from the databricks-sql-kernel repo:\n"
61+
" cd databricks-sql-kernel/pyo3 && maturin develop --release"
6062
) from exc
6163

6264
# Route the kernel's Rust-side logs into Python's ``logging`` as soon as

tests/e2e/test_kernel_backend.py

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from __future__ import annotations
2323

24+
import sys
2425
from uuid import uuid4
2526

2627
import pytest
@@ -34,24 +35,59 @@
3435
ServerOperationError,
3536
)
3637

37-
# Skip the whole module unless the kernel wheel is genuinely installed.
38-
# ``pytest.importorskip`` alone isn't enough: the kernel unit tests inject a
39-
# fake ``databricks_sql_kernel`` ModuleType into ``sys.modules`` so the
40-
# connector's import-time ``import databricks_sql_kernel`` succeeds without
41-
# the Rust extension. In the same pytest session that fake module is still
42-
# in ``sys.modules`` when this e2e file is collected, and importorskip
43-
# happily returns it. A real wheel exposes ``__file__`` (the compiled
44-
# extension on disk); the fake ModuleType does not.
45-
_kernel_mod = pytest.importorskip(
46-
"databricks_sql_kernel",
47-
reason="use_kernel=True requires the databricks-sql-kernel package",
48-
)
49-
if not getattr(_kernel_mod, "__file__", None):
38+
# These tests must run against the REAL databricks-sql-kernel wheel and
39+
# must NOT silently pass when it's absent or shadowed. We distinguish
40+
# three states explicitly so a misconfigured CI job can't look green:
41+
#
42+
# 1. Wheel genuinely not installed -> legitimate skip.
43+
# 2. Wheel installed in the env but ``sys.modules`` currently holds a
44+
# stub (the kernel UNIT tests inject a fake ``databricks_sql_kernel``
45+
# ModuleType; in a shared ``pytest tests/unit tests/e2e`` session
46+
# that fake can still be resident when this file is collected) ->
47+
# FAIL loudly. Silently skipping here is what made the coverage job
48+
# look like it exercised the kernel when it didn't.
49+
# 3. Wheel installed and importable for real -> run.
50+
#
51+
# "Installed in the env" is decided via importlib.metadata (the dist
52+
# database on disk), which a ``sys.modules`` stub can't fake. The
53+
# ``__file__`` check then tells a real compiled extension from a stub
54+
# ModuleType.
55+
import importlib.metadata as _ilm
56+
57+
try:
58+
_ilm.version("databricks-sql-kernel")
59+
_kernel_installed = True
60+
except _ilm.PackageNotFoundError:
61+
_kernel_installed = False
62+
63+
_kernel_mod = sys.modules.get("databricks_sql_kernel")
64+
if _kernel_mod is None:
65+
try:
66+
import databricks_sql_kernel as _kernel_mod # type: ignore[import-not-found]
67+
except ImportError:
68+
_kernel_mod = None
69+
70+
_kernel_is_real = _kernel_mod is not None and getattr(_kernel_mod, "__file__", None)
71+
72+
if not _kernel_installed:
73+
# State 1: nothing to test against.
5074
pytest.skip(
51-
"databricks_sql_kernel is a test stub (no __file__); "
52-
"install the real wheel to run kernel e2e tests",
75+
"databricks-sql-kernel is not installed; "
76+
"install the real wheel (pip install 'databricks-sql-connector[kernel]') "
77+
"to run kernel e2e tests",
5378
allow_module_level=True,
5479
)
80+
elif not _kernel_is_real:
81+
# State 2: the wheel IS installed but a stub is shadowing it. Do NOT
82+
# skip — that would hide the fact that the kernel path never ran.
83+
raise RuntimeError(
84+
"databricks-sql-kernel is installed in this environment but "
85+
"sys.modules holds a stub (no __file__) — the kernel e2e tests "
86+
"would not actually exercise the real wheel. This usually means a "
87+
"unit test's fake databricks_sql_kernel module is shadowing the "
88+
"real one in a shared pytest session. Run the kernel e2e tests in "
89+
"isolation (separate pytest invocation) so the real wheel loads."
90+
)
5591

5692

5793
@pytest.fixture(scope="module")
@@ -80,9 +116,21 @@ def kernel_conn_params(connection_details):
80116
@pytest.fixture
81117
def conn(kernel_conn_params):
82118
"""One-shot connection per test (the simple_test pattern the
83-
existing e2e suite uses for cursor-level tests)."""
119+
existing e2e suite uses for cursor-level tests).
120+
121+
Asserts the connection actually routed through the kernel backend —
122+
if ``use_kernel=True`` silently fell back to Thrift (e.g. a wiring
123+
regression), these tests must fail rather than pass against the
124+
wrong backend.
125+
"""
126+
from databricks.sql.backend.kernel.client import KernelDatabricksClient
127+
84128
c = sql.connect(**kernel_conn_params)
85129
try:
130+
assert isinstance(c.session.backend, KernelDatabricksClient), (
131+
"use_kernel=True did not route through KernelDatabricksClient; "
132+
f"got {type(c.session.backend).__name__}"
133+
)
86134
yield c
87135
finally:
88136
c.close()

tests/e2e/test_kernel_tls.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,46 @@
3030
from __future__ import annotations
3131

3232
import os
33+
import sys
3334

3435
import pytest
3536

3637
import databricks.sql as sql
3738
from databricks.sql.exc import Error as DatabricksSqlError
3839

39-
# Same real-wheel guard as test_kernel_backend.py: a fake
40-
# ``databricks_sql_kernel`` ModuleType injected by the unit tests has no
41-
# ``__file__``; only a compiled wheel does.
42-
_kernel_mod = pytest.importorskip(
43-
"databricks_sql_kernel",
44-
reason="use_kernel=True requires the databricks-sql-kernel package",
45-
)
46-
if not getattr(_kernel_mod, "__file__", None):
40+
# Same real-wheel guard as test_kernel_backend.py — see the detailed
41+
# rationale there. Skip only when the wheel is genuinely not installed;
42+
# FAIL LOUDLY if it's installed but shadowed by a stub (so a misconfigured
43+
# shared pytest session can't silently pass as covering the kernel).
44+
import importlib.metadata as _ilm
45+
46+
try:
47+
_ilm.version("databricks-sql-kernel")
48+
_kernel_installed = True
49+
except _ilm.PackageNotFoundError:
50+
_kernel_installed = False
51+
52+
_kernel_mod = sys.modules.get("databricks_sql_kernel")
53+
if _kernel_mod is None:
54+
try:
55+
import databricks_sql_kernel as _kernel_mod # type: ignore[import-not-found]
56+
except ImportError:
57+
_kernel_mod = None
58+
_kernel_is_real = _kernel_mod is not None and getattr(_kernel_mod, "__file__", None)
59+
60+
if not _kernel_installed:
4761
pytest.skip(
48-
"databricks_sql_kernel is a test stub (no __file__); "
49-
"install the real wheel to run kernel TLS e2e tests",
62+
"databricks-sql-kernel is not installed; install the real wheel "
63+
"to run kernel TLS e2e tests",
5064
allow_module_level=True,
5165
)
66+
elif not _kernel_is_real:
67+
raise RuntimeError(
68+
"databricks-sql-kernel is installed but sys.modules holds a stub "
69+
"(no __file__) — the kernel TLS e2e tests would not exercise the "
70+
"real wheel. Run them in isolation (separate pytest invocation) so "
71+
"a unit-test fake module doesn't shadow the real one."
72+
)
5273

5374
_MITM_CA = os.getenv("MITMPROXY_CA_CERT")
5475
if not _MITM_CA:

0 commit comments

Comments
 (0)