databricks-sdk 0.44.1__py3-none-any.whl → 0.46.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.

Files changed (63) hide show
  1. databricks/sdk/__init__.py +135 -116
  2. databricks/sdk/_base_client.py +112 -88
  3. databricks/sdk/_property.py +12 -7
  4. databricks/sdk/_widgets/__init__.py +13 -2
  5. databricks/sdk/_widgets/default_widgets_utils.py +21 -15
  6. databricks/sdk/_widgets/ipywidgets_utils.py +47 -24
  7. databricks/sdk/azure.py +8 -6
  8. databricks/sdk/casing.py +5 -5
  9. databricks/sdk/config.py +156 -99
  10. databricks/sdk/core.py +57 -47
  11. databricks/sdk/credentials_provider.py +306 -206
  12. databricks/sdk/data_plane.py +75 -50
  13. databricks/sdk/dbutils.py +123 -87
  14. databricks/sdk/environments.py +52 -35
  15. databricks/sdk/errors/base.py +61 -35
  16. databricks/sdk/errors/customizer.py +3 -3
  17. databricks/sdk/errors/deserializer.py +38 -25
  18. databricks/sdk/errors/details.py +417 -0
  19. databricks/sdk/errors/mapper.py +1 -1
  20. databricks/sdk/errors/overrides.py +27 -24
  21. databricks/sdk/errors/parser.py +26 -14
  22. databricks/sdk/errors/platform.py +10 -10
  23. databricks/sdk/errors/private_link.py +24 -24
  24. databricks/sdk/logger/round_trip_logger.py +28 -20
  25. databricks/sdk/mixins/compute.py +90 -60
  26. databricks/sdk/mixins/files.py +815 -145
  27. databricks/sdk/mixins/jobs.py +191 -16
  28. databricks/sdk/mixins/open_ai_client.py +26 -20
  29. databricks/sdk/mixins/workspace.py +45 -34
  30. databricks/sdk/oauth.py +379 -198
  31. databricks/sdk/retries.py +14 -12
  32. databricks/sdk/runtime/__init__.py +34 -17
  33. databricks/sdk/runtime/dbutils_stub.py +52 -39
  34. databricks/sdk/service/_internal.py +12 -7
  35. databricks/sdk/service/apps.py +618 -418
  36. databricks/sdk/service/billing.py +827 -604
  37. databricks/sdk/service/catalog.py +6552 -4474
  38. databricks/sdk/service/cleanrooms.py +550 -388
  39. databricks/sdk/service/compute.py +5263 -3536
  40. databricks/sdk/service/dashboards.py +1331 -924
  41. databricks/sdk/service/files.py +446 -309
  42. databricks/sdk/service/iam.py +2115 -1483
  43. databricks/sdk/service/jobs.py +4151 -2588
  44. databricks/sdk/service/marketplace.py +2210 -1517
  45. databricks/sdk/service/ml.py +3839 -2256
  46. databricks/sdk/service/oauth2.py +910 -584
  47. databricks/sdk/service/pipelines.py +1865 -1203
  48. databricks/sdk/service/provisioning.py +1435 -1029
  49. databricks/sdk/service/serving.py +2060 -1290
  50. databricks/sdk/service/settings.py +2846 -1929
  51. databricks/sdk/service/sharing.py +2201 -877
  52. databricks/sdk/service/sql.py +4650 -3103
  53. databricks/sdk/service/vectorsearch.py +816 -550
  54. databricks/sdk/service/workspace.py +1330 -906
  55. databricks/sdk/useragent.py +36 -22
  56. databricks/sdk/version.py +1 -1
  57. {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.46.0.dist-info}/METADATA +31 -31
  58. databricks_sdk-0.46.0.dist-info/RECORD +70 -0
  59. {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.46.0.dist-info}/WHEEL +1 -1
  60. databricks_sdk-0.44.1.dist-info/RECORD +0 -69
  61. {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.46.0.dist-info}/LICENSE +0 -0
  62. {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.46.0.dist-info}/NOTICE +0 -0
  63. {databricks_sdk-0.44.1.dist-info → databricks_sdk-0.46.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}")
@@ -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('error_code', None)
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(debug_name="Clusters InvalidParameterValue=>ResourceDoesNotExist",
10
- path_regex=re.compile(r'^/api/2\.\d/clusters/get'),
11
- verb="GET",
12
- status_code_matcher=re.compile(r'^400$'),
13
- error_code_matcher=re.compile(r'INVALID_PARAMETER_VALUE'),
14
- message_matcher=re.compile(r'Cluster .* does not exist'),
15
- custom_error=ResourceDoesNotExist,
16
- ),
17
- _ErrorOverride(debug_name="Jobs InvalidParameterValue=>ResourceDoesNotExist",
18
- path_regex=re.compile(r'^/api/2\.\d/jobs/get'),
19
- verb="GET",
20
- status_code_matcher=re.compile(r'^400$'),
21
- error_code_matcher=re.compile(r'INVALID_PARAMETER_VALUE'),
22
- message_matcher=re.compile(r'Job .* does not exist'),
23
- custom_error=ResourceDoesNotExist,
24
- ),
25
- _ErrorOverride(debug_name="Job Runs InvalidParameterValue=>ResourceDoesNotExist",
26
- path_regex=re.compile(r'^/api/2\.\d/jobs/runs/get'),
27
- verb="GET",
28
- status_code_matcher=re.compile(r'^400$'),
29
- error_code_matcher=re.compile(r'INVALID_PARAMETER_VALUE'),
30
- message_matcher=re.compile(r'(Run .* does not exist|Run: .* in job: .* doesn\'t exist)'),
31
- custom_error=ResourceDoesNotExist,
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
  ]
@@ -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 = [_RetryAfterCustomizer(), ]
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
- 'This is likely a bug in the Databricks SDK for Python or the underlying '
40
- 'API. Please report this issue with the following debugging information to the SDK issue tracker at '
41
- f'https://github.com/databricks/databricks-sdk-go/issues. Request log:```{request_log}```')
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__(self,
53
- extra_error_parsers: List[_ErrorDeserializer] = [],
54
- extra_error_customizers: List[_ErrorCustomizer] = []):
55
- self._error_parsers = _error_deserializers + (extra_error_parsers
56
- if extra_error_parsers is not None else [])
57
- self._error_customizers = _error_customizers + (extra_error_customizers
58
- if extra_error_customizers is not None else [])
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(f'Error parsing response with {parser}, continuing', exc_info=e)
77
- return _error_mapper(response,
78
- {'message': 'unable to parse response. ' + _unknown_error(response)})
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
- '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,
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'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}.')
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
- _PrivateLinkInfo(serviceName='AWS PrivateLink',
26
- endpointName='AWS VPC endpoint',
27
- referencePage='https://docs.databricks.com/en/security/network/classic/privatelink.html',
28
- ),
29
- Cloud.AZURE:
30
- _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',
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 == '/login.html' and 'error=private-link-validation-error' in parsed.query
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(message=_private_link_info_map[env.cloud].error_message(),
58
- error_code='PRIVATE_LINK_VALIDATION_ERROR',
59
- status_code=403,
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
+ )