From 4fb76c6006df4c558e44c68ee41230abd75b6880 Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 14:58:57 +1000 Subject: [PATCH 01/11] feat(databricks): add platform access package --- semapact/platforms/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 semapact/platforms/__init__.py diff --git a/semapact/platforms/__init__.py b/semapact/platforms/__init__.py new file mode 100644 index 0000000..fffc453 --- /dev/null +++ b/semapact/platforms/__init__.py @@ -0,0 +1 @@ +"""External platform access boundaries for SemaPact.""" From 852d93429106412e344ee0117675f5a902940257 Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 14:59:05 +1000 Subject: [PATCH 02/11] feat(databricks): expose workspace client factory --- semapact/platforms/databricks/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 semapact/platforms/databricks/__init__.py diff --git a/semapact/platforms/databricks/__init__.py b/semapact/platforms/databricks/__init__.py new file mode 100644 index 0000000..2df926e --- /dev/null +++ b/semapact/platforms/databricks/__init__.py @@ -0,0 +1,5 @@ +"""Databricks platform-access helpers.""" + +from semapact.platforms.databricks.client import create_databricks_workspace_client + +__all__ = ["create_databricks_workspace_client"] From eb34a98d26192c6e5875992d202d8f2b31950c56 Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 14:59:19 +1000 Subject: [PATCH 03/11] feat(databricks): add authenticated workspace client boundary --- semapact/platforms/databricks/client.py | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 semapact/platforms/databricks/client.py diff --git a/semapact/platforms/databricks/client.py b/semapact/platforms/databricks/client.py new file mode 100644 index 0000000..3e67ef9 --- /dev/null +++ b/semapact/platforms/databricks/client.py @@ -0,0 +1,50 @@ +"""Databricks authenticated-client construction boundary. + +This module owns construction of an initialized Databricks ``WorkspaceClient``. +Downstream platform capabilities such as observation consume the resulting +client and remain independent from the authentication mechanism used to create +it. + +The initial supported path is explicit workspace URL + token. Additional +Databricks SDK authentication modes can be added here without changing +observation models or platform-operation signatures. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from databricks.sdk import WorkspaceClient + + +def create_databricks_workspace_client( + *, + workspace_url: str, + token: str, +) -> WorkspaceClient: + """Create an authenticated Databricks SDK client from explicit credentials. + + Credential resolution belongs to the caller/application boundary. This + function intentionally performs no logging and never includes the token in + validation errors. + """ + host = workspace_url.strip() if workspace_url else "" + if not host: + raise ValueError("workspace_url is required") + if not token or not token.strip(): + raise ValueError("token is required") + + workspace_client_cls = _load_workspace_client_class() + return workspace_client_cls(host=host.rstrip("/"), token=token) + + +def _load_workspace_client_class() -> Any: + """Load the optional Databricks SDK only when client construction is used.""" + try: + from databricks.sdk import WorkspaceClient + except ImportError as exc: + raise RuntimeError( + 'Databricks support requires the optional extra: pip install "semapact[databricks]"' + ) from exc + return WorkspaceClient From 0f216208b16db168a51afd18e0548f801a7d22eb Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 14:59:38 +1000 Subject: [PATCH 04/11] test(databricks): cover authenticated client construction --- tests/test_databricks_client.py | 74 +++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/test_databricks_client.py diff --git a/tests/test_databricks_client.py b/tests/test_databricks_client.py new file mode 100644 index 0000000..82ff013 --- /dev/null +++ b/tests/test_databricks_client.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import pytest + +from semapact.platforms.databricks import client as databricks_client +from semapact.platforms.databricks.client import create_databricks_workspace_client + + +class _FakeWorkspaceClient: + def __init__(self, **kwargs: str) -> None: + self.kwargs = kwargs + + +def test_create_databricks_workspace_client_uses_explicit_host_and_token( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + databricks_client, + "_load_workspace_client_class", + lambda: _FakeWorkspaceClient, + ) + + client = create_databricks_workspace_client( + workspace_url=" https://adb.example/ ", + token="secret-token", + ) + + assert isinstance(client, _FakeWorkspaceClient) + assert client.kwargs == { + "host": "https://adb.example", + "token": "secret-token", + } + + +@pytest.mark.parametrize( + ("workspace_url", "token", "message"), + [ + ("", "secret-token", "workspace_url is required"), + (" ", "secret-token", "workspace_url is required"), + ("https://adb.example", "", "token is required"), + ("https://adb.example", " ", "token is required"), + ], +) +def test_missing_credentials_fail_without_echoing_secret_values( + workspace_url: str, + token: str, + message: str, +) -> None: + with pytest.raises(ValueError) as exc_info: + create_databricks_workspace_client( + workspace_url=workspace_url, + token=token, + ) + + assert str(exc_info.value) == message + assert "secret-token" not in str(exc_info.value) + + +def test_workspace_client_factory_is_loaded_only_after_validation( + monkeypatch: pytest.MonkeyPatch, +) -> None: + loaded = False + + def _load() -> type[_FakeWorkspaceClient]: + nonlocal loaded + loaded = True + return _FakeWorkspaceClient + + monkeypatch.setattr(databricks_client, "_load_workspace_client_class", _load) + + with pytest.raises(ValueError, match="workspace_url is required"): + create_databricks_workspace_client(workspace_url="", token="secret-token") + + assert loaded is False From 56d58c6ba4a767f2fabc8254d5823e0bbfc4909b Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 15:00:14 +1000 Subject: [PATCH 05/11] ci(databricks): verify workspace client factory --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30abc7c..47e1ae9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,13 +66,13 @@ jobs: - name: Install SemaPact Databricks extra from project metadata run: uv pip install --python .venv/bin/python -e ".[databricks]" pytest - - name: Verify Databricks SDK and existing Unity import boundaries + - name: Verify Databricks SDK, client factory, and existing Unity import boundaries run: >- .venv/bin/python -c - "from databricks.sdk import WorkspaceClient; from databricks.sdk.service.catalog import TableInfo; from semapact.importers.unity_importer import import_unity_contract; print(WorkspaceClient.__name__, TableInfo.__name__, import_unity_contract.__name__)" + "from databricks.sdk import WorkspaceClient; from databricks.sdk.service.catalog import TableInfo; from semapact.importers.unity_importer import import_unity_contract; from semapact.platforms.databricks import create_databricks_workspace_client; client = create_databricks_workspace_client(workspace_url='https://adb.example/', token='test-token'); assert isinstance(client, WorkspaceClient); print(WorkspaceClient.__name__, TableInfo.__name__, import_unity_contract.__name__, type(client).__name__)" - - name: Run Databricks observation tests with official SDK installed - run: .venv/bin/python -m pytest tests/test_observation_databricks.py + - name: Run Databricks platform-boundary tests with official SDK installed + run: .venv/bin/python -m pytest tests/test_databricks_client.py tests/test_observation_databricks.py coverage: runs-on: ubuntu-latest From e23aa63c2fc9294b1c158af995731bf2c92a6ae2 Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 15:03:23 +1000 Subject: [PATCH 06/11] fix(databricks): make PAT auth explicit --- semapact/platforms/databricks/client.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/semapact/platforms/databricks/client.py b/semapact/platforms/databricks/client.py index 3e67ef9..7077504 100644 --- a/semapact/platforms/databricks/client.py +++ b/semapact/platforms/databricks/client.py @@ -5,7 +5,7 @@ client and remain independent from the authentication mechanism used to create it. -The initial supported path is explicit workspace URL + token. Additional +The initial supported path is explicit workspace URL + PAT token. Additional Databricks SDK authentication modes can be added here without changing observation models or platform-operation signatures. """ @@ -23,11 +23,12 @@ def create_databricks_workspace_client( workspace_url: str, token: str, ) -> WorkspaceClient: - """Create an authenticated Databricks SDK client from explicit credentials. + """Create a Databricks SDK client using explicit PAT authentication. Credential resolution belongs to the caller/application boundary. This function intentionally performs no logging and never includes the token in - validation errors. + validation errors. ``auth_type='pat'`` prevents the SDK from selecting a + different authentication provider based on ambient machine configuration. """ host = workspace_url.strip() if workspace_url else "" if not host: @@ -36,7 +37,11 @@ def create_databricks_workspace_client( raise ValueError("token is required") workspace_client_cls = _load_workspace_client_class() - return workspace_client_cls(host=host.rstrip("/"), token=token) + return workspace_client_cls( + host=host.rstrip("/"), + token=token, + auth_type="pat", + ) def _load_workspace_client_class() -> Any: From c5fc83624b712ffd2b0808a4b5e1f90930381508 Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 15:03:36 +1000 Subject: [PATCH 07/11] test(databricks): require explicit PAT auth type --- tests/test_databricks_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_databricks_client.py b/tests/test_databricks_client.py index 82ff013..70078f0 100644 --- a/tests/test_databricks_client.py +++ b/tests/test_databricks_client.py @@ -11,7 +11,7 @@ def __init__(self, **kwargs: str) -> None: self.kwargs = kwargs -def test_create_databricks_workspace_client_uses_explicit_host_and_token( +def test_create_databricks_workspace_client_uses_explicit_pat_auth( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr( @@ -29,6 +29,7 @@ def test_create_databricks_workspace_client_uses_explicit_host_and_token( assert client.kwargs == { "host": "https://adb.example", "token": "secret-token", + "auth_type": "pat", } From e082ba13acca50b74ebae9a1e49e44ddffe0845f Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 15:05:38 +1000 Subject: [PATCH 08/11] ci(databricks): keep SDK smoke offline --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47e1ae9..363cd0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: - name: Verify Databricks SDK, client factory, and existing Unity import boundaries run: >- .venv/bin/python -c - "from databricks.sdk import WorkspaceClient; from databricks.sdk.service.catalog import TableInfo; from semapact.importers.unity_importer import import_unity_contract; from semapact.platforms.databricks import create_databricks_workspace_client; client = create_databricks_workspace_client(workspace_url='https://adb.example/', token='test-token'); assert isinstance(client, WorkspaceClient); print(WorkspaceClient.__name__, TableInfo.__name__, import_unity_contract.__name__, type(client).__name__)" + "import inspect; from databricks.sdk import WorkspaceClient; from databricks.sdk.service.catalog import TableInfo; from semapact.importers.unity_importer import import_unity_contract; from semapact.platforms.databricks import create_databricks_workspace_client; params = inspect.signature(WorkspaceClient).parameters; assert {'host', 'token', 'auth_type'} <= set(params); print(WorkspaceClient.__name__, TableInfo.__name__, import_unity_contract.__name__, create_databricks_workspace_client.__name__)" - name: Run Databricks platform-boundary tests with official SDK installed run: .venv/bin/python -m pytest tests/test_databricks_client.py tests/test_observation_databricks.py From 494fbb3ea40c0232ca03a5ec36302006bbe387b2 Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 15:25:05 +1000 Subject: [PATCH 09/11] refactor(databricks): delegate auth resolution to sdk --- semapact/platforms/databricks/client.py | 53 +++++++++++++++---------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/semapact/platforms/databricks/client.py b/semapact/platforms/databricks/client.py index 7077504..913a71d 100644 --- a/semapact/platforms/databricks/client.py +++ b/semapact/platforms/databricks/client.py @@ -5,9 +5,9 @@ client and remain independent from the authentication mechanism used to create it. -The initial supported path is explicit workspace URL + PAT token. Additional -Databricks SDK authentication modes can be added here without changing -observation models or platform-operation signatures. +SemaPact forwards only the connection/authentication hints supplied by the +caller. The Databricks SDK remains responsible for selecting and validating the +authentication mechanism, including its default/unified authentication chain. """ from __future__ import annotations @@ -20,28 +20,41 @@ def create_databricks_workspace_client( *, - workspace_url: str, - token: str, + workspace_url: str | None = None, + token: str | None = None, + profile: str | None = None, ) -> WorkspaceClient: - """Create a Databricks SDK client using explicit PAT authentication. + """Create a Databricks SDK client from the auth hints the caller has. - Credential resolution belongs to the caller/application boundary. This - function intentionally performs no logging and never includes the token in - validation errors. ``auth_type='pat'`` prevents the SDK from selecting a - different authentication provider based on ambient machine configuration. + SemaPact does not choose an authentication provider. Non-empty values are + forwarded to ``WorkspaceClient`` and omitted values are left for the SDK to + resolve from its standard configuration/authentication chain. Calling this + function with no arguments is therefore equivalent to ``WorkspaceClient()``. + + The function performs no credential logging or serialization. """ - host = workspace_url.strip() if workspace_url else "" - if not host: - raise ValueError("workspace_url is required") - if not token or not token.strip(): - raise ValueError("token is required") + kwargs: dict[str, str] = {} + + host = _clean_optional(workspace_url) + if host: + kwargs["host"] = host.rstrip("/") + + if token and token.strip(): + kwargs["token"] = token + + selected_profile = _clean_optional(profile) + if selected_profile: + kwargs["profile"] = selected_profile workspace_client_cls = _load_workspace_client_class() - return workspace_client_cls( - host=host.rstrip("/"), - token=token, - auth_type="pat", - ) + return workspace_client_cls(**kwargs) + + +def _clean_optional(value: str | None) -> str | None: + if value is None: + return None + cleaned = value.strip() + return cleaned or None def _load_workspace_client_class() -> Any: From 244e0a2c131528fd786cd0905f274b477531e337 Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 15:25:18 +1000 Subject: [PATCH 10/11] test(databricks): cover sdk-owned auth resolution --- tests/test_databricks_client.py | 64 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/test_databricks_client.py b/tests/test_databricks_client.py index 70078f0..dfd3190 100644 --- a/tests/test_databricks_client.py +++ b/tests/test_databricks_client.py @@ -11,65 +11,65 @@ def __init__(self, **kwargs: str) -> None: self.kwargs = kwargs -def test_create_databricks_workspace_client_uses_explicit_pat_auth( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def _use_fake_workspace_client(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( databricks_client, "_load_workspace_client_class", lambda: _FakeWorkspaceClient, ) + +def test_create_databricks_workspace_client_forwards_available_hints( + monkeypatch: pytest.MonkeyPatch, +) -> None: + _use_fake_workspace_client(monkeypatch) + client = create_databricks_workspace_client( workspace_url=" https://adb.example/ ", token="secret-token", + profile=" prod ", ) assert isinstance(client, _FakeWorkspaceClient) assert client.kwargs == { "host": "https://adb.example", "token": "secret-token", - "auth_type": "pat", + "profile": "prod", } @pytest.mark.parametrize( - ("workspace_url", "token", "message"), + ("kwargs", "expected"), [ - ("", "secret-token", "workspace_url is required"), - (" ", "secret-token", "workspace_url is required"), - ("https://adb.example", "", "token is required"), - ("https://adb.example", " ", "token is required"), + ({}, {}), + ({"workspace_url": "https://adb.example/"}, {"host": "https://adb.example"}), + ({"token": "secret-token"}, {"token": "secret-token"}), + ({"profile": "prod"}, {"profile": "prod"}), ], ) -def test_missing_credentials_fail_without_echoing_secret_values( - workspace_url: str, - token: str, - message: str, +def test_create_databricks_workspace_client_leaves_missing_auth_to_sdk( + monkeypatch: pytest.MonkeyPatch, + kwargs: dict[str, str], + expected: dict[str, str], ) -> None: - with pytest.raises(ValueError) as exc_info: - create_databricks_workspace_client( - workspace_url=workspace_url, - token=token, - ) + _use_fake_workspace_client(monkeypatch) - assert str(exc_info.value) == message - assert "secret-token" not in str(exc_info.value) + client = create_databricks_workspace_client(**kwargs) + + assert isinstance(client, _FakeWorkspaceClient) + assert client.kwargs == expected -def test_workspace_client_factory_is_loaded_only_after_validation( +def test_create_databricks_workspace_client_omits_blank_hints( monkeypatch: pytest.MonkeyPatch, ) -> None: - loaded = False - - def _load() -> type[_FakeWorkspaceClient]: - nonlocal loaded - loaded = True - return _FakeWorkspaceClient - - monkeypatch.setattr(databricks_client, "_load_workspace_client_class", _load) + _use_fake_workspace_client(monkeypatch) - with pytest.raises(ValueError, match="workspace_url is required"): - create_databricks_workspace_client(workspace_url="", token="secret-token") + client = create_databricks_workspace_client( + workspace_url=" ", + token=" ", + profile=" ", + ) - assert loaded is False + assert isinstance(client, _FakeWorkspaceClient) + assert client.kwargs == {} From cacf9de38ec8c86e8e9a72716deb8013ccf5565b Mon Sep 17 00:00:00 2001 From: Elliot Sun Date: Sun, 30 Aug 2026 15:25:33 +1000 Subject: [PATCH 11/11] ci(databricks): verify sdk auth hint boundary --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 363cd0f..1592bd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: - name: Verify Databricks SDK, client factory, and existing Unity import boundaries run: >- .venv/bin/python -c - "import inspect; from databricks.sdk import WorkspaceClient; from databricks.sdk.service.catalog import TableInfo; from semapact.importers.unity_importer import import_unity_contract; from semapact.platforms.databricks import create_databricks_workspace_client; params = inspect.signature(WorkspaceClient).parameters; assert {'host', 'token', 'auth_type'} <= set(params); print(WorkspaceClient.__name__, TableInfo.__name__, import_unity_contract.__name__, create_databricks_workspace_client.__name__)" + "import inspect; from databricks.sdk import WorkspaceClient; from databricks.sdk.service.catalog import TableInfo; from semapact.importers.unity_importer import import_unity_contract; from semapact.platforms.databricks import create_databricks_workspace_client; params = inspect.signature(WorkspaceClient).parameters; assert {'host', 'token', 'profile'} <= set(params); print(WorkspaceClient.__name__, TableInfo.__name__, import_unity_contract.__name__, create_databricks_workspace_client.__name__)" - name: Run Databricks platform-boundary tests with official SDK installed run: .venv/bin/python -m pytest tests/test_databricks_client.py tests/test_observation_databricks.py