Skip to content

Commit f48e102

Browse files
committed
Add HostType to HostMetadata
Signed-off-by: Tanmay Rustagi <tanmay.rustagi@databricks.com>
1 parent 165b3ec commit f48e102

4 files changed

Lines changed: 144 additions & 0 deletions

File tree

databricks/sdk/client_types.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from enum import Enum
2+
from typing import Optional
23

34

45
class HostType(Enum):
@@ -8,6 +9,24 @@ class HostType(Enum):
89
WORKSPACE = "workspace"
910
UNIFIED = "unified"
1011

12+
@staticmethod
13+
def from_api_value(value: str) -> Optional["HostType"]:
14+
"""Normalize a host_type string from the API to a HostType enum value.
15+
16+
Maps "workspace" -> WORKSPACE, "account" -> ACCOUNTS, "unified" -> UNIFIED.
17+
Returns None for unrecognized or empty values.
18+
"""
19+
if not value:
20+
return None
21+
normalized = value.lower()
22+
if normalized == "workspace":
23+
return HostType.WORKSPACE
24+
if normalized == "account":
25+
return HostType.ACCOUNTS
26+
if normalized == "unified":
27+
return HostType.UNIFIED
28+
return None
29+
1130

1231
class ClientType(Enum):
1332
"""Enum representing the type of client configuration."""

databricks/sdk/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ def __init__(
294294
self._header_factory = None
295295
self._inner = {}
296296
self._user_agent_other_info = []
297+
self._resolved_host_type = None
297298
self._custom_headers = custom_headers or {}
298299
if credentials_strategy and credentials_provider:
299300
raise ValueError("When providing `credentials_strategy` field, `credential_provider` cannot be specified.")
@@ -665,6 +666,11 @@ def _resolve_host_metadata(self) -> None:
665666
if not self.cloud and meta.cloud:
666667
logger.debug(f"Resolved cloud from host metadata: {meta.cloud.value}")
667668
self.cloud = meta.cloud
669+
if self._resolved_host_type is None and meta.host_type:
670+
resolved = HostType.from_api_value(meta.host_type)
671+
if resolved is not None:
672+
logger.debug(f"Resolved host_type from host metadata: {meta.host_type}")
673+
self._resolved_host_type = resolved
668674
# Account hosts use account_id as the OIDC token audience instead of the token endpoint.
669675
# This is a special case: when the metadata has no workspace_id, the host is acting as an
670676
# account-level endpoint and the audience must be scoped to the account.

databricks/sdk/oauth.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ class HostMetadata:
448448
account_id: Optional[str] = None
449449
workspace_id: Optional[str] = None
450450
cloud: Optional[Cloud] = None
451+
host_type: Optional[str] = None
451452

452453
@staticmethod
453454
def from_dict(d: dict) -> "HostMetadata":
@@ -456,6 +457,7 @@ def from_dict(d: dict) -> "HostMetadata":
456457
account_id=d.get("account_id"),
457458
workspace_id=d.get("workspace_id"),
458459
cloud=Cloud.parse(d.get("cloud", "")),
460+
host_type=d.get("host_type"),
459461
)
460462

461463
def as_dict(self) -> dict:
@@ -464,6 +466,7 @@ def as_dict(self) -> dict:
464466
"account_id": self.account_id,
465467
"workspace_id": self.workspace_id,
466468
"cloud": self.cloud.value if self.cloud else None,
469+
"host_type": self.host_type,
467470
}
468471

469472

tests/test_config.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,3 +981,119 @@ def test_resolve_host_metadata_does_not_overwrite_token_audience(mocker):
981981
token_audience="custom-audience",
982982
)
983983
assert config.token_audience == "custom-audience"
984+
985+
986+
# ---------------------------------------------------------------------------
987+
# HostType.from_api_value tests
988+
# ---------------------------------------------------------------------------
989+
990+
991+
@pytest.mark.parametrize(
992+
"api_value,expected",
993+
[
994+
("workspace", HostType.WORKSPACE),
995+
("Workspace", HostType.WORKSPACE),
996+
("WORKSPACE", HostType.WORKSPACE),
997+
("account", HostType.ACCOUNTS),
998+
("Account", HostType.ACCOUNTS),
999+
("ACCOUNT", HostType.ACCOUNTS),
1000+
("unified", HostType.UNIFIED),
1001+
("Unified", HostType.UNIFIED),
1002+
("UNIFIED", HostType.UNIFIED),
1003+
("unknown", None),
1004+
("", None),
1005+
(None, None),
1006+
],
1007+
)
1008+
def test_host_type_from_api_value(api_value, expected):
1009+
assert HostType.from_api_value(api_value) == expected
1010+
1011+
1012+
# ---------------------------------------------------------------------------
1013+
# HostMetadata.from_dict with host_type field
1014+
# ---------------------------------------------------------------------------
1015+
1016+
1017+
def test_host_metadata_from_dict_with_host_type():
1018+
"""HostMetadata.from_dict parses the host_type field."""
1019+
d = {
1020+
"oidc_endpoint": "https://host/oidc",
1021+
"account_id": "acc-1",
1022+
"host_type": "workspace",
1023+
}
1024+
meta = HostMetadata.from_dict(d)
1025+
assert meta.host_type == "workspace"
1026+
1027+
1028+
def test_host_metadata_from_dict_without_host_type():
1029+
"""HostMetadata.from_dict returns None for missing host_type."""
1030+
d = {"oidc_endpoint": "https://host/oidc"}
1031+
meta = HostMetadata.from_dict(d)
1032+
assert meta.host_type is None
1033+
1034+
1035+
# ---------------------------------------------------------------------------
1036+
# _resolve_host_metadata populates _resolved_host_type
1037+
# ---------------------------------------------------------------------------
1038+
1039+
1040+
def test_resolve_host_metadata_populates_resolved_host_type(mocker):
1041+
"""_resolved_host_type is populated from metadata host_type."""
1042+
mocker.patch(
1043+
"databricks.sdk.config.get_host_metadata",
1044+
return_value=HostMetadata.from_dict(
1045+
{
1046+
"oidc_endpoint": f"{_DUMMY_WS_HOST}/oidc",
1047+
"host_type": "unified",
1048+
}
1049+
),
1050+
)
1051+
config = Config(host=_DUMMY_WS_HOST, token="t")
1052+
assert config._resolved_host_type == HostType.UNIFIED
1053+
1054+
1055+
def test_resolve_host_metadata_does_not_overwrite_existing_resolved_host_type(mocker):
1056+
"""An already-set _resolved_host_type is not overwritten by metadata."""
1057+
mocker.patch(
1058+
"databricks.sdk.config.get_host_metadata",
1059+
return_value=HostMetadata.from_dict(
1060+
{
1061+
"oidc_endpoint": f"{_DUMMY_WS_HOST}/oidc",
1062+
"host_type": "account",
1063+
}
1064+
),
1065+
)
1066+
config = Config(host=_DUMMY_WS_HOST, token="t")
1067+
# Manually set resolved host type then re-resolve
1068+
config._resolved_host_type = HostType.UNIFIED
1069+
config._resolve_host_metadata()
1070+
assert config._resolved_host_type == HostType.UNIFIED
1071+
1072+
1073+
def test_resolve_host_metadata_resolved_host_type_none_when_missing(mocker):
1074+
"""_resolved_host_type stays None when metadata has no host_type."""
1075+
mocker.patch(
1076+
"databricks.sdk.config.get_host_metadata",
1077+
return_value=HostMetadata.from_dict(
1078+
{
1079+
"oidc_endpoint": f"{_DUMMY_WS_HOST}/oidc",
1080+
}
1081+
),
1082+
)
1083+
config = Config(host=_DUMMY_WS_HOST, token="t")
1084+
assert config._resolved_host_type is None
1085+
1086+
1087+
def test_resolve_host_metadata_resolved_host_type_unknown_string(mocker):
1088+
"""_resolved_host_type stays None for unrecognized host_type strings."""
1089+
mocker.patch(
1090+
"databricks.sdk.config.get_host_metadata",
1091+
return_value=HostMetadata.from_dict(
1092+
{
1093+
"oidc_endpoint": f"{_DUMMY_WS_HOST}/oidc",
1094+
"host_type": "some_future_type",
1095+
}
1096+
),
1097+
)
1098+
config = Config(host=_DUMMY_WS_HOST, token="t")
1099+
assert config._resolved_host_type is None

0 commit comments

Comments
 (0)