Skip to content

Commit fb80382

Browse files
authored
Merge branch 'databricks:main' into main
2 parents e73525b + f06bb27 commit fb80382

File tree

15 files changed

+414
-251
lines changed

15 files changed

+414
-251
lines changed

.codegen/__init__.py.tmpl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ class WorkspaceClient:
4141
"""
4242
The WorkspaceClient is a client for the workspace-level Databricks REST API.
4343
"""
44-
def __init__(self, *{{range $args}}, {{.}}: str = None{{end}},
45-
debug_truncate_bytes: int = None,
46-
debug_headers: bool = None,
44+
def __init__(self, *{{range $args}}, {{.}}: Optional[str] = None{{end}},
45+
debug_truncate_bytes: Optional[int] = None,
46+
debug_headers: Optional[bool] = None,
4747
product="unknown",
4848
product_version="0.0.0",
49-
credentials_strategy: CredentialsStrategy = None,
50-
credentials_provider: CredentialsStrategy = None,
51-
config: client.Config = None):
49+
credentials_strategy: Optional[CredentialsStrategy] = None,
50+
credentials_provider: Optional[CredentialsStrategy] = None,
51+
config: Optional[client.Config] = None):
5252
if not config:
5353
config = client.Config({{range $args}}{{.}}={{.}}, {{end}}
5454
credentials_strategy=credentials_strategy,
@@ -110,14 +110,14 @@ class AccountClient:
110110
The AccountClient is a client for the account-level Databricks REST API.
111111
"""
112112

113-
def __init__(self, *{{range $args}}, {{.}}: str = None{{end}},
114-
debug_truncate_bytes: int = None,
115-
debug_headers: bool = None,
113+
def __init__(self, *{{range $args}}, {{.}}: Optional[str] = None{{end}},
114+
debug_truncate_bytes: Optional[int] = None,
115+
debug_headers: Optional[bool] = None,
116116
product="unknown",
117117
product_version="0.0.0",
118-
credentials_strategy: CredentialsStrategy = None,
119-
credentials_provider: CredentialsStrategy = None,
120-
config: client.Config = None):
118+
credentials_strategy: Optional[CredentialsStrategy] = None,
119+
credentials_provider: Optional[CredentialsStrategy] = None,
120+
config: Optional[client.Config] = None):
121121
if not config:
122122
config = client.Config({{range $args}}{{.}}={{.}}, {{end}}
123123
credentials_strategy=credentials_strategy,

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Version changelog
22

3+
## [Release] Release v0.32.1
4+
5+
### Bug Fixes
6+
7+
* Properly include message when handing SCIM errors ([#753](https://github.com/databricks/databricks-sdk-py/pull/753)).
8+
9+
10+
311
## [Release] Release v0.32.0
412

513
### Bug Fixes

databricks/sdk/__init__.py

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

databricks/sdk/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,11 @@ class Config:
9292
def __init__(self,
9393
*,
9494
# Deprecated. Use credentials_strategy instead.
95-
credentials_provider: CredentialsStrategy = None,
96-
credentials_strategy: CredentialsStrategy = None,
95+
credentials_provider: Optional[CredentialsStrategy] = None,
96+
credentials_strategy: Optional[CredentialsStrategy] = None,
9797
product=None,
9898
product_version=None,
99-
clock: Clock = None,
99+
clock: Optional[Clock] = None,
100100
**kwargs):
101101
self._header_factory = None
102102
self._inner = {}

databricks/sdk/core.py

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .config import *
1111
# To preserve backwards compatibility (as these definitions were previously in this module)
1212
from .credentials_provider import *
13-
from .errors import DatabricksError, get_api_error
13+
from .errors import DatabricksError, _ErrorCustomizer, _Parser
1414
from .logger import RoundTrip
1515
from .oauth import retrieve_token
1616
from .retries import retried
@@ -71,6 +71,8 @@ def __init__(self, cfg: Config = None):
7171
# Default to 60 seconds
7272
self._http_timeout_seconds = cfg.http_timeout_seconds if cfg.http_timeout_seconds else 60
7373

74+
self._error_parser = _Parser(extra_error_customizers=[_AddDebugErrorCustomizer(cfg)])
75+
7476
@property
7577
def account_id(self) -> str:
7678
return self._cfg.account_id
@@ -219,27 +221,6 @@ def _is_retryable(err: BaseException) -> Optional[str]:
219221
return f'matched {substring}'
220222
return None
221223

222-
@classmethod
223-
def _parse_retry_after(cls, response: requests.Response) -> Optional[int]:
224-
retry_after = response.headers.get("Retry-After")
225-
if retry_after is None:
226-
# 429 requests should include a `Retry-After` header, but if it's missing,
227-
# we default to 1 second.
228-
return cls._RETRY_AFTER_DEFAULT
229-
# If the request is throttled, try parse the `Retry-After` header and sleep
230-
# for the specified number of seconds. Note that this header can contain either
231-
# an integer or a RFC1123 datetime string.
232-
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
233-
#
234-
# For simplicity, we only try to parse it as an integer, as this is what Databricks
235-
# platform returns. Otherwise, we fall back and don't sleep.
236-
try:
237-
return int(retry_after)
238-
except ValueError:
239-
logger.debug(f'Invalid Retry-After header received: {retry_after}. Defaulting to 1')
240-
# defaulting to 1 sleep second to make self._is_retryable() simpler
241-
return cls._RETRY_AFTER_DEFAULT
242-
243224
def _perform(self,
244225
method: str,
245226
url: str,
@@ -261,15 +242,8 @@ def _perform(self,
261242
stream=raw,
262243
timeout=self._http_timeout_seconds)
263244
self._record_request_log(response, raw=raw or data is not None or files is not None)
264-
error = get_api_error(response)
245+
error = self._error_parser.get_api_error(response)
265246
if error is not None:
266-
status_code = response.status_code
267-
is_http_unauthorized_or_forbidden = status_code in (401, 403)
268-
is_too_many_requests_or_unavailable = status_code in (429, 503)
269-
if is_http_unauthorized_or_forbidden:
270-
error.message = self._cfg.wrap_debug_info(error.message)
271-
if is_too_many_requests_or_unavailable:
272-
error.retry_after_secs = self._parse_retry_after(response)
273247
raise error from None
274248
return response
275249

@@ -279,6 +253,19 @@ def _record_request_log(self, response: requests.Response, raw: bool = False) ->
279253
logger.debug(RoundTrip(response, self._cfg.debug_headers, self._debug_truncate_bytes, raw).generate())
280254

281255

256+
class _AddDebugErrorCustomizer(_ErrorCustomizer):
257+
"""An error customizer that adds debug information about the configuration to unauthenticated and
258+
unauthorized errors."""
259+
260+
def __init__(self, cfg: Config):
261+
self._cfg = cfg
262+
263+
def customize_error(self, response: requests.Response, kwargs: dict):
264+
if response.status_code in (401, 403):
265+
message = kwargs.get('message', 'request failed')
266+
kwargs['message'] = self._cfg.wrap_debug_info(message)
267+
268+
282269
class StreamingResponse(BinaryIO):
283270
_response: requests.Response
284271
_buffer: bytes

databricks/sdk/errors/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .base import DatabricksError, ErrorDetail
2-
from .mapper import _error_mapper
3-
from .parser import get_api_error
2+
from .customizer import _ErrorCustomizer
3+
from .parser import _Parser
44
from .platform import *
55
from .private_link import PrivateLinkValidationError
66
from .sdk import *
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import abc
2+
import logging
3+
4+
import requests
5+
6+
7+
class _ErrorCustomizer(abc.ABC):
8+
"""A customizer for errors from the Databricks REST API."""
9+
10+
@abc.abstractmethod
11+
def customize_error(self, response: requests.Response, kwargs: dict):
12+
"""Customize the error constructor parameters."""
13+
14+
15+
class _RetryAfterCustomizer(_ErrorCustomizer):
16+
"""An error customizer that sets the retry_after_secs parameter based on the Retry-After header."""
17+
18+
_DEFAULT_RETRY_AFTER_SECONDS = 1
19+
"""The default number of seconds to wait before retrying a request if the Retry-After header is missing or is not
20+
a valid integer."""
21+
22+
@classmethod
23+
def _parse_retry_after(cls, response: requests.Response) -> int:
24+
retry_after = response.headers.get("Retry-After")
25+
if retry_after is None:
26+
logging.debug(
27+
f'No Retry-After header received in response with status code 429 or 503. Defaulting to {cls._DEFAULT_RETRY_AFTER_SECONDS}'
28+
)
29+
# 429 requests should include a `Retry-After` header, but if it's missing,
30+
# we default to 1 second.
31+
return cls._DEFAULT_RETRY_AFTER_SECONDS
32+
# If the request is throttled, try parse the `Retry-After` header and sleep
33+
# for the specified number of seconds. Note that this header can contain either
34+
# an integer or a RFC1123 datetime string.
35+
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
36+
#
37+
# For simplicity, we only try to parse it as an integer, as this is what Databricks
38+
# platform returns. Otherwise, we fall back and don't sleep.
39+
try:
40+
return int(retry_after)
41+
except ValueError:
42+
logging.debug(
43+
f'Invalid Retry-After header received: {retry_after}. Defaulting to {cls._DEFAULT_RETRY_AFTER_SECONDS}'
44+
)
45+
# defaulting to 1 sleep second to make self._is_retryable() simpler
46+
return cls._DEFAULT_RETRY_AFTER_SECONDS
47+
48+
def customize_error(self, response: requests.Response, kwargs: dict):
49+
if response.status_code in (429, 503):
50+
kwargs['retry_after_secs'] = self._parse_retry_after(response)

0 commit comments

Comments
 (0)