databricks-sdk 0.37.0__py3-none-any.whl → 0.38.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.
Potentially problematic release.
This version of databricks-sdk might be problematic. Click here for more details.
- databricks/sdk/__init__.py +3 -2
- databricks/sdk/_base_client.py +61 -14
- databricks/sdk/config.py +10 -9
- databricks/sdk/credentials_provider.py +6 -5
- databricks/sdk/mixins/jobs.py +49 -0
- databricks/sdk/service/apps.py +10 -4
- databricks/sdk/service/billing.py +1 -1
- databricks/sdk/service/catalog.py +196 -32
- databricks/sdk/service/dashboards.py +10 -10
- databricks/sdk/service/iam.py +2 -2
- databricks/sdk/service/jobs.py +17 -8
- databricks/sdk/service/oauth2.py +1 -0
- databricks/sdk/service/pipelines.py +82 -15
- databricks/sdk/service/provisioning.py +15 -0
- databricks/sdk/service/settings.py +3 -1
- databricks/sdk/service/sharing.py +2 -0
- databricks/sdk/service/workspace.py +2 -1
- databricks/sdk/version.py +1 -1
- {databricks_sdk-0.37.0.dist-info → databricks_sdk-0.38.0.dist-info}/METADATA +1 -1
- {databricks_sdk-0.37.0.dist-info → databricks_sdk-0.38.0.dist-info}/RECORD +24 -23
- {databricks_sdk-0.37.0.dist-info → databricks_sdk-0.38.0.dist-info}/LICENSE +0 -0
- {databricks_sdk-0.37.0.dist-info → databricks_sdk-0.38.0.dist-info}/NOTICE +0 -0
- {databricks_sdk-0.37.0.dist-info → databricks_sdk-0.38.0.dist-info}/WHEEL +0 -0
- {databricks_sdk-0.37.0.dist-info → databricks_sdk-0.38.0.dist-info}/top_level.txt +0 -0
databricks/sdk/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from databricks.sdk import azure
|
|
|
6
6
|
from databricks.sdk.credentials_provider import CredentialsStrategy
|
|
7
7
|
from databricks.sdk.mixins.compute import ClustersExt
|
|
8
8
|
from databricks.sdk.mixins.files import DbfsExt
|
|
9
|
+
from databricks.sdk.mixins.jobs import JobsExt
|
|
9
10
|
from databricks.sdk.mixins.open_ai_client import ServingEndpointsExt
|
|
10
11
|
from databricks.sdk.mixins.workspace import WorkspaceExt
|
|
11
12
|
from databricks.sdk.service.apps import AppsAPI
|
|
@@ -204,7 +205,7 @@ class WorkspaceClient:
|
|
|
204
205
|
self._instance_pools = InstancePoolsAPI(self._api_client)
|
|
205
206
|
self._instance_profiles = InstanceProfilesAPI(self._api_client)
|
|
206
207
|
self._ip_access_lists = IpAccessListsAPI(self._api_client)
|
|
207
|
-
self._jobs =
|
|
208
|
+
self._jobs = JobsExt(self._api_client)
|
|
208
209
|
self._lakeview = LakeviewAPI(self._api_client)
|
|
209
210
|
self._libraries = LibrariesAPI(self._api_client)
|
|
210
211
|
self._metastores = MetastoresAPI(self._api_client)
|
|
@@ -450,7 +451,7 @@ class WorkspaceClient:
|
|
|
450
451
|
return self._ip_access_lists
|
|
451
452
|
|
|
452
453
|
@property
|
|
453
|
-
def jobs(self) ->
|
|
454
|
+
def jobs(self) -> JobsExt:
|
|
454
455
|
"""The Jobs API allows you to create, edit, and delete jobs."""
|
|
455
456
|
return self._jobs
|
|
456
457
|
|
databricks/sdk/_base_client.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import io
|
|
1
2
|
import logging
|
|
2
3
|
import urllib.parse
|
|
3
4
|
from datetime import timedelta
|
|
@@ -50,7 +51,8 @@ class _BaseClient:
|
|
|
50
51
|
http_timeout_seconds: float = None,
|
|
51
52
|
extra_error_customizers: List[_ErrorCustomizer] = None,
|
|
52
53
|
debug_headers: bool = False,
|
|
53
|
-
clock: Clock = None
|
|
54
|
+
clock: Clock = None,
|
|
55
|
+
streaming_buffer_size: int = 1024 * 1024): # 1MB
|
|
54
56
|
"""
|
|
55
57
|
:param debug_truncate_bytes:
|
|
56
58
|
:param retry_timeout_seconds:
|
|
@@ -68,6 +70,7 @@ class _BaseClient:
|
|
|
68
70
|
:param extra_error_customizers:
|
|
69
71
|
:param debug_headers: Whether to include debug headers in the request log.
|
|
70
72
|
:param clock: Clock object to use for time-related operations.
|
|
73
|
+
:param streaming_buffer_size: The size of the buffer to use for streaming responses.
|
|
71
74
|
"""
|
|
72
75
|
|
|
73
76
|
self._debug_truncate_bytes = debug_truncate_bytes or 96
|
|
@@ -78,6 +81,7 @@ class _BaseClient:
|
|
|
78
81
|
self._clock = clock or RealClock()
|
|
79
82
|
self._session = requests.Session()
|
|
80
83
|
self._session.auth = self._authenticate
|
|
84
|
+
self._streaming_buffer_size = streaming_buffer_size
|
|
81
85
|
|
|
82
86
|
# We don't use `max_retries` from HTTPAdapter to align with a more production-ready
|
|
83
87
|
# retry strategy established in the Databricks SDK for Go. See _is_retryable and
|
|
@@ -127,6 +131,14 @@ class _BaseClient:
|
|
|
127
131
|
flattened = dict(flatten_dict(with_fixed_bools))
|
|
128
132
|
return flattened
|
|
129
133
|
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _is_seekable_stream(data) -> bool:
|
|
136
|
+
if data is None:
|
|
137
|
+
return False
|
|
138
|
+
if not isinstance(data, io.IOBase):
|
|
139
|
+
return False
|
|
140
|
+
return data.seekable()
|
|
141
|
+
|
|
130
142
|
def do(self,
|
|
131
143
|
method: str,
|
|
132
144
|
url: str,
|
|
@@ -141,24 +153,39 @@ class _BaseClient:
|
|
|
141
153
|
if headers is None:
|
|
142
154
|
headers = {}
|
|
143
155
|
headers['User-Agent'] = self._user_agent_base
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
|
|
157
|
+
# Wrap strings and bytes in a seekable stream so that we can rewind them.
|
|
158
|
+
if isinstance(data, (str, bytes)):
|
|
159
|
+
data = io.BytesIO(data.encode('utf-8') if isinstance(data, str) else data)
|
|
160
|
+
|
|
161
|
+
# Only retry if the request is not a stream or if the stream is seekable and
|
|
162
|
+
# we can rewind it. This is necessary to avoid bugs where the retry doesn't
|
|
163
|
+
# re-read already read data from the body.
|
|
164
|
+
if data is not None and not self._is_seekable_stream(data):
|
|
165
|
+
logger.debug(f"Retry disabled for non-seekable stream: type={type(data)}")
|
|
166
|
+
call = self._perform
|
|
167
|
+
else:
|
|
168
|
+
call = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
|
|
169
|
+
is_retryable=self._is_retryable,
|
|
170
|
+
clock=self._clock)(self._perform)
|
|
171
|
+
|
|
172
|
+
response = call(method,
|
|
173
|
+
url,
|
|
174
|
+
query=query,
|
|
175
|
+
headers=headers,
|
|
176
|
+
body=body,
|
|
177
|
+
raw=raw,
|
|
178
|
+
files=files,
|
|
179
|
+
data=data,
|
|
180
|
+
auth=auth)
|
|
156
181
|
|
|
157
182
|
resp = dict()
|
|
158
183
|
for header in response_headers if response_headers else []:
|
|
159
184
|
resp[header] = response.headers.get(Casing.to_header_case(header))
|
|
160
185
|
if raw:
|
|
161
|
-
|
|
186
|
+
streaming_response = _StreamingResponse(response)
|
|
187
|
+
streaming_response.set_chunk_size(self._streaming_buffer_size)
|
|
188
|
+
resp["contents"] = streaming_response
|
|
162
189
|
return resp
|
|
163
190
|
if not len(response.content):
|
|
164
191
|
return resp
|
|
@@ -221,6 +248,12 @@ class _BaseClient:
|
|
|
221
248
|
files=None,
|
|
222
249
|
data=None,
|
|
223
250
|
auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None):
|
|
251
|
+
# Keep track of the initial position of the stream so that we can rewind it if
|
|
252
|
+
# we need to retry the request.
|
|
253
|
+
initial_data_position = 0
|
|
254
|
+
if self._is_seekable_stream(data):
|
|
255
|
+
initial_data_position = data.tell()
|
|
256
|
+
|
|
224
257
|
response = self._session.request(method,
|
|
225
258
|
url,
|
|
226
259
|
params=self._fix_query_string(query),
|
|
@@ -232,9 +265,18 @@ class _BaseClient:
|
|
|
232
265
|
stream=raw,
|
|
233
266
|
timeout=self._http_timeout_seconds)
|
|
234
267
|
self._record_request_log(response, raw=raw or data is not None or files is not None)
|
|
268
|
+
|
|
235
269
|
error = self._error_parser.get_api_error(response)
|
|
236
270
|
if error is not None:
|
|
271
|
+
# If the request body is a seekable stream, rewind it so that it is ready
|
|
272
|
+
# to be read again in case of a retry.
|
|
273
|
+
#
|
|
274
|
+
# TODO: This should be moved into a "before-retry" hook to avoid one
|
|
275
|
+
# unnecessary seek on the last failed retry before aborting.
|
|
276
|
+
if self._is_seekable_stream(data):
|
|
277
|
+
data.seek(initial_data_position)
|
|
237
278
|
raise error from None
|
|
279
|
+
|
|
238
280
|
return response
|
|
239
281
|
|
|
240
282
|
def _record_request_log(self, response: requests.Response, raw: bool = False) -> None:
|
|
@@ -283,6 +325,11 @@ class _StreamingResponse(BinaryIO):
|
|
|
283
325
|
return False
|
|
284
326
|
|
|
285
327
|
def read(self, n: int = -1) -> bytes:
|
|
328
|
+
"""
|
|
329
|
+
Read up to n bytes from the response stream. If n is negative, read
|
|
330
|
+
until the end of the stream.
|
|
331
|
+
"""
|
|
332
|
+
|
|
286
333
|
self._open()
|
|
287
334
|
read_everything = n < 0
|
|
288
335
|
remaining_bytes = n
|
databricks/sdk/config.py
CHANGED
|
@@ -92,15 +92,16 @@ class Config:
|
|
|
92
92
|
max_connections_per_pool: int = ConfigAttribute()
|
|
93
93
|
databricks_environment: Optional[DatabricksEnvironment] = None
|
|
94
94
|
|
|
95
|
-
def __init__(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
*,
|
|
98
|
+
# Deprecated. Use credentials_strategy instead.
|
|
99
|
+
credentials_provider: Optional[CredentialsStrategy] = None,
|
|
100
|
+
credentials_strategy: Optional[CredentialsStrategy] = None,
|
|
101
|
+
product=None,
|
|
102
|
+
product_version=None,
|
|
103
|
+
clock: Optional[Clock] = None,
|
|
104
|
+
**kwargs):
|
|
104
105
|
self._header_factory = None
|
|
105
106
|
self._inner = {}
|
|
106
107
|
self._user_agent_other_info = []
|
|
@@ -304,11 +304,12 @@ def github_oidc_azure(cfg: 'Config') -> Optional[CredentialsProvider]:
|
|
|
304
304
|
# detect Azure AD Tenant ID if it's not specified directly
|
|
305
305
|
token_endpoint = cfg.oidc_endpoints.token_endpoint
|
|
306
306
|
cfg.azure_tenant_id = token_endpoint.replace(aad_endpoint, '').split('/')[0]
|
|
307
|
-
inner = ClientCredentials(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
307
|
+
inner = ClientCredentials(
|
|
308
|
+
client_id=cfg.azure_client_id,
|
|
309
|
+
client_secret="", # we have no (rotatable) secrets in OIDC flow
|
|
310
|
+
token_url=f"{aad_endpoint}{cfg.azure_tenant_id}/oauth2/token",
|
|
311
|
+
endpoint_params=params,
|
|
312
|
+
use_params=True)
|
|
312
313
|
|
|
313
314
|
def refreshed_headers() -> Dict[str, str]:
|
|
314
315
|
token = inner.token()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from databricks.sdk.service import jobs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JobsExt(jobs.JobsAPI):
|
|
7
|
+
|
|
8
|
+
def get_run(self,
|
|
9
|
+
run_id: int,
|
|
10
|
+
*,
|
|
11
|
+
include_history: Optional[bool] = None,
|
|
12
|
+
include_resolved_values: Optional[bool] = None,
|
|
13
|
+
page_token: Optional[str] = None) -> jobs.Run:
|
|
14
|
+
"""
|
|
15
|
+
This method fetches the details of a run identified by `run_id`. If the run has multiple pages of tasks or iterations,
|
|
16
|
+
it will paginate through all pages and aggregate the results.
|
|
17
|
+
:param run_id: int
|
|
18
|
+
The canonical identifier of the run for which to retrieve the metadata. This field is required.
|
|
19
|
+
:param include_history: bool (optional)
|
|
20
|
+
Whether to include the repair history in the response.
|
|
21
|
+
:param include_resolved_values: bool (optional)
|
|
22
|
+
Whether to include resolved parameter values in the response.
|
|
23
|
+
:param page_token: str (optional)
|
|
24
|
+
To list the next page or the previous page of job tasks, set this field to the value of the
|
|
25
|
+
`next_page_token` or `prev_page_token` returned in the GetJob response.
|
|
26
|
+
:returns: :class:`Run`
|
|
27
|
+
"""
|
|
28
|
+
run = super().get_run(run_id,
|
|
29
|
+
include_history=include_history,
|
|
30
|
+
include_resolved_values=include_resolved_values,
|
|
31
|
+
page_token=page_token)
|
|
32
|
+
|
|
33
|
+
# When querying a Job run, a page token is returned when there are more than 100 tasks. No iterations are defined for a Job run. Therefore, the next page in the response only includes the next page of tasks.
|
|
34
|
+
# When querying a ForEach task run, a page token is returned when there are more than 100 iterations. Only a single task is returned, corresponding to the ForEach task itself. Therefore, the client only reads the iterations from the next page and not the tasks.
|
|
35
|
+
is_paginating_iterations = run.iterations is not None and len(run.iterations) > 0
|
|
36
|
+
|
|
37
|
+
while run.next_page_token is not None:
|
|
38
|
+
next_run = super().get_run(run_id,
|
|
39
|
+
include_history=include_history,
|
|
40
|
+
include_resolved_values=include_resolved_values,
|
|
41
|
+
page_token=run.next_page_token)
|
|
42
|
+
if is_paginating_iterations:
|
|
43
|
+
run.iterations.extend(next_run.iterations)
|
|
44
|
+
else:
|
|
45
|
+
run.tasks.extend(next_run.tasks)
|
|
46
|
+
run.next_page_token = next_run.next_page_token
|
|
47
|
+
|
|
48
|
+
run.prev_page_token = None
|
|
49
|
+
return run
|
databricks/sdk/service/apps.py
CHANGED
|
@@ -52,6 +52,8 @@ class App:
|
|
|
52
52
|
resources: Optional[List[AppResource]] = None
|
|
53
53
|
"""Resources for the app."""
|
|
54
54
|
|
|
55
|
+
service_principal_client_id: Optional[str] = None
|
|
56
|
+
|
|
55
57
|
service_principal_id: Optional[int] = None
|
|
56
58
|
|
|
57
59
|
service_principal_name: Optional[str] = None
|
|
@@ -79,6 +81,8 @@ class App:
|
|
|
79
81
|
if self.name is not None: body['name'] = self.name
|
|
80
82
|
if self.pending_deployment: body['pending_deployment'] = self.pending_deployment.as_dict()
|
|
81
83
|
if self.resources: body['resources'] = [v.as_dict() for v in self.resources]
|
|
84
|
+
if self.service_principal_client_id is not None:
|
|
85
|
+
body['service_principal_client_id'] = self.service_principal_client_id
|
|
82
86
|
if self.service_principal_id is not None: body['service_principal_id'] = self.service_principal_id
|
|
83
87
|
if self.service_principal_name is not None:
|
|
84
88
|
body['service_principal_name'] = self.service_principal_name
|
|
@@ -100,6 +104,7 @@ class App:
|
|
|
100
104
|
name=d.get('name', None),
|
|
101
105
|
pending_deployment=_from_dict(d, 'pending_deployment', AppDeployment),
|
|
102
106
|
resources=_repeated_dict(d, 'resources', AppResource),
|
|
107
|
+
service_principal_client_id=d.get('service_principal_client_id', None),
|
|
103
108
|
service_principal_id=d.get('service_principal_id', None),
|
|
104
109
|
service_principal_name=d.get('service_principal_name', None),
|
|
105
110
|
update_time=d.get('update_time', None),
|
|
@@ -798,7 +803,7 @@ class AppsAPI:
|
|
|
798
803
|
Long-running operation waiter for :class:`App`.
|
|
799
804
|
See :method:wait_get_app_active for more details.
|
|
800
805
|
"""
|
|
801
|
-
body = app
|
|
806
|
+
body = app.as_dict()
|
|
802
807
|
headers = {'Accept': 'application/json', 'Content-Type': 'application/json', }
|
|
803
808
|
|
|
804
809
|
op_response = self._api.do('POST', '/api/2.0/apps', body=body, headers=headers)
|
|
@@ -836,7 +841,7 @@ class AppsAPI:
|
|
|
836
841
|
Long-running operation waiter for :class:`AppDeployment`.
|
|
837
842
|
See :method:wait_get_deployment_app_succeeded for more details.
|
|
838
843
|
"""
|
|
839
|
-
body = app_deployment
|
|
844
|
+
body = app_deployment.as_dict()
|
|
840
845
|
headers = {'Accept': 'application/json', 'Content-Type': 'application/json', }
|
|
841
846
|
|
|
842
847
|
op_response = self._api.do('POST',
|
|
@@ -1053,12 +1058,13 @@ class AppsAPI:
|
|
|
1053
1058
|
Updates the app with the supplied name.
|
|
1054
1059
|
|
|
1055
1060
|
:param name: str
|
|
1056
|
-
The name of the app.
|
|
1061
|
+
The name of the app. The name must contain only lowercase alphanumeric characters and hyphens. It
|
|
1062
|
+
must be unique within the workspace.
|
|
1057
1063
|
:param app: :class:`App` (optional)
|
|
1058
1064
|
|
|
1059
1065
|
:returns: :class:`App`
|
|
1060
1066
|
"""
|
|
1061
|
-
body = app
|
|
1067
|
+
body = app.as_dict()
|
|
1062
1068
|
headers = {'Accept': 'application/json', 'Content-Type': 'application/json', }
|
|
1063
1069
|
|
|
1064
1070
|
res = self._api.do('PATCH', f'/api/2.0/apps/{name}', body=body, headers=headers)
|
|
@@ -1121,7 +1121,7 @@ class BudgetsAPI:
|
|
|
1121
1121
|
Gets a budget configuration for an account. Both account and budget configuration are specified by ID.
|
|
1122
1122
|
|
|
1123
1123
|
:param budget_id: str
|
|
1124
|
-
The
|
|
1124
|
+
The budget configuration ID
|
|
1125
1125
|
|
|
1126
1126
|
:returns: :class:`GetBudgetConfigurationResponse`
|
|
1127
1127
|
"""
|