|
1 | | -"""Tests for the import-time behavior of the ``databricks.sdk.runtime`` package.""" |
| 1 | +"""Tests for the import-time behavior of ``databricks.sdk.runtime``.""" |
2 | 2 |
|
3 | 3 | import importlib |
4 | 4 | import sys |
5 | 5 | import types |
6 | 6 |
|
7 | 7 | import pytest |
8 | 8 |
|
| 9 | +from databricks.sdk.dbutils import RemoteDbUtils |
9 | 10 |
|
10 | | -class _RemoteClientInitializer: |
11 | | - """Stand-in for ``dbruntime.UserNamespaceInitializer`` on a Spark Connect runtime. |
12 | 11 |
|
13 | | - On a shared-access-mode (Spark Connect) cluster, materializing the legacy user namespace |
14 | | - builds a ``SparkContext``, which is unavailable in remote clients and raises |
15 | | - ``CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT``. |
16 | | - """ |
17 | | - |
18 | | - @staticmethod |
19 | | - def getOrCreate(): |
20 | | - class _Namespace: |
21 | | - def get_namespace_globals(self): |
22 | | - raise RuntimeError( |
23 | | - "[CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT] Calls to SparkContext are not " |
24 | | - "supported on a Spark Connect cluster. Use spark instead." |
25 | | - ) |
| 12 | +@pytest.fixture |
| 13 | +def spark_connect_runtime(monkeypatch): |
| 14 | + """``dbruntime`` is importable, but materializing the legacy user namespace raises |
| 15 | + ``CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT`` — the Spark Connect failure mode.""" |
26 | 16 |
|
27 | | - return _Namespace() |
| 17 | + class _Initializer: |
| 18 | + @staticmethod |
| 19 | + def getOrCreate(): |
| 20 | + class _Namespace: |
| 21 | + def get_namespace_globals(self): |
| 22 | + raise RuntimeError( |
| 23 | + "[CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT] Calls to SparkContext are " |
| 24 | + "not supported on a Spark Connect cluster. Use spark instead." |
| 25 | + ) |
28 | 26 |
|
| 27 | + return _Namespace() |
29 | 28 |
|
30 | | -@pytest.fixture |
31 | | -def spark_connect_runtime(monkeypatch): |
32 | | - """Simulate a Spark Connect runtime: ``dbruntime`` is importable, but materializing the |
33 | | - legacy user namespace raises ``CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT``.""" |
34 | 29 | fake = types.ModuleType("dbruntime") |
35 | | - fake.UserNamespaceInitializer = _RemoteClientInitializer |
36 | | - had_dbruntime = "dbruntime" in sys.modules |
37 | | - saved_dbruntime = sys.modules.get("dbruntime") |
38 | | - sys.modules["dbruntime"] = fake |
39 | | - |
40 | | - # The remote fallback constructs ``RemoteDbUtils()``, which initializes a default ``Config``; |
41 | | - # give it hermetic PAT credentials so the fallback itself doesn't fail for unrelated auth |
42 | | - # reasons (see databricks-sdk-py#986). |
| 30 | + fake.UserNamespaceInitializer = _Initializer |
| 31 | + monkeypatch.setitem(sys.modules, "dbruntime", fake) |
| 32 | + |
| 33 | + # The remote fallback constructs ``RemoteDbUtils()``, which initializes a default |
| 34 | + # ``Config``; hermetic PAT credentials keep the fallback from failing for unrelated |
| 35 | + # auth reasons (see databricks-sdk-py#986). |
43 | 36 | monkeypatch.setenv("DATABRICKS_HOST", "https://test.cloud.databricks.com") |
44 | 37 | monkeypatch.setenv("DATABRICKS_TOKEN", "test-token") |
45 | 38 |
|
46 | | - saved_runtime = sys.modules.get("databricks.sdk.runtime") |
47 | | - try: |
48 | | - yield |
49 | | - finally: |
50 | | - if had_dbruntime: |
51 | | - sys.modules["dbruntime"] = saved_dbruntime |
52 | | - else: |
53 | | - sys.modules.pop("dbruntime", None) |
54 | | - # Restore ``databricks.sdk.runtime`` to its pre-test state. If it was loaded before this |
55 | | - # test, reload it cleanly while the PAT env is still set; otherwise drop it so it is |
56 | | - # re-imported lazily on next use. |
57 | | - if saved_runtime is None: |
58 | | - sys.modules.pop("databricks.sdk.runtime", None) |
59 | | - else: |
60 | | - importlib.reload(saved_runtime) |
61 | | - |
62 | | - |
63 | | -def test_runtime_import_survives_spark_connect_remote_client(spark_connect_runtime): |
64 | | - """Regression for dbt-databricks#1252: importing ``databricks.sdk.runtime`` on a Spark |
65 | | - Connect runtime must fall back to the remote implementation instead of raising.""" |
66 | | - import databricks.sdk.runtime as runtime |
| 39 | + # Re-execute ``databricks.sdk.runtime``'s module body with the fake ``dbruntime`` in |
| 40 | + # place, then restore on teardown by reloading once more without it. |
| 41 | + import databricks.sdk.runtime |
67 | 42 |
|
68 | | - importlib.reload(runtime) # re-execute the module body with the faked ``dbruntime`` present |
69 | | - |
70 | | - assert runtime.is_local_implementation is True |
71 | | - assert runtime.dbutils is not None |
| 43 | + importlib.reload(databricks.sdk.runtime) |
| 44 | + yield |
| 45 | + importlib.reload(databricks.sdk.runtime) |
72 | 46 |
|
73 | 47 |
|
74 | | -def test_workspace_client_constructs_on_spark_connect(spark_connect_runtime, config): |
75 | | - """End-to-end: constructing a ``WorkspaceClient`` on a Spark Connect runtime must not raise |
76 | | - ``CONTEXT_UNAVAILABLE_FOR_REMOTE_CLIENT`` (the actual dbt-databricks#1252 failure, since |
77 | | - ``WorkspaceClient.__init__`` eagerly builds dbutils via ``databricks.sdk.runtime``).""" |
| 48 | +def test_runtime_import_falls_back_on_spark_connect(spark_connect_runtime): |
| 49 | + """Regression for dbt-databricks#1252: import survives the namespace failure.""" |
78 | 50 | import databricks.sdk.runtime as runtime |
79 | 51 |
|
80 | | - importlib.reload(runtime) |
| 52 | + assert runtime.is_local_implementation is True |
| 53 | + assert isinstance(runtime.dbutils, RemoteDbUtils) |
| 54 | + |
81 | 55 |
|
| 56 | +def test_workspace_client_constructs_on_spark_connect(spark_connect_runtime, config): |
| 57 | + """Regression for dbt-databricks#1252: ``WorkspaceClient.__init__`` eagerly builds |
| 58 | + dbutils via ``databricks.sdk.runtime`` and must not raise on Spark Connect.""" |
82 | 59 | from databricks.sdk import WorkspaceClient |
83 | 60 |
|
84 | 61 | ws = WorkspaceClient(config=config) |
|
0 commit comments