databricks-sdk 0.59.0__py3-none-any.whl → 0.61.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ from databricks.sdk.mixins.files import DbfsExt, FilesExt
13
13
  from databricks.sdk.mixins.jobs import JobsExt
14
14
  from databricks.sdk.mixins.open_ai_client import ServingEndpointsExt
15
15
  from databricks.sdk.mixins.workspace import WorkspaceExt
16
- from databricks.sdk.service import aibuilder as pkg_aibuilder
16
+ from databricks.sdk.service import agentbricks as pkg_agentbricks
17
17
  from databricks.sdk.service import apps as pkg_apps
18
18
  from databricks.sdk.service import billing as pkg_billing
19
19
  from databricks.sdk.service import catalog as pkg_catalog
@@ -36,7 +36,7 @@ from databricks.sdk.service import sharing as pkg_sharing
36
36
  from databricks.sdk.service import sql as pkg_sql
37
37
  from databricks.sdk.service import vectorsearch as pkg_vectorsearch
38
38
  from databricks.sdk.service import workspace as pkg_workspace
39
- from databricks.sdk.service.aibuilder import AiBuilderAPI
39
+ from databricks.sdk.service.agentbricks import AgentBricksAPI
40
40
  from databricks.sdk.service.apps import AppsAPI
41
41
  from databricks.sdk.service.billing import (BillableUsageAPI, BudgetPolicyAPI,
42
42
  BudgetsAPI, LogDeliveryAPI,
@@ -59,7 +59,9 @@ from databricks.sdk.service.catalog import (AccountMetastoreAssignmentsAPI,
59
59
  TableConstraintsAPI, TablesAPI,
60
60
  TemporaryTableCredentialsAPI,
61
61
  VolumesAPI, WorkspaceBindingsAPI)
62
- from databricks.sdk.service.cleanrooms import (CleanRoomAssetsAPI,
62
+ from databricks.sdk.service.cleanrooms import (CleanRoomAssetRevisionsAPI,
63
+ CleanRoomAssetsAPI,
64
+ CleanRoomAutoApprovalRulesAPI,
63
65
  CleanRoomsAPI,
64
66
  CleanRoomTaskRunsAPI)
65
67
  from databricks.sdk.service.compute import (ClusterPoliciesAPI, ClustersAPI,
@@ -240,14 +242,16 @@ class WorkspaceClient:
240
242
  serving_endpoints = ServingEndpointsExt(self._api_client)
241
243
  self._access_control = pkg_iam.AccessControlAPI(self._api_client)
242
244
  self._account_access_control_proxy = pkg_iam.AccountAccessControlProxyAPI(self._api_client)
243
- self._ai_builder = pkg_aibuilder.AiBuilderAPI(self._api_client)
245
+ self._agent_bricks = pkg_agentbricks.AgentBricksAPI(self._api_client)
244
246
  self._alerts = pkg_sql.AlertsAPI(self._api_client)
245
247
  self._alerts_legacy = pkg_sql.AlertsLegacyAPI(self._api_client)
246
248
  self._alerts_v2 = pkg_sql.AlertsV2API(self._api_client)
247
249
  self._apps = pkg_apps.AppsAPI(self._api_client)
248
250
  self._artifact_allowlists = pkg_catalog.ArtifactAllowlistsAPI(self._api_client)
249
251
  self._catalogs = pkg_catalog.CatalogsAPI(self._api_client)
252
+ self._clean_room_asset_revisions = pkg_cleanrooms.CleanRoomAssetRevisionsAPI(self._api_client)
250
253
  self._clean_room_assets = pkg_cleanrooms.CleanRoomAssetsAPI(self._api_client)
254
+ self._clean_room_auto_approval_rules = pkg_cleanrooms.CleanRoomAutoApprovalRulesAPI(self._api_client)
251
255
  self._clean_room_task_runs = pkg_cleanrooms.CleanRoomTaskRunsAPI(self._api_client)
252
256
  self._clean_rooms = pkg_cleanrooms.CleanRoomsAPI(self._api_client)
253
257
  self._cluster_policies = pkg_compute.ClusterPoliciesAPI(self._api_client)
@@ -377,9 +381,9 @@ class WorkspaceClient:
377
381
  return self._account_access_control_proxy
378
382
 
379
383
  @property
380
- def ai_builder(self) -> pkg_aibuilder.AiBuilderAPI:
384
+ def agent_bricks(self) -> pkg_agentbricks.AgentBricksAPI:
381
385
  """The Custom LLMs service manages state and powers the UI for the Custom LLM product."""
382
- return self._ai_builder
386
+ return self._agent_bricks
383
387
 
384
388
  @property
385
389
  def alerts(self) -> pkg_sql.AlertsAPI:
@@ -411,11 +415,21 @@ class WorkspaceClient:
411
415
  """A catalog is the first layer of Unity Catalog’s three-level namespace."""
412
416
  return self._catalogs
413
417
 
418
+ @property
419
+ def clean_room_asset_revisions(self) -> pkg_cleanrooms.CleanRoomAssetRevisionsAPI:
420
+ """Clean Room Asset Revisions denote new versions of uploaded assets (e.g."""
421
+ return self._clean_room_asset_revisions
422
+
414
423
  @property
415
424
  def clean_room_assets(self) -> pkg_cleanrooms.CleanRoomAssetsAPI:
416
425
  """Clean room assets are data and code objects — Tables, volumes, and notebooks that are shared with the clean room."""
417
426
  return self._clean_room_assets
418
427
 
428
+ @property
429
+ def clean_room_auto_approval_rules(self) -> pkg_cleanrooms.CleanRoomAutoApprovalRulesAPI:
430
+ """Clean room auto-approval rules automatically create an approval on your behalf when an asset (e.g."""
431
+ return self._clean_room_auto_approval_rules
432
+
419
433
  @property
420
434
  def clean_room_task_runs(self) -> pkg_cleanrooms.CleanRoomTaskRunsAPI:
421
435
  """Clean room task runs are the executions of notebooks in a clean room."""
@@ -331,7 +331,7 @@ def file_oidc(cfg) -> Optional[CredentialsProvider]:
331
331
  # that provides a Databricks token from an IdTokenSource.
332
332
  def _oidc_credentials_provider(cfg, id_token_source: oidc.IdTokenSource) -> Optional[CredentialsProvider]:
333
333
  try:
334
- id_token = id_token_source.id_token()
334
+ id_token_source.id_token() # validate the id_token_source
335
335
  except Exception as e:
336
336
  logger.debug(f"Failed to get OIDC token: {e}")
337
337
  return None
@@ -341,7 +341,7 @@ def _oidc_credentials_provider(cfg, id_token_source: oidc.IdTokenSource) -> Opti
341
341
  token_endpoint=cfg.oidc_endpoints.token_endpoint,
342
342
  client_id=cfg.client_id,
343
343
  account_id=cfg.account_id,
344
- id_token=id_token,
344
+ id_token_source=id_token_source,
345
345
  disable_async=cfg.disable_async_token_refresh,
346
346
  )
347
347
 
@@ -28,7 +28,6 @@ from .._base_client import _BaseClient, _RawResponse, _StreamingResponse
28
28
  from .._property import _cached_property
29
29
  from ..config import Config
30
30
  from ..errors import AlreadyExists, NotFound
31
- from ..errors.customizer import _RetryAfterCustomizer
32
31
  from ..errors.mapper import _error_mapper
33
32
  from ..retries import retried
34
33
  from ..service import files
@@ -577,6 +576,27 @@ class _DbfsPath(_Path):
577
576
  return f"<_DbfsPath {self._path}>"
578
577
 
579
578
 
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
+
580
600
  class DbfsExt(files.DbfsAPI):
581
601
  __doc__ = files.DbfsAPI.__doc__
582
602
 
@@ -885,7 +905,7 @@ class FilesExt(files.FilesAPI):
885
905
  timeout=self._config.multipart_upload_single_chunk_upload_timeout_seconds,
886
906
  )
887
907
 
888
- upload_response = self._retry_idempotent_operation(perform, rewind)
908
+ upload_response = self._retry_cloud_idempotent_operation(perform, rewind)
889
909
 
890
910
  if upload_response.status_code in (200, 201):
891
911
  # Chunk upload successful
@@ -1097,7 +1117,7 @@ class FilesExt(files.FilesAPI):
1097
1117
  )
1098
1118
 
1099
1119
  try:
1100
- return self._retry_idempotent_operation(perform)
1120
+ return self._retry_cloud_idempotent_operation(perform)
1101
1121
  except RequestException:
1102
1122
  _LOG.warning("Failed to retrieve upload status")
1103
1123
  return None
@@ -1116,7 +1136,7 @@ class FilesExt(files.FilesAPI):
1116
1136
  # a 503 or 500 response, then you need to resume the interrupted upload from where it left off.
1117
1137
 
1118
1138
  # 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().
1120
1140
  if upload_response.status_code in self._RETRYABLE_STATUS_CODES:
1121
1141
  if retry_count < self._config.multipart_upload_max_retries:
1122
1142
  retry_count += 1
@@ -1243,7 +1263,7 @@ class FilesExt(files.FilesAPI):
1243
1263
  timeout=self._config.multipart_upload_single_chunk_upload_timeout_seconds,
1244
1264
  )
1245
1265
 
1246
- abort_response = self._retry_idempotent_operation(perform)
1266
+ abort_response = self._retry_cloud_idempotent_operation(perform)
1247
1267
 
1248
1268
  if abort_response.status_code not in (200, 201):
1249
1269
  raise ValueError(abort_response)
@@ -1265,7 +1285,7 @@ class FilesExt(files.FilesAPI):
1265
1285
  timeout=self._config.multipart_upload_single_chunk_upload_timeout_seconds,
1266
1286
  )
1267
1287
 
1268
- abort_response = self._retry_idempotent_operation(perform)
1288
+ abort_response = self._retry_cloud_idempotent_operation(perform)
1269
1289
 
1270
1290
  if abort_response.status_code not in (200, 201):
1271
1291
  raise ValueError(abort_response)
@@ -1283,23 +1303,31 @@ class FilesExt(files.FilesAPI):
1283
1303
  session.mount("http://", http_adapter)
1284
1304
  return session
1285
1305
 
1286
- def _retry_idempotent_operation(
1306
+ def _retry_cloud_idempotent_operation(
1287
1307
  self, operation: Callable[[], requests.Response], before_retry: Callable = None
1288
1308
  ) -> 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.
1291
1312
  """
1292
1313
 
1293
- def delegate():
1314
+ def delegate() -> requests.Response:
1294
1315
  response = operation()
1295
1316
  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)
1300
1318
  else:
1301
1319
  return response
1302
1320
 
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
+
1303
1331
  # following _BaseClient timeout
1304
1332
  retry_timeout_seconds = self._config.retry_timeout_seconds or 300
1305
1333
 
@@ -1307,7 +1335,7 @@ class FilesExt(files.FilesAPI):
1307
1335
  timeout=timedelta(seconds=retry_timeout_seconds),
1308
1336
  # also retry on network errors (connection error, connection timeout)
1309
1337
  # where we believe request didn't reach the server
1310
- is_retryable=_BaseClient._is_retryable,
1338
+ is_retryable=extended_is_retryable,
1311
1339
  before_retry=before_retry,
1312
1340
  )(delegate)()
1313
1341
 
@@ -4,6 +4,7 @@ from typing import Dict, Optional
4
4
  from requests import Response
5
5
 
6
6
  from databricks.sdk.service.serving import (ExternalFunctionRequestHttpMethod,
7
+ HttpRequestResponse,
7
8
  ServingEndpointsAPI)
8
9
 
9
10
 
@@ -88,15 +89,30 @@ class ServingEndpointsExt(ServingEndpointsAPI):
88
89
  """
89
90
  response = Response()
90
91
  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,
98
111
  )
99
112
 
113
+ # Create HttpRequestResponse from the raw response
114
+ server_response = HttpRequestResponse.from_dict(res)
115
+
100
116
  # Read the content from the HttpRequestResponse object
101
117
  if hasattr(server_response, "contents") and hasattr(server_response.contents, "read"):
102
118
  raw_content = server_response.contents.read() # Read the bytes
@@ -109,4 +125,9 @@ class ServingEndpointsExt(ServingEndpointsAPI):
109
125
  else:
110
126
  raise ValueError("Contents must be bytes.")
111
127
 
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
+
112
133
  return response
databricks/sdk/oidc.py CHANGED
@@ -188,14 +188,18 @@ class DatabricksOidcTokenSource(oauth.TokenSource):
188
188
  logger.debug("Client ID provided, authenticating with Workload Identity Federation")
189
189
 
190
190
  id_token = self._id_token_source.id_token()
191
+ return self._exchange_id_token(id_token)
191
192
 
193
+ # This function is used to create the OAuth client.
194
+ # It exists to make it easier to test.
195
+ def _exchange_id_token(self, id_token: IdToken) -> oauth.Token:
192
196
  client = oauth.ClientCredentials(
193
197
  client_id=self._client_id,
194
- client_secret="", # we have no (rotatable) secrets in OIDC flow
198
+ client_secret="", # there is no (rotatable) secrets in the OIDC flow
195
199
  token_url=self._token_endpoint,
196
200
  endpoint_params={
197
201
  "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
198
- "subject_token": id_token,
202
+ "subject_token": id_token.jwt,
199
203
  "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
200
204
  },
201
205
  scopes=["all-apis"],
@@ -23,9 +23,6 @@ class CustomLlm:
23
23
  instructions: str
24
24
  """Instructions for the custom LLM to follow"""
25
25
 
26
- optimization_state: State
27
- """If optimization is kicked off, tracks the state of the custom LLM"""
28
-
29
26
  agent_artifact_path: Optional[str] = None
30
27
 
31
28
  creation_time: Optional[str] = None
@@ -45,6 +42,9 @@ class CustomLlm:
45
42
 
46
43
  id: Optional[str] = None
47
44
 
45
+ optimization_state: Optional[State] = None
46
+ """If optimization is kicked off, tracks the state of the custom LLM"""
47
+
48
48
  def as_dict(self) -> dict:
49
49
  """Serializes the CustomLlm into a dictionary suitable for use as a JSON request body."""
50
50
  body = {}
@@ -190,7 +190,7 @@ class Table:
190
190
  )
191
191
 
192
192
 
193
- class AiBuilderAPI:
193
+ class AgentBricksAPI:
194
194
  """The Custom LLMs service manages state and powers the UI for the Custom LLM product."""
195
195
 
196
196
  def __init__(self, api_client):
@@ -270,7 +270,7 @@ class AiBuilderAPI:
270
270
  "Accept": "application/json",
271
271
  }
272
272
 
273
- self._api.do("DELETE", f"/api/2.0/custom-lms/{id}", headers=headers)
273
+ self._api.do("DELETE", f"/api/2.0/custom-llms/{id}", headers=headers)
274
274
 
275
275
  def get_custom_llm(self, id: str) -> CustomLlm:
276
276
  """Get a Custom LLM.