Skip to content

Commit b5adfc6

Browse files
authored
Merge branch 'main' into main
2 parents 4bee886 + e7d296b commit b5adfc6

124 files changed

Lines changed: 8558 additions & 22405 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codegen/_openapi_sha

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
033bcb9242b006001e2cf3956896711681de1a8c
1+
69902d1abe35bd9e78e0231927bf14d11b383a16

.release_metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"timestamp": "2025-06-12 19:21:54+0000"
2+
"timestamp": "2025-07-17 11:13:08+0000"
33
}

CHANGELOG.md

Lines changed: 124 additions & 0 deletions
Large diffs are not rendered by default.

NEXT_CHANGELOG.md

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
# NEXT CHANGELOG
22

3-
## Release v0.58.0
3+
## Release v0.60.0
44

55
### New Features and Improvements
66

7+
* Added headers to HttpRequestResponse in OpenAI client.
8+
79
### Bug Fixes
810

11+
- Correctly issue in OIDC implementation that prevented the use of the feature (see #994).
12+
- Fix a reported issue where `FilesExt` fails to retry if it receives certain status code from server.
13+
914
### Documentation
1015

1116
### Internal Changes
1217

18+
- Refactor unit tests for `FilesExt` to improve its readability.
19+
1320
### API Changes
14-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.ClusterAttributes`.
15-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.ClusterDetails`.
16-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.ClusterSpec`.
17-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.CreateCluster`.
18-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.CreateInstancePool`.
19-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.EditCluster`.
20-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.EditInstancePool`.
21-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.GetInstancePool`.
22-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.InstancePoolAndStats`.
23-
* Added `remote_disk_throughput` and `total_initial_remote_disk_size` fields for `databricks.sdk.service.compute.UpdateClusterResource`.
24-
* Added `r` enum value for `databricks.sdk.service.compute.Language`.
25-
* Added `continuous` and `continuous_restart` enum values for `databricks.sdk.service.jobs.TriggerType`.

databricks/sdk/__init__.py

Lines changed: 37 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

databricks/sdk/credentials_provider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def file_oidc(cfg) -> Optional[CredentialsProvider]:
331331
# that provides a Databricks token from an IdTokenSource.
332332
def _oidc_credentials_provider(cfg, id_token_source: oidc.IdTokenSource) -> Optional[CredentialsProvider]:
333333
try:
334-
id_token = id_token_source.id_token()
334+
id_token_source.id_token() # validate the id_token_source
335335
except Exception as e:
336336
logger.debug(f"Failed to get OIDC token: {e}")
337337
return None
@@ -341,7 +341,7 @@ def _oidc_credentials_provider(cfg, id_token_source: oidc.IdTokenSource) -> Opti
341341
token_endpoint=cfg.oidc_endpoints.token_endpoint,
342342
client_id=cfg.client_id,
343343
account_id=cfg.account_id,
344-
id_token=id_token,
344+
id_token_source=id_token_source,
345345
disable_async=cfg.disable_async_token_refresh,
346346
)
347347

databricks/sdk/mixins/files.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from .._property import _cached_property
2929
from ..config import Config
3030
from ..errors import AlreadyExists, NotFound
31-
from ..errors.customizer import _RetryAfterCustomizer
3231
from ..errors.mapper import _error_mapper
3332
from ..retries import retried
3433
from ..service import files
@@ -577,6 +576,27 @@ def __repr__(self) -> str:
577576
return f"<_DbfsPath {self._path}>"
578577

579578

579+
class _RetryableException(Exception):
580+
"""Base class for retryable exceptions in DBFS operations."""
581+
582+
def __init__(self, message: str, http_status_code: int):
583+
super().__init__()
584+
self.message = message
585+
self.http_status_code = http_status_code
586+
587+
def __str__(self) -> str:
588+
return f"{self.message} (HTTP Status: {self.http_status_code})"
589+
590+
@staticmethod
591+
def make_error(response: requests.Response) -> "_RetryableException":
592+
"""Map the response to a retryable exception."""
593+
594+
return _RetryableException(
595+
message=response.text,
596+
http_status_code=response.status_code,
597+
)
598+
599+
580600
class DbfsExt(files.DbfsAPI):
581601
__doc__ = files.DbfsAPI.__doc__
582602

@@ -885,7 +905,7 @@ def perform():
885905
timeout=self._config.multipart_upload_single_chunk_upload_timeout_seconds,
886906
)
887907

888-
upload_response = self._retry_idempotent_operation(perform, rewind)
908+
upload_response = self._retry_cloud_idempotent_operation(perform, rewind)
889909

890910
if upload_response.status_code in (200, 201):
891911
# Chunk upload successful
@@ -1097,7 +1117,7 @@ def perform():
10971117
)
10981118

10991119
try:
1100-
return self._retry_idempotent_operation(perform)
1120+
return self._retry_cloud_idempotent_operation(perform)
11011121
except RequestException:
11021122
_LOG.warning("Failed to retrieve upload status")
11031123
return None
@@ -1116,7 +1136,7 @@ def perform():
11161136
# a 503 or 500 response, then you need to resume the interrupted upload from where it left off.
11171137

11181138
# Let's follow that for all potentially retryable status codes.
1119-
# Together with the catch block below we replicate the logic in _retry_idempotent_operation().
1139+
# Together with the catch block below we replicate the logic in _retry_databricks_idempotent_operation().
11201140
if upload_response.status_code in self._RETRYABLE_STATUS_CODES:
11211141
if retry_count < self._config.multipart_upload_max_retries:
11221142
retry_count += 1
@@ -1243,7 +1263,7 @@ def perform():
12431263
timeout=self._config.multipart_upload_single_chunk_upload_timeout_seconds,
12441264
)
12451265

1246-
abort_response = self._retry_idempotent_operation(perform)
1266+
abort_response = self._retry_cloud_idempotent_operation(perform)
12471267

12481268
if abort_response.status_code not in (200, 201):
12491269
raise ValueError(abort_response)
@@ -1265,7 +1285,7 @@ def perform():
12651285
timeout=self._config.multipart_upload_single_chunk_upload_timeout_seconds,
12661286
)
12671287

1268-
abort_response = self._retry_idempotent_operation(perform)
1288+
abort_response = self._retry_cloud_idempotent_operation(perform)
12691289

12701290
if abort_response.status_code not in (200, 201):
12711291
raise ValueError(abort_response)
@@ -1283,31 +1303,39 @@ def _create_cloud_provider_session(self):
12831303
session.mount("http://", http_adapter)
12841304
return session
12851305

1286-
def _retry_idempotent_operation(
1306+
def _retry_cloud_idempotent_operation(
12871307
self, operation: Callable[[], requests.Response], before_retry: Callable = None
12881308
) -> requests.Response:
1289-
"""Perform given idempotent operation with necessary retries. Since operation is idempotent it's
1290-
safe to retry it for response codes where server state might have changed.
1309+
"""Perform given idempotent operation with necessary retries for requests to non Databricks APIs.
1310+
For cloud APIs, we will retry on network errors and on server response codes.
1311+
Since operation is idempotent it's safe to retry it for response codes where server state might have changed.
12911312
"""
12921313

1293-
def delegate():
1314+
def delegate() -> requests.Response:
12941315
response = operation()
12951316
if response.status_code in self._RETRYABLE_STATUS_CODES:
1296-
attrs = {}
1297-
# this will assign "retry_after_secs" to the attrs, essentially making exception look retryable
1298-
_RetryAfterCustomizer().customize_error(response, attrs)
1299-
raise _error_mapper(response, attrs)
1317+
raise _RetryableException.make_error(response)
13001318
else:
13011319
return response
13021320

1321+
def extended_is_retryable(e: BaseException) -> Optional[str]:
1322+
retry_reason_from_base = _BaseClient._is_retryable(e)
1323+
if retry_reason_from_base is not None:
1324+
return retry_reason_from_base
1325+
1326+
if isinstance(e, _RetryableException):
1327+
# this is a retriable exception, but not a network error
1328+
return f"retryable exception (status_code:{e.http_status_code})"
1329+
return None
1330+
13031331
# following _BaseClient timeout
13041332
retry_timeout_seconds = self._config.retry_timeout_seconds or 300
13051333

13061334
return retried(
13071335
timeout=timedelta(seconds=retry_timeout_seconds),
13081336
# also retry on network errors (connection error, connection timeout)
13091337
# where we believe request didn't reach the server
1310-
is_retryable=_BaseClient._is_retryable,
1338+
is_retryable=extended_is_retryable,
13111339
before_retry=before_retry,
13121340
)(delegate)()
13131341

databricks/sdk/mixins/open_ai_client.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from requests import Response
55

66
from databricks.sdk.service.serving import (ExternalFunctionRequestHttpMethod,
7+
HttpRequestResponse,
78
ServingEndpointsAPI)
89

910

@@ -88,15 +89,30 @@ def http_request(
8889
"""
8990
response = Response()
9091
response.status_code = 200
91-
server_response = super().http_request(
92-
connection_name=conn,
93-
method=method,
94-
path=path,
95-
headers=js.dumps(headers) if headers is not None else None,
96-
json=js.dumps(json) if json is not None else None,
97-
params=js.dumps(params) if params is not None else None,
92+
93+
# We currently don't call super.http_request because we need to pass in response_headers
94+
# This is a temporary fix to get the headers we need for the MCP session id
95+
# TODO: Remove this once we have a better way to get back the response headers
96+
headers_to_capture = ["mcp-session-id"]
97+
res = self._api.do(
98+
"POST",
99+
"/api/2.0/external-function",
100+
body={
101+
"connection_name": conn,
102+
"method": method.value,
103+
"path": path,
104+
"headers": js.dumps(headers) if headers is not None else None,
105+
"json": js.dumps(json) if json is not None else None,
106+
"params": js.dumps(params) if params is not None else None,
107+
},
108+
headers={"Accept": "text/plain", "Content-Type": "application/json"},
109+
raw=True,
110+
response_headers=headers_to_capture,
98111
)
99112

113+
# Create HttpRequestResponse from the raw response
114+
server_response = HttpRequestResponse.from_dict(res)
115+
100116
# Read the content from the HttpRequestResponse object
101117
if hasattr(server_response, "contents") and hasattr(server_response.contents, "read"):
102118
raw_content = server_response.contents.read() # Read the bytes
@@ -109,4 +125,9 @@ def http_request(
109125
else:
110126
raise ValueError("Contents must be bytes.")
111127

128+
# Copy headers from raw response to Response
129+
for header_name in headers_to_capture:
130+
if header_name in res:
131+
response.headers[header_name] = res[header_name]
132+
112133
return response

0 commit comments

Comments
 (0)