Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
### Internal Changes
* Replace the async-disabling mechanism on token refresh failure with a 1-minute retry backoff. Previously, a single failed async refresh would disable proactive token renewal until the token expired. Now, the SDK waits a short cooldown period and retries, improving resilience to transient errors.
* Extract `_resolve_profile` to simplify config file loading and improve `__settings__` error messages.
* Add `host_type` to `HostMetadata` and `HostType.from_api_value()` for normalizing host type strings from the discovery endpoint.

### API Changes
* Add `create_catalog()`, `create_synced_table()`, `delete_catalog()`, `delete_synced_table()`, `get_catalog()` and `get_synced_table()` methods for [w.postgres](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/postgres/postgres.html) workspace-level service.
Expand Down
19 changes: 19 additions & 0 deletions databricks/sdk/client_types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import Enum
from typing import Optional


class HostType(Enum):
Expand All @@ -8,6 +9,24 @@ class HostType(Enum):
WORKSPACE = "workspace"
UNIFIED = "unified"

@staticmethod
def from_api_value(value: str) -> Optional["HostType"]:
"""Normalize a host_type string from the API to a HostType enum value.

Maps "workspace" -> WORKSPACE, "account" -> ACCOUNTS, "unified" -> UNIFIED.
Returns None for unrecognized or empty values.
"""
if not value:
return None
normalized = value.lower()
if normalized == "workspace":
return HostType.WORKSPACE
if normalized == "account":
return HostType.ACCOUNTS
if normalized == "unified":
return HostType.UNIFIED
return None


class ClientType(Enum):
"""Enum representing the type of client configuration."""
Expand Down
22 changes: 15 additions & 7 deletions databricks/sdk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ def __init__(
self._header_factory = None
self._inner = {}
self._user_agent_other_info = []
self._resolved_host_type = None
self._custom_headers = custom_headers or {}
if credentials_strategy and credentials_provider:
raise ValueError("When providing `credentials_strategy` field, `credential_provider` cannot be specified.")
Expand Down Expand Up @@ -420,14 +421,14 @@ def is_aws(self) -> bool:

@property
def host_type(self) -> HostType:
"""
[DEPRECATED]
Host type and client type are deprecated. Some hosts can now support both workspace and account APIs.
This method returns the HostType based on the host pattern, which is not accurate.
For example, a unified host can support both workspace and account APIs, but WORKSPACE is returned.
"""Returns the type of host that the client is configured for.

This method still returns the correct value for legacy hosts which only support either workspace or account APIs.
When available, uses the host type resolved from the /.well-known/databricks-config
discovery endpoint. Falls back to URL-based pattern matching when metadata is unavailable.
"""
if self._resolved_host_type is not None:
return self._resolved_host_type

if not self.host:
return HostType.WORKSPACE

Expand Down Expand Up @@ -665,10 +666,17 @@ def _resolve_host_metadata(self) -> None:
if not self.cloud and meta.cloud:
logger.debug(f"Resolved cloud from host metadata: {meta.cloud.value}")
self.cloud = meta.cloud
if self._resolved_host_type is None and meta.host_type:
resolved = HostType.from_api_value(meta.host_type)
if resolved is not None:
logger.debug(f"Resolved host_type from host metadata: {meta.host_type}")
self._resolved_host_type = resolved
if not self.token_audience and meta.default_oidc_audience:
logger.debug(f"Resolved token_audience from host metadata default_oidc_audience: {meta.default_oidc_audience}")
self.token_audience = meta.default_oidc_audience
# Account hosts use account_id as the OIDC token audience instead of the token endpoint.
# This is a special case: when the metadata has no workspace_id, the host is acting as an
# account-level endpoint and the audience must be scoped to the account.
# TODO: Add explicit audience to the metadata discovery endpoint.
if not self.token_audience and not meta.workspace_id and self.account_id:
logger.debug(f"Setting token_audience to account_id for account host: {self.account_id}")
self.token_audience = self.account_id
Expand Down
6 changes: 6 additions & 0 deletions databricks/sdk/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ class HostMetadata:
account_id: Optional[str] = None
workspace_id: Optional[str] = None
cloud: Optional[Cloud] = None
host_type: Optional[str] = None
default_oidc_audience: Optional[str] = None

@staticmethod
def from_dict(d: dict) -> "HostMetadata":
Expand All @@ -456,6 +458,8 @@ def from_dict(d: dict) -> "HostMetadata":
account_id=d.get("account_id"),
workspace_id=d.get("workspace_id"),
cloud=Cloud.parse(d.get("cloud", "")),
host_type=d.get("host_type"),
default_oidc_audience=d.get("default_oidc_audience"),
)

def as_dict(self) -> dict:
Expand All @@ -464,6 +468,8 @@ def as_dict(self) -> dict:
"account_id": self.account_id,
"workspace_id": self.workspace_id,
"cloud": self.cloud.value if self.cloud else None,
"host_type": self.host_type,
"default_oidc_audience": self.default_oidc_audience,
}


Expand Down
Loading
Loading