Skip to content

Commit 96b01a9

Browse files
committed
tests: validate WorkspaceClient.dbutils lazy-property behavior
Add four tests covering the cached_property contract: - ``dbutils`` is a ``functools.cached_property`` descriptor on ``WorkspaceClient``. - ``WorkspaceClient.__init__`` does not invoke ``_make_dbutils``. - The first ``ws.dbutils`` read invokes ``_make_dbutils`` exactly once; subsequent reads return the cached value without re-invoking. - Constructing ``WorkspaceClient`` on a faked Spark Connect runtime (whose ``dbruntime`` raises ``CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT`` on any namespace materialization) succeeds without importing ``databricks.sdk.runtime`` at all — the durable sidestep of databricks/dbt-databricks#1252.
1 parent 9744f91 commit 96b01a9

1 file changed

Lines changed: 65 additions & 0 deletions

File tree

tests/test_client.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import functools
2+
import sys
3+
import types
14
from unittest.mock import create_autospec
25

36
import pytest
@@ -15,3 +18,65 @@ def test_autospec_fails_on_setting_unknown_property():
1518
w = create_autospec(WorkspaceClient, spec_set=True)
1619
with pytest.raises(AttributeError):
1720
w.bar = 1
21+
22+
23+
def test_dbutils_is_a_cached_property():
24+
"""``dbutils`` is a ``functools.cached_property`` so consumers that never read it
25+
pay no build cost — and, on Spark Connect runtimes, never touch the legacy
26+
``SparkContext`` path that ``databricks.sdk.runtime`` materializes on import."""
27+
descriptor = WorkspaceClient.__dict__["dbutils"]
28+
assert isinstance(descriptor, functools.cached_property)
29+
30+
31+
def test_workspace_client_init_does_not_build_dbutils(config, mocker):
32+
"""Constructing a ``WorkspaceClient`` must not invoke ``_make_dbutils``."""
33+
spy = mocker.patch("databricks.sdk._make_dbutils")
34+
35+
WorkspaceClient(config=config)
36+
37+
spy.assert_not_called()
38+
39+
40+
def test_dbutils_first_access_builds_exactly_once(config, mocker):
41+
"""First read of ``.dbutils`` calls ``_make_dbutils`` once; subsequent reads
42+
return the cached value without re-invoking."""
43+
sentinel = object()
44+
spy = mocker.patch("databricks.sdk._make_dbutils", return_value=sentinel)
45+
ws = WorkspaceClient(config=config)
46+
47+
first = ws.dbutils
48+
assert spy.call_count == 1
49+
assert first is sentinel
50+
51+
second = ws.dbutils
52+
assert spy.call_count == 1 # still 1 — cached_property short-circuits via __dict__
53+
assert second is sentinel
54+
55+
56+
def test_workspace_client_constructs_on_spark_connect_without_touching_runtime(monkeypatch, config):
57+
"""End-to-end Layer 2 win: with the lazy property, ``WorkspaceClient(config=...)``
58+
on a Spark Connect cluster succeeds without ever importing
59+
``databricks.sdk.runtime`` — so the legacy ``SparkContext`` materialization that
60+
raises ``CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT`` is never even attempted.
61+
62+
Faked ``dbruntime`` raises on any namespace materialization; if anything during
63+
construction triggered ``databricks.sdk.runtime``'s import, this test would crash.
64+
"""
65+
66+
class _Initializer:
67+
@staticmethod
68+
def getOrCreate():
69+
raise RuntimeError(
70+
"[CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT] Calls to SparkContext are not "
71+
"supported on a Spark Connect cluster."
72+
)
73+
74+
fake = types.ModuleType("dbruntime")
75+
fake.UserNamespaceInitializer = _Initializer
76+
monkeypatch.setitem(sys.modules, "dbruntime", fake)
77+
monkeypatch.delitem(sys.modules, "databricks.sdk.runtime", raising=False)
78+
79+
ws = WorkspaceClient(config=config)
80+
81+
assert ws is not None
82+
assert "databricks.sdk.runtime" not in sys.modules

0 commit comments

Comments
 (0)