databricks-sdk 0.44.1__py3-none-any.whl → 0.45.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 +123 -115
- databricks/sdk/_base_client.py +112 -88
- databricks/sdk/_property.py +12 -7
- databricks/sdk/_widgets/__init__.py +13 -2
- databricks/sdk/_widgets/default_widgets_utils.py +21 -15
- databricks/sdk/_widgets/ipywidgets_utils.py +47 -24
- databricks/sdk/azure.py +8 -6
- databricks/sdk/casing.py +5 -5
- databricks/sdk/config.py +152 -99
- databricks/sdk/core.py +57 -47
- databricks/sdk/credentials_provider.py +300 -205
- databricks/sdk/data_plane.py +86 -3
- databricks/sdk/dbutils.py +123 -87
- databricks/sdk/environments.py +52 -35
- databricks/sdk/errors/base.py +61 -35
- databricks/sdk/errors/customizer.py +3 -3
- databricks/sdk/errors/deserializer.py +38 -25
- databricks/sdk/errors/details.py +417 -0
- databricks/sdk/errors/mapper.py +1 -1
- databricks/sdk/errors/overrides.py +27 -24
- databricks/sdk/errors/parser.py +26 -14
- databricks/sdk/errors/platform.py +10 -10
- databricks/sdk/errors/private_link.py +24 -24
- databricks/sdk/logger/round_trip_logger.py +28 -20
- databricks/sdk/mixins/compute.py +90 -60
- databricks/sdk/mixins/files.py +815 -145
- databricks/sdk/mixins/jobs.py +191 -16
- databricks/sdk/mixins/open_ai_client.py +26 -20
- databricks/sdk/mixins/workspace.py +45 -34
- databricks/sdk/oauth.py +372 -196
- databricks/sdk/retries.py +14 -12
- databricks/sdk/runtime/__init__.py +34 -17
- databricks/sdk/runtime/dbutils_stub.py +52 -39
- databricks/sdk/service/_internal.py +12 -7
- databricks/sdk/service/apps.py +618 -418
- databricks/sdk/service/billing.py +827 -604
- databricks/sdk/service/catalog.py +6552 -4474
- databricks/sdk/service/cleanrooms.py +550 -388
- databricks/sdk/service/compute.py +5241 -3531
- databricks/sdk/service/dashboards.py +1313 -923
- databricks/sdk/service/files.py +442 -309
- databricks/sdk/service/iam.py +2115 -1483
- databricks/sdk/service/jobs.py +4151 -2588
- databricks/sdk/service/marketplace.py +2210 -1517
- databricks/sdk/service/ml.py +3364 -2255
- databricks/sdk/service/oauth2.py +922 -584
- databricks/sdk/service/pipelines.py +1865 -1203
- databricks/sdk/service/provisioning.py +1435 -1029
- databricks/sdk/service/serving.py +2040 -1278
- databricks/sdk/service/settings.py +2846 -1929
- databricks/sdk/service/sharing.py +2201 -877
- databricks/sdk/service/sql.py +4650 -3103
- databricks/sdk/service/vectorsearch.py +816 -550
- databricks/sdk/service/workspace.py +1330 -906
- databricks/sdk/useragent.py +36 -22
- databricks/sdk/version.py +1 -1
- {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.45.0.dist-info}/METADATA +31 -31
- databricks_sdk-0.45.0.dist-info/RECORD +70 -0
- {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.45.0.dist-info}/WHEEL +1 -1
- databricks_sdk-0.44.1.dist-info/RECORD +0 -69
- {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.45.0.dist-info}/LICENSE +0 -0
- {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.45.0.dist-info}/NOTICE +0 -0
- {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.45.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ErrorInfo:
|
|
8
|
+
"""Describes the cause of the error with structured details."""
|
|
9
|
+
|
|
10
|
+
reason: str
|
|
11
|
+
domain: str
|
|
12
|
+
metadata: Dict[str, str]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class RequestInfo:
|
|
17
|
+
"""
|
|
18
|
+
Contains metadata about the request that clients can attach when
|
|
19
|
+
filing a bug or providing other forms of feedback.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
request_id: str
|
|
23
|
+
serving_data: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class RetryInfo:
|
|
28
|
+
"""
|
|
29
|
+
Describes when the clients can retry a failed request. Clients could
|
|
30
|
+
ignore the recommendation here or retry when this information is missing
|
|
31
|
+
from error responses.
|
|
32
|
+
|
|
33
|
+
It's always recommended that clients should use exponential backoff
|
|
34
|
+
when retrying.
|
|
35
|
+
|
|
36
|
+
Clients should wait until `retry_delay` amount of time has passed since
|
|
37
|
+
receiving the error response before retrying. If retrying requests also
|
|
38
|
+
fail, clients should use an exponential backoff scheme to gradually
|
|
39
|
+
increase the delay between retries based on `retry_delay`, until either
|
|
40
|
+
a maximum number of retries have been reached or a maximum retry delay
|
|
41
|
+
cap has been reached.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
retry_delay_seconds: float
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class DebugInfo:
|
|
49
|
+
"""Describes additional debugging info."""
|
|
50
|
+
|
|
51
|
+
stack_entries: List[str]
|
|
52
|
+
detail: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class QuotaFailureViolation:
|
|
57
|
+
"""Describes a single quota violation."""
|
|
58
|
+
|
|
59
|
+
subject: str
|
|
60
|
+
description: str
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class QuotaFailure:
|
|
65
|
+
"""
|
|
66
|
+
Describes how a quota check failed.
|
|
67
|
+
|
|
68
|
+
For example if a daily limit was exceeded for the calling project, a
|
|
69
|
+
service could respond with a QuotaFailure detail containing the project
|
|
70
|
+
id and the description of the quota limit that was exceeded. If the
|
|
71
|
+
calling project hasn't enabled the service in the developer console,
|
|
72
|
+
then a service could respond with the project id and set
|
|
73
|
+
`service_disabled` to true.
|
|
74
|
+
|
|
75
|
+
Also see RetryInfo and Help types for other details about handling a
|
|
76
|
+
quota failure.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
violations: List[QuotaFailureViolation]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class PreconditionFailureViolation:
|
|
84
|
+
"""Describes a single precondition violation."""
|
|
85
|
+
|
|
86
|
+
type: str
|
|
87
|
+
subject: str
|
|
88
|
+
description: str
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class PreconditionFailure:
|
|
93
|
+
"""Describes what preconditions have failed."""
|
|
94
|
+
|
|
95
|
+
violations: List[PreconditionFailureViolation]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class BadRequestFieldViolation:
|
|
100
|
+
"""Describes a single field violation in a bad request."""
|
|
101
|
+
|
|
102
|
+
field: str
|
|
103
|
+
description: str
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class BadRequest:
|
|
108
|
+
"""
|
|
109
|
+
Describes violations in a client request. This error type
|
|
110
|
+
focuses on the syntactic aspects of the request.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
field_violations: List[BadRequestFieldViolation]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class ResourceInfo:
|
|
118
|
+
"""Describes the resource that is being accessed."""
|
|
119
|
+
|
|
120
|
+
resource_type: str
|
|
121
|
+
resource_name: str
|
|
122
|
+
owner: str
|
|
123
|
+
description: str
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class HelpLink:
|
|
128
|
+
"""Describes a single help link."""
|
|
129
|
+
|
|
130
|
+
description: str
|
|
131
|
+
url: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class Help:
|
|
136
|
+
"""
|
|
137
|
+
Provides links to documentation or for performing an out of
|
|
138
|
+
band action.
|
|
139
|
+
|
|
140
|
+
For example, if a quota check failed with an error indicating
|
|
141
|
+
the calling project hasn't enabled the accessed service, this
|
|
142
|
+
can contain a URL pointing directly to the right place in the
|
|
143
|
+
developer console to flip the bit.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
links: List[HelpLink]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class ErrorDetails:
|
|
151
|
+
"""
|
|
152
|
+
ErrorDetails contains the error details of an API error. It
|
|
153
|
+
is the union of known error details types and unknown details.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
error_info: Optional[ErrorInfo] = None
|
|
157
|
+
request_info: Optional[RequestInfo] = None
|
|
158
|
+
retry_info: Optional[RetryInfo] = None
|
|
159
|
+
debug_info: Optional[DebugInfo] = None
|
|
160
|
+
quota_failure: Optional[QuotaFailure] = None
|
|
161
|
+
precondition_failure: Optional[PreconditionFailure] = None
|
|
162
|
+
bad_request: Optional[BadRequest] = None
|
|
163
|
+
resource_info: Optional[ResourceInfo] = None
|
|
164
|
+
help: Optional[Help] = None
|
|
165
|
+
unknown_details: List[Any] = field(default_factory=list)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Supported error details proto types.
|
|
169
|
+
_ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo"
|
|
170
|
+
_REQUEST_INFO_TYPE = "type.googleapis.com/google.rpc.RequestInfo"
|
|
171
|
+
_RETRY_INFO_TYPE = "type.googleapis.com/google.rpc.RetryInfo"
|
|
172
|
+
_DEBUG_INFO_TYPE = "type.googleapis.com/google.rpc.DebugInfo"
|
|
173
|
+
_QUOTA_FAILURE_TYPE = "type.googleapis.com/google.rpc.QuotaFailure"
|
|
174
|
+
_PRECONDITION_FAILURE_TYPE = "type.googleapis.com/google.rpc.PreconditionFailure"
|
|
175
|
+
_BAD_REQUEST_TYPE = "type.googleapis.com/google.rpc.BadRequest"
|
|
176
|
+
_RESOURCE_INFO_TYPE = "type.googleapis.com/google.rpc.ResourceInfo"
|
|
177
|
+
_HELP_TYPE = "type.googleapis.com/google.rpc.Help"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def parse_error_details(details: List[Any]) -> ErrorDetails:
|
|
181
|
+
ed = ErrorDetails()
|
|
182
|
+
|
|
183
|
+
if not details:
|
|
184
|
+
return ed
|
|
185
|
+
|
|
186
|
+
for d in details:
|
|
187
|
+
pd = _parse_json_error_details(d)
|
|
188
|
+
|
|
189
|
+
if isinstance(pd, ErrorInfo):
|
|
190
|
+
ed.error_info = pd
|
|
191
|
+
elif isinstance(pd, RequestInfo):
|
|
192
|
+
ed.request_info = pd
|
|
193
|
+
elif isinstance(pd, RetryInfo):
|
|
194
|
+
ed.retry_info = pd
|
|
195
|
+
elif isinstance(pd, DebugInfo):
|
|
196
|
+
ed.debug_info = pd
|
|
197
|
+
elif isinstance(pd, QuotaFailure):
|
|
198
|
+
ed.quota_failure = pd
|
|
199
|
+
elif isinstance(pd, PreconditionFailure):
|
|
200
|
+
ed.precondition_failure = pd
|
|
201
|
+
elif isinstance(pd, BadRequest):
|
|
202
|
+
ed.bad_request = pd
|
|
203
|
+
elif isinstance(pd, ResourceInfo):
|
|
204
|
+
ed.resource_info = pd
|
|
205
|
+
elif isinstance(pd, Help):
|
|
206
|
+
ed.help = pd
|
|
207
|
+
else:
|
|
208
|
+
ed.unknown_details.append(pd)
|
|
209
|
+
|
|
210
|
+
return ed
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _parse_json_error_details(value: Any) -> Any:
|
|
214
|
+
"""
|
|
215
|
+
Attempts to parse an error details type from the given JSON value. If the
|
|
216
|
+
value is not a known error details type, it returns the input as is.
|
|
217
|
+
|
|
218
|
+
:param value: The JSON value to parse.
|
|
219
|
+
:return: The parsed error details type or the input value if it is not
|
|
220
|
+
a known error details type.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
if not isinstance(value, dict):
|
|
224
|
+
return value # not a JSON object
|
|
225
|
+
|
|
226
|
+
t = value.get("@type")
|
|
227
|
+
if not isinstance(t, str):
|
|
228
|
+
return value # JSON object with no @type field
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
if t == _ERROR_INFO_TYPE:
|
|
232
|
+
return _parse_error_info(value)
|
|
233
|
+
elif t == _REQUEST_INFO_TYPE:
|
|
234
|
+
return _parse_req_info(value)
|
|
235
|
+
elif t == _RETRY_INFO_TYPE:
|
|
236
|
+
return _parse_retry_info(value)
|
|
237
|
+
elif t == _DEBUG_INFO_TYPE:
|
|
238
|
+
return _parse_debug_info(value)
|
|
239
|
+
elif t == _QUOTA_FAILURE_TYPE:
|
|
240
|
+
return _parse_quota_failure(value)
|
|
241
|
+
elif t == _PRECONDITION_FAILURE_TYPE:
|
|
242
|
+
return _parse_precondition_failure(value)
|
|
243
|
+
elif t == _BAD_REQUEST_TYPE:
|
|
244
|
+
return _parse_bad_request(value)
|
|
245
|
+
elif t == _RESOURCE_INFO_TYPE:
|
|
246
|
+
return _parse_resource_info(value)
|
|
247
|
+
elif t == _HELP_TYPE:
|
|
248
|
+
return _parse_help(value)
|
|
249
|
+
else: # unknown type
|
|
250
|
+
return value
|
|
251
|
+
except (TypeError, ValueError):
|
|
252
|
+
return value # not a valid known type
|
|
253
|
+
except Exception:
|
|
254
|
+
return value
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _parse_error_info(d: Dict[str, Any]) -> ErrorInfo:
|
|
258
|
+
return ErrorInfo(
|
|
259
|
+
domain=_parse_string(d.get("domain", "")),
|
|
260
|
+
reason=_parse_string(d.get("reason", "")),
|
|
261
|
+
metadata=_parse_dict(d.get("metadata", {})),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _parse_req_info(d: Dict[str, Any]) -> RequestInfo:
|
|
266
|
+
return RequestInfo(
|
|
267
|
+
request_id=_parse_string(d.get("request_id", "")),
|
|
268
|
+
serving_data=_parse_string(d.get("serving_data", "")),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _parse_retry_info(d: Dict[str, Any]) -> RetryInfo:
|
|
273
|
+
delay = 0.0
|
|
274
|
+
if "retry_delay" in d:
|
|
275
|
+
delay = _parse_seconds(d["retry_delay"])
|
|
276
|
+
|
|
277
|
+
return RetryInfo(
|
|
278
|
+
retry_delay_seconds=delay,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _parse_debug_info(d: Dict[str, Any]) -> DebugInfo:
|
|
283
|
+
di = DebugInfo(
|
|
284
|
+
stack_entries=[],
|
|
285
|
+
detail=_parse_string(d.get("detail", "")),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if "stack_entries" not in d:
|
|
289
|
+
return di
|
|
290
|
+
|
|
291
|
+
if not isinstance(d["stack_entries"], list):
|
|
292
|
+
raise ValueError(f"Expected list, got {d['stack_entries']!r}")
|
|
293
|
+
for entry in d["stack_entries"]:
|
|
294
|
+
di.stack_entries.append(_parse_string(entry))
|
|
295
|
+
|
|
296
|
+
return di
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _parse_quota_failure_violation(d: Dict[str, Any]) -> QuotaFailureViolation:
|
|
300
|
+
return QuotaFailureViolation(
|
|
301
|
+
subject=_parse_string(d.get("subject", "")),
|
|
302
|
+
description=_parse_string(d.get("description", "")),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _parse_quota_failure(d: Dict[str, Any]) -> QuotaFailure:
|
|
307
|
+
violations = []
|
|
308
|
+
if "violations" in d:
|
|
309
|
+
if not isinstance(d["violations"], list):
|
|
310
|
+
raise ValueError(f"Expected list, got {d['violations']!r}")
|
|
311
|
+
for violation in d["violations"]:
|
|
312
|
+
if not isinstance(violation, dict):
|
|
313
|
+
raise ValueError(f"Expected dict, got {violation!r}")
|
|
314
|
+
violations.append(_parse_quota_failure_violation(violation))
|
|
315
|
+
return QuotaFailure(violations=violations)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _parse_precondition_failure_violation(d: Dict[str, Any]) -> PreconditionFailureViolation:
|
|
319
|
+
return PreconditionFailureViolation(
|
|
320
|
+
type=_parse_string(d.get("type", "")),
|
|
321
|
+
subject=_parse_string(d.get("subject", "")),
|
|
322
|
+
description=_parse_string(d.get("description", "")),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _parse_precondition_failure(d: Dict[str, Any]) -> PreconditionFailure:
|
|
327
|
+
violations = []
|
|
328
|
+
if "violations" in d:
|
|
329
|
+
if not isinstance(d["violations"], list):
|
|
330
|
+
raise ValueError(f"Expected list, got {d['violations']!r}")
|
|
331
|
+
for v in d["violations"]:
|
|
332
|
+
if not isinstance(v, dict):
|
|
333
|
+
raise ValueError(f"Expected dict, got {v!r}")
|
|
334
|
+
violations.append(_parse_precondition_failure_violation(v))
|
|
335
|
+
return PreconditionFailure(violations=violations)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _parse_bad_request_field_violation(d: Dict[str, Any]) -> BadRequestFieldViolation:
|
|
339
|
+
return BadRequestFieldViolation(
|
|
340
|
+
field=_parse_string(d.get("field", "")),
|
|
341
|
+
description=_parse_string(d.get("description", "")),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _parse_bad_request(d: Dict[str, Any]) -> BadRequest:
|
|
346
|
+
field_violations = []
|
|
347
|
+
if "field_violations" in d:
|
|
348
|
+
if not isinstance(d["field_violations"], list):
|
|
349
|
+
raise ValueError(f"Expected list, got {d['field_violations']!r}")
|
|
350
|
+
for violation in d["field_violations"]:
|
|
351
|
+
if not isinstance(violation, dict):
|
|
352
|
+
raise ValueError(f"Expected dict, got {violation!r}")
|
|
353
|
+
field_violations.append(_parse_bad_request_field_violation(violation))
|
|
354
|
+
return BadRequest(field_violations=field_violations)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _parse_resource_info(d: Dict[str, Any]) -> ResourceInfo:
|
|
358
|
+
return ResourceInfo(
|
|
359
|
+
resource_type=_parse_string(d.get("resource_type", "")),
|
|
360
|
+
resource_name=_parse_string(d.get("resource_name", "")),
|
|
361
|
+
owner=_parse_string(d.get("owner", "")),
|
|
362
|
+
description=_parse_string(d.get("description", "")),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _parse_help_link(d: Dict[str, Any]) -> HelpLink:
|
|
367
|
+
return HelpLink(
|
|
368
|
+
description=_parse_string(d.get("description", "")),
|
|
369
|
+
url=_parse_string(d.get("url", "")),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _parse_help(d: Dict[str, Any]) -> Help:
|
|
374
|
+
links = []
|
|
375
|
+
if "links" in d:
|
|
376
|
+
if not isinstance(d["links"], list):
|
|
377
|
+
raise ValueError(f"Expected list, got {d['links']!r}")
|
|
378
|
+
for link in d["links"]:
|
|
379
|
+
if not isinstance(link, dict):
|
|
380
|
+
raise ValueError(f"Expected dict, got {link!r}")
|
|
381
|
+
links.append(_parse_help_link(link))
|
|
382
|
+
return Help(links=links)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _parse_string(a: Any) -> str:
|
|
386
|
+
if isinstance(a, str):
|
|
387
|
+
return a
|
|
388
|
+
raise ValueError(f"Expected string, got {a!r}")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _parse_dict(a: Any) -> Dict[str, str]:
|
|
392
|
+
if not isinstance(a, dict):
|
|
393
|
+
raise ValueError(f"Expected Dict[str, str], got {a!r}")
|
|
394
|
+
for key, value in a.items():
|
|
395
|
+
if not isinstance(key, str) or not isinstance(value, str):
|
|
396
|
+
raise ValueError(f"Expected Dict[str, str], got {a!r}")
|
|
397
|
+
return a
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _parse_seconds(a: Any) -> float:
|
|
401
|
+
"""
|
|
402
|
+
Parse a duration string into a float representing the number of seconds.
|
|
403
|
+
|
|
404
|
+
The duration type is encoded as a string rather than an where the string
|
|
405
|
+
ends in the suffix "s" (indicating seconds) and is preceded by a decimal
|
|
406
|
+
number of seconds. For example, "3.000000001s", represents a duration of
|
|
407
|
+
3 seconds and 1 nanosecond.
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
if not isinstance(a, str):
|
|
411
|
+
raise ValueError(f"Expected string, got {a!r}")
|
|
412
|
+
|
|
413
|
+
match = re.match(r"^(\d+(\.\d+)?)s$", a)
|
|
414
|
+
if match:
|
|
415
|
+
return float(match.group(1))
|
|
416
|
+
|
|
417
|
+
raise ValueError(f"Expected duration string, got {a!r}")
|
databricks/sdk/errors/mapper.py
CHANGED
|
@@ -11,7 +11,7 @@ def _error_mapper(response: requests.Response, raw: dict) -> DatabricksError:
|
|
|
11
11
|
if override.matches(response, raw):
|
|
12
12
|
return override.custom_error(**raw)
|
|
13
13
|
status_code = response.status_code
|
|
14
|
-
error_code = raw.get(
|
|
14
|
+
error_code = raw.get("error_code", None)
|
|
15
15
|
if error_code in platform.ERROR_CODE_MAPPING:
|
|
16
16
|
# more specific error codes override more generic HTTP status codes
|
|
17
17
|
return platform.ERROR_CODE_MAPPING[error_code](**raw)
|
|
@@ -6,28 +6,31 @@ from .base import _ErrorOverride
|
|
|
6
6
|
from .platform import ResourceDoesNotExist
|
|
7
7
|
|
|
8
8
|
_ALL_OVERRIDES = [
|
|
9
|
-
_ErrorOverride(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
9
|
+
_ErrorOverride(
|
|
10
|
+
debug_name="Clusters InvalidParameterValue=>ResourceDoesNotExist",
|
|
11
|
+
path_regex=re.compile(r"^/api/2\.\d/clusters/get"),
|
|
12
|
+
verb="GET",
|
|
13
|
+
status_code_matcher=re.compile(r"^400$"),
|
|
14
|
+
error_code_matcher=re.compile(r"INVALID_PARAMETER_VALUE"),
|
|
15
|
+
message_matcher=re.compile(r"Cluster .* does not exist"),
|
|
16
|
+
custom_error=ResourceDoesNotExist,
|
|
17
|
+
),
|
|
18
|
+
_ErrorOverride(
|
|
19
|
+
debug_name="Jobs InvalidParameterValue=>ResourceDoesNotExist",
|
|
20
|
+
path_regex=re.compile(r"^/api/2\.\d/jobs/get"),
|
|
21
|
+
verb="GET",
|
|
22
|
+
status_code_matcher=re.compile(r"^400$"),
|
|
23
|
+
error_code_matcher=re.compile(r"INVALID_PARAMETER_VALUE"),
|
|
24
|
+
message_matcher=re.compile(r"Job .* does not exist"),
|
|
25
|
+
custom_error=ResourceDoesNotExist,
|
|
26
|
+
),
|
|
27
|
+
_ErrorOverride(
|
|
28
|
+
debug_name="Job Runs InvalidParameterValue=>ResourceDoesNotExist",
|
|
29
|
+
path_regex=re.compile(r"^/api/2\.\d/jobs/runs/get"),
|
|
30
|
+
verb="GET",
|
|
31
|
+
status_code_matcher=re.compile(r"^400$"),
|
|
32
|
+
error_code_matcher=re.compile(r"INVALID_PARAMETER_VALUE"),
|
|
33
|
+
message_matcher=re.compile(r"(Run .* does not exist|Run: .* in job: .* doesn\'t exist)"),
|
|
34
|
+
custom_error=ResourceDoesNotExist,
|
|
35
|
+
),
|
|
33
36
|
]
|
databricks/sdk/errors/parser.py
CHANGED
|
@@ -26,7 +26,9 @@ _error_deserializers = [
|
|
|
26
26
|
# A list of _ErrorCustomizers that are applied to the error arguments after they are parsed. Customizers can modify the
|
|
27
27
|
# error arguments in any way, including adding or removing fields. Customizers are applied in order, so later
|
|
28
28
|
# customizers can override the changes made by earlier customizers.
|
|
29
|
-
_error_customizers = [
|
|
29
|
+
_error_customizers = [
|
|
30
|
+
_RetryAfterCustomizer(),
|
|
31
|
+
]
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def _unknown_error(response: requests.Response) -> str:
|
|
@@ -36,9 +38,10 @@ def _unknown_error(response: requests.Response) -> str:
|
|
|
36
38
|
"""
|
|
37
39
|
request_log = RoundTrip(response, debug_headers=True, debug_truncate_bytes=10 * 1024).generate()
|
|
38
40
|
return (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
f
|
|
41
|
+
"This is likely a bug in the Databricks SDK for Python or the underlying "
|
|
42
|
+
"API. Please report this issue with the following debugging information to the SDK issue tracker at "
|
|
43
|
+
f"https://github.com/databricks/databricks-sdk-go/issues. Request log:```{request_log}```"
|
|
44
|
+
)
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
class _Parser:
|
|
@@ -49,13 +52,15 @@ class _Parser:
|
|
|
49
52
|
issue tracker.
|
|
50
53
|
"""
|
|
51
54
|
|
|
52
|
-
def __init__(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
self.
|
|
58
|
-
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
extra_error_parsers: List[_ErrorDeserializer] = [],
|
|
58
|
+
extra_error_customizers: List[_ErrorCustomizer] = [],
|
|
59
|
+
):
|
|
60
|
+
self._error_parsers = _error_deserializers + (extra_error_parsers if extra_error_parsers is not None else [])
|
|
61
|
+
self._error_customizers = _error_customizers + (
|
|
62
|
+
extra_error_customizers if extra_error_customizers is not None else []
|
|
63
|
+
)
|
|
59
64
|
|
|
60
65
|
def get_api_error(self, response: requests.Response) -> Optional[DatabricksError]:
|
|
61
66
|
"""
|
|
@@ -73,11 +78,18 @@ class _Parser:
|
|
|
73
78
|
customizer.customize_error(response, error_args)
|
|
74
79
|
return _error_mapper(response, error_args)
|
|
75
80
|
except Exception as e:
|
|
76
|
-
logging.debug(
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
logging.debug(
|
|
82
|
+
f"Error parsing response with {parser}, continuing",
|
|
83
|
+
exc_info=e,
|
|
84
|
+
)
|
|
85
|
+
return _error_mapper(
|
|
86
|
+
response,
|
|
87
|
+
{"message": "unable to parse response. " + _unknown_error(response)},
|
|
88
|
+
)
|
|
79
89
|
|
|
80
90
|
# Private link failures happen via a redirect to the login page. From a requests-perspective, the request
|
|
81
91
|
# is successful, but the response is not what we expect. We need to handle this case separately.
|
|
82
92
|
if _is_private_link_redirect(response):
|
|
83
93
|
return _get_private_link_validation_error(response.url)
|
|
94
|
+
|
|
95
|
+
return None
|
|
@@ -103,14 +103,14 @@ STATUS_CODE_MAPPING = {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
ERROR_CODE_MAPPING = {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
"INVALID_STATE": InvalidState,
|
|
107
|
+
"INVALID_PARAMETER_VALUE": InvalidParameterValue,
|
|
108
|
+
"RESOURCE_DOES_NOT_EXIST": ResourceDoesNotExist,
|
|
109
|
+
"ABORTED": Aborted,
|
|
110
|
+
"ALREADY_EXISTS": AlreadyExists,
|
|
111
|
+
"RESOURCE_ALREADY_EXISTS": ResourceAlreadyExists,
|
|
112
|
+
"RESOURCE_EXHAUSTED": ResourceExhausted,
|
|
113
|
+
"REQUEST_LIMIT_EXCEEDED": RequestLimitExceeded,
|
|
114
|
+
"UNKNOWN": Unknown,
|
|
115
|
+
"DATA_LOSS": DataLoss,
|
|
116
116
|
}
|
|
@@ -15,29 +15,28 @@ class _PrivateLinkInfo:
|
|
|
15
15
|
|
|
16
16
|
def error_message(self):
|
|
17
17
|
return (
|
|
18
|
-
f
|
|
19
|
-
f
|
|
20
|
-
f
|
|
18
|
+
f"The requested workspace has {self.serviceName} enabled and is not accessible from the current network. "
|
|
19
|
+
f"Ensure that {self.serviceName} is properly configured and that your device has access to the "
|
|
20
|
+
f"{self.endpointName}. For more information, see {self.referencePage}."
|
|
21
|
+
)
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
_private_link_info_map = {
|
|
24
|
-
Cloud.AWS:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Cloud.AZURE:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
Cloud.AWS: _PrivateLinkInfo(
|
|
26
|
+
serviceName="AWS PrivateLink",
|
|
27
|
+
endpointName="AWS VPC endpoint",
|
|
28
|
+
referencePage="https://docs.databricks.com/en/security/network/classic/privatelink.html",
|
|
29
|
+
),
|
|
30
|
+
Cloud.AZURE: _PrivateLinkInfo(
|
|
31
|
+
serviceName="Azure Private Link",
|
|
32
|
+
endpointName="Azure Private Link endpoint",
|
|
33
|
+
referencePage="https://learn.microsoft.com/en-us/azure/databricks/security/network/classic/private-link-standard#authentication-troubleshooting",
|
|
34
|
+
),
|
|
35
|
+
Cloud.GCP: _PrivateLinkInfo(
|
|
36
|
+
serviceName="Private Service Connect",
|
|
37
|
+
endpointName="GCP VPC endpoint",
|
|
38
|
+
referencePage="https://docs.gcp.databricks.com/en/security/network/classic/private-service-connect.html",
|
|
34
39
|
),
|
|
35
|
-
Cloud.GCP:
|
|
36
|
-
_PrivateLinkInfo(
|
|
37
|
-
serviceName='Private Service Connect',
|
|
38
|
-
endpointName='GCP VPC endpoint',
|
|
39
|
-
referencePage='https://docs.gcp.databricks.com/en/security/network/classic/private-service-connect.html',
|
|
40
|
-
)
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
|
|
@@ -48,13 +47,14 @@ class PrivateLinkValidationError(PermissionDenied):
|
|
|
48
47
|
|
|
49
48
|
def _is_private_link_redirect(resp: requests.Response) -> bool:
|
|
50
49
|
parsed = parse.urlparse(resp.url)
|
|
51
|
-
return parsed.path ==
|
|
50
|
+
return parsed.path == "/login.html" and "error=private-link-validation-error" in parsed.query
|
|
52
51
|
|
|
53
52
|
|
|
54
53
|
def _get_private_link_validation_error(url: str) -> PrivateLinkValidationError:
|
|
55
54
|
parsed = parse.urlparse(url)
|
|
56
55
|
env = get_environment_for_hostname(parsed.hostname)
|
|
57
|
-
return PrivateLinkValidationError(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
return PrivateLinkValidationError(
|
|
57
|
+
message=_private_link_info_map[env.cloud].error_message(),
|
|
58
|
+
error_code="PRIVATE_LINK_VALIDATION_ERROR",
|
|
59
|
+
status_code=403,
|
|
60
|
+
)
|