Skip to content

Commit 8261c66

Browse files
committed
Disable Python telemetry for kernel connections
1 parent e550cb7 commit 8261c66

6 files changed

Lines changed: 224 additions & 2 deletions

File tree

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
NotSupportedError,
4848
ProgrammingError,
4949
)
50+
from databricks.sql.telemetry.telemetry_client import TelemetryHelper
5051
from databricks.sql.thrift_api.TCLIService import ttypes
5152

5253
if TYPE_CHECKING:
@@ -165,6 +166,31 @@ def _is_staging_statement(operation: str) -> bool:
165166
return verb in _STAGING_VERBS
166167

167168

169+
def _kernel_telemetry_kwargs(options: Dict[str, Any]) -> Dict[str, Any]:
170+
"""Build phase-7 telemetry/system kwargs for ``databricks_sql_kernel.Session``."""
171+
system = TelemetryHelper.get_driver_system_configuration()
172+
out: Dict[str, Any] = {
173+
"driver_name": system.driver_name,
174+
"driver_version": system.driver_version,
175+
"runtime_name": system.runtime_name,
176+
"runtime_version": system.runtime_version,
177+
"runtime_vendor": system.runtime_vendor,
178+
"os_name": system.os_name,
179+
"os_version": system.os_version,
180+
"os_arch": system.os_arch,
181+
"client_app_name": system.client_app_name,
182+
"locale_name": system.locale_name,
183+
"char_set_encoding": system.char_set_encoding,
184+
# The Python telemetry model does not currently track process
185+
# name; omit it and let the kernel fill what it can derive.
186+
"process_name": None,
187+
"telemetry_enabled": bool(options.get("enable_telemetry", True)),
188+
}
189+
if options.get("telemetry_batch_size") is not None:
190+
out["telemetry_batch_size"] = options["telemetry_batch_size"]
191+
return out
192+
193+
168194
# ─── Client ─────────────────────────────────────────────────────────────────
169195

170196

@@ -217,6 +243,9 @@ def __init__(
217243
# to the kernel ``Session``'s ``retry_*`` kwargs in
218244
# ``open_session`` via ``_kernel_retry_kwargs``.
219245
self._retry_options = kwargs.get("retry_options") or {}
246+
# Kernel telemetry phase 7 adds binding/runtime identity and
247+
# telemetry config kwargs directly to ``databricks_sql_kernel.Session``.
248+
self._telemetry_options = kwargs.get("telemetry_options") or {}
220249
self._catalog = catalog
221250
self._schema = schema
222251
# ``_use_arrow_native_complex_types`` is the connector-side
@@ -316,6 +345,7 @@ def open_session(
316345
# Translate the connector's ``_retry_*`` kwargs into the
317346
# kernel's ``retry_*`` kwargs. Empty when at defaults.
318347
retry_kwargs = _kernel_retry_kwargs(self._retry_options)
348+
telemetry_kwargs = _kernel_telemetry_kwargs(self._telemetry_options)
319349
# Forward caller / connector HTTP headers. The kernel applies
320350
# them on every request; a caller ``User-Agent`` is appended
321351
# to the kernel's base UA. Only pass the kwarg when there's
@@ -358,6 +388,7 @@ def open_session(
358388
**auth_kwargs,
359389
**tls_kwargs,
360390
**retry_kwargs,
391+
**telemetry_kwargs,
361392
**http_headers_kwargs,
362393
)
363394
except Exception as exc:

src/databricks/sql/session.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,16 @@ def _create_backend(
204204
"_retry_stop_after_attempts_duration"
205205
),
206206
}
207+
# Forward the binding/runtime identity and telemetry knobs
208+
# added by kernel telemetry phase 7. Python-side telemetry
209+
# still owns feature-flag evaluation and event export for the
210+
# Thrift/SEA paths; the kernel path needs the same driver
211+
# identity at Session construction time so kernel-owned
212+
# telemetry can populate its system configuration.
213+
kernel_telemetry_options = {
214+
"enable_telemetry": kwargs.get("enable_telemetry", True),
215+
"telemetry_batch_size": kwargs.get("telemetry_batch_size"),
216+
}
207217
return KernelDatabricksClient(
208218
server_hostname=server_hostname,
209219
http_path=http_path,
@@ -216,6 +226,7 @@ def _create_backend(
216226
_use_arrow_native_complex_types=_use_arrow_native_complex_types,
217227
auth_options=kernel_auth_options,
218228
retry_options=kernel_retry_options,
229+
telemetry_options=kernel_telemetry_options,
219230
)
220231

221232
databricks_client_class: Type[DatabricksClient]

src/databricks/sql/telemetry/telemetry_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ def get_auth_flow(auth_provider):
122122

123123
@staticmethod
124124
def is_telemetry_enabled(connection: "Connection") -> bool:
125+
if getattr(connection.session, "use_kernel", False) is True:
126+
return False
127+
125128
# Fast path: force enabled - skip feature flag fetch entirely
126129
if connection.force_enable_telemetry:
127130
return True

tests/unit/test_kernel_client.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,64 @@ def fake_session(**kw):
344344
assert captured.get("complex_types_as_json") is expected_flag
345345

346346

347+
def test_open_session_passes_phase_7_telemetry_kwargs_to_kernel(monkeypatch):
348+
"""Kernel telemetry phase 7 added binding/runtime identity and
349+
telemetry config kwargs to ``databricks_sql_kernel.Session``."""
350+
captured = {}
351+
352+
def fake_session(**kw):
353+
captured.update(kw)
354+
sess = MagicMock()
355+
sess.session_id = "sess-id"
356+
return sess
357+
358+
monkeypatch.setattr(kernel_client._kernel, "Session", fake_session)
359+
monkeypatch.setattr(
360+
kernel_client.TelemetryHelper,
361+
"get_driver_system_configuration",
362+
lambda: types.SimpleNamespace(
363+
driver_name="Databricks SQL Python Connector",
364+
driver_version="1.2.3",
365+
runtime_name="Python 3.12.0",
366+
runtime_version="3.12.0",
367+
runtime_vendor="CPython",
368+
os_name="Linux",
369+
os_version="6.1",
370+
os_arch="x86_64",
371+
client_app_name=None,
372+
locale_name="en_US",
373+
char_set_encoding="utf-8",
374+
),
375+
)
376+
377+
c = kernel_client.KernelDatabricksClient(
378+
server_hostname="example.cloud.databricks.com",
379+
http_path="/sql/1.0/warehouses/abc",
380+
auth_provider=AccessTokenAuthProvider("dapi-test"),
381+
ssl_options=None,
382+
telemetry_options={
383+
"enable_telemetry": True,
384+
"telemetry_batch_size": 17,
385+
},
386+
)
387+
c.open_session(session_configuration=None, catalog=None, schema=None)
388+
389+
assert captured["driver_name"] == "Databricks SQL Python Connector"
390+
assert captured["driver_version"] == "1.2.3"
391+
assert captured["runtime_name"] == "Python 3.12.0"
392+
assert captured["runtime_version"] == "3.12.0"
393+
assert captured["runtime_vendor"] == "CPython"
394+
assert captured["os_name"] == "Linux"
395+
assert captured["os_version"] == "6.1"
396+
assert captured["os_arch"] == "x86_64"
397+
assert captured["client_app_name"] is None
398+
assert captured["locale_name"] == "en_US"
399+
assert captured["char_set_encoding"] == "utf-8"
400+
assert captured["process_name"] is None
401+
assert captured["telemetry_enabled"] is True
402+
assert captured["telemetry_batch_size"] == 17
403+
404+
347405
def test_execute_command_forwards_parameters_to_bind_param():
348406
"""``execute_command(parameters=[...])`` routes each parameter
349407
through ``bind_tspark_params`` onto the kernel statement before

tests/unit/test_session.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import sys
23
from unittest.mock import patch, MagicMock, Mock, PropertyMock
34
import gc
45

@@ -14,6 +15,14 @@
1415
import databricks.sql
1516

1617

18+
def _forget_kernel_client_module():
19+
sys.modules.pop("databricks.sql.backend.kernel.client", None)
20+
import databricks.sql.backend.kernel as kernel_pkg
21+
22+
if hasattr(kernel_pkg, "client"):
23+
delattr(kernel_pkg, "client")
24+
25+
1726
class TestSession:
1827
"""
1928
Unit tests for Session functionality
@@ -427,7 +436,6 @@ class TestKernelRetryOptionsThreading:
427436
PACKAGE = "databricks.sql"
428437

429438
def test_retry_kwargs_threaded_into_kernel_client(self):
430-
import sys
431439
import types
432440

433441
pytest.importorskip(
@@ -442,6 +450,7 @@ def test_retry_kwargs_threaded_into_kernel_client(self):
442450
fake = types.ModuleType("databricks_sql_kernel")
443451
fake.KernelError = type("KernelError", (Exception,), {})
444452
fake.Session = MagicMock()
453+
_forget_kernel_client_module()
445454

446455
# Patch the kernel client class (imported lazily inside
447456
# _create_backend) and the provider builder; capture the kwargs
@@ -478,6 +487,54 @@ def test_retry_kwargs_threaded_into_kernel_client(self):
478487
conn.close()
479488

480489

490+
class TestKernelTelemetryOptionsThreading:
491+
"""The kernel path must forward telemetry options from connect()
492+
into ``KernelDatabricksClient`` so phase-7 PyO3 Session kwargs can
493+
be populated before the kernel opens its session."""
494+
495+
PACKAGE = "databricks.sql"
496+
497+
def test_telemetry_kwargs_threaded_into_kernel_client(self):
498+
import types
499+
500+
pytest.importorskip(
501+
"pyarrow",
502+
reason="kernel client module imports pyarrow at load",
503+
)
504+
505+
fake = types.ModuleType("databricks_sql_kernel")
506+
fake.KernelError = type("KernelError", (Exception,), {})
507+
fake.Session = MagicMock()
508+
_forget_kernel_client_module()
509+
510+
with patch.dict(sys.modules, {"databricks_sql_kernel": fake}), patch(
511+
"databricks.sql.backend.kernel.client.KernelDatabricksClient"
512+
) as mock_kernel_client, patch(
513+
"%s.session.get_python_sql_connector_auth_provider" % self.PACKAGE
514+
):
515+
instance = mock_kernel_client.return_value
516+
instance.open_session.return_value = SessionId(
517+
BackendType.SEA, "sess-id", None
518+
)
519+
520+
conn = databricks.sql.connect(
521+
server_hostname="foo",
522+
http_path="/sql/1.0/warehouses/abc",
523+
use_kernel=True,
524+
access_token="dapi-xyz",
525+
enable_telemetry=True,
526+
force_enable_telemetry=False,
527+
telemetry_batch_size=17,
528+
)
529+
try:
530+
_, kwargs = mock_kernel_client.call_args
531+
opts = kwargs["telemetry_options"]
532+
assert opts["enable_telemetry"] is True
533+
assert opts["telemetry_batch_size"] == 17
534+
finally:
535+
conn.close()
536+
537+
481538
class TestKernelUserAgentForwarding:
482539
"""user_agent_entry must reach the kernel on the use_kernel path —
483540
session.py folds it into the composed User-Agent and includes it in
@@ -488,7 +545,6 @@ class TestKernelUserAgentForwarding:
488545
PACKAGE = "databricks.sql"
489546

490547
def test_user_agent_entry_reaches_kernel_client_http_headers(self):
491-
import sys
492548
import types
493549

494550
pytest.importorskip(
@@ -498,6 +554,7 @@ def test_user_agent_entry_reaches_kernel_client_http_headers(self):
498554
fake = types.ModuleType("databricks_sql_kernel")
499555
fake.KernelError = type("KernelError", (Exception,), {})
500556
fake.Session = MagicMock()
557+
_forget_kernel_client_module()
501558

502559
with patch.dict(sys.modules, {"databricks_sql_kernel": fake}), patch(
503560
"databricks.sql.backend.kernel.client.KernelDatabricksClient"

tests/unit/test_telemetry.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,68 @@ def test_token_federation_with_no_inner_provider(self):
270270
assert TelemetryHelper.get_auth_mechanism(fed) is None
271271
assert TelemetryHelper.get_auth_flow(fed) is None
272272

273+
@staticmethod
274+
def _kernel_telemetry_kwargs_for_test(options):
275+
import importlib
276+
import sys
277+
import types
278+
279+
pytest.importorskip(
280+
"pyarrow",
281+
reason="kernel client module imports pyarrow at load",
282+
)
283+
284+
fake = types.ModuleType("databricks_sql_kernel")
285+
fake.KernelError = type("KernelError", (Exception,), {})
286+
fake.Session = MagicMock()
287+
288+
sys.modules.pop("databricks.sql.backend.kernel.client", None)
289+
import databricks.sql.backend.kernel as kernel_pkg
290+
291+
if hasattr(kernel_pkg, "client"):
292+
delattr(kernel_pkg, "client")
293+
294+
try:
295+
with patch.dict(sys.modules, {"databricks_sql_kernel": fake}):
296+
kernel_client = importlib.import_module(
297+
"databricks.sql.backend.kernel.client"
298+
)
299+
return kernel_client._kernel_telemetry_kwargs(options)
300+
finally:
301+
sys.modules.pop("databricks.sql.backend.kernel.client", None)
302+
if hasattr(kernel_pkg, "client"):
303+
delattr(kernel_pkg, "client")
304+
305+
@pytest.mark.parametrize(
306+
("enable_telemetry", "expected_kernel_telemetry_enabled"),
307+
[
308+
(True, True),
309+
(False, False),
310+
],
311+
)
312+
def test_is_telemetry_enabled_returns_false_for_kernel(
313+
self,
314+
enable_telemetry,
315+
expected_kernel_telemetry_enabled,
316+
):
317+
connection = MagicMock()
318+
connection.session.use_kernel = True
319+
connection.force_enable_telemetry = True
320+
connection.enable_telemetry = enable_telemetry
321+
322+
assert TelemetryHelper.is_telemetry_enabled(connection) is False
323+
324+
kernel_kwargs = self._kernel_telemetry_kwargs_for_test(
325+
{
326+
"enable_telemetry": enable_telemetry,
327+
"force_enable_telemetry": True,
328+
}
329+
)
330+
assert (
331+
kernel_kwargs["telemetry_enabled"]
332+
is expected_kernel_telemetry_enabled
333+
)
334+
273335

274336
class TestTelemetryFactory:
275337
"""Tests for TelemetryClientFactory lifecycle and management."""

0 commit comments

Comments
 (0)