Skip to content

Commit

Permalink
Merge branch 'main' into fix_oauth_token_cache
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-wang-1990 authored Nov 19, 2024
2 parents 8d23372 + 197b5f9 commit 7580ecf
Show file tree
Hide file tree
Showing 32 changed files with 608 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .codegen/_openapi_sha
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5285ce76f81314f342c1702d5c2ad4ef42488781
f2385add116e3716c8a90a0b68e204deb40f996c
61 changes: 49 additions & 12 deletions databricks/sdk/_base_client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import io
import logging
import urllib.parse
from datetime import timedelta
Expand Down Expand Up @@ -130,6 +131,14 @@ def flatten_dict(d: Dict[str, Any]) -> Dict[str, Any]:
flattened = dict(flatten_dict(with_fixed_bools))
return flattened

@staticmethod
def _is_seekable_stream(data) -> bool:
if data is None:
return False
if not isinstance(data, io.IOBase):
return False
return data.seekable()

def do(self,
method: str,
url: str,
Expand All @@ -144,18 +153,31 @@ def do(self,
if headers is None:
headers = {}
headers['User-Agent'] = self._user_agent_base
retryable = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
is_retryable=self._is_retryable,
clock=self._clock)
response = retryable(self._perform)(method,
url,
query=query,
headers=headers,
body=body,
raw=raw,
files=files,
data=data,
auth=auth)

# Wrap strings and bytes in a seekable stream so that we can rewind them.
if isinstance(data, (str, bytes)):
data = io.BytesIO(data.encode('utf-8') if isinstance(data, str) else data)

# Only retry if the request is not a stream or if the stream is seekable and
# we can rewind it. This is necessary to avoid bugs where the retry doesn't
# re-read already read data from the body.
if data is not None and not self._is_seekable_stream(data):
logger.debug(f"Retry disabled for non-seekable stream: type={type(data)}")
call = self._perform
else:
call = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
is_retryable=self._is_retryable,
clock=self._clock)(self._perform)

response = call(method,
url,
query=query,
headers=headers,
body=body,
raw=raw,
files=files,
data=data,
auth=auth)

resp = dict()
for header in response_headers if response_headers else []:
Expand Down Expand Up @@ -226,6 +248,12 @@ def _perform(self,
files=None,
data=None,
auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None):
# Keep track of the initial position of the stream so that we can rewind it if
# we need to retry the request.
initial_data_position = 0
if self._is_seekable_stream(data):
initial_data_position = data.tell()

response = self._session.request(method,
url,
params=self._fix_query_string(query),
Expand All @@ -237,9 +265,18 @@ def _perform(self,
stream=raw,
timeout=self._http_timeout_seconds)
self._record_request_log(response, raw=raw or data is not None or files is not None)

error = self._error_parser.get_api_error(response)
if error is not None:
# If the request body is a seekable stream, rewind it so that it is ready
# to be read again in case of a retry.
#
# TODO: This should be moved into a "before-retry" hook to avoid one
# unnecessary seek on the last failed retry before aborting.
if self._is_seekable_stream(data):
data.seek(initial_data_position)
raise error from None

return response

def _record_request_log(self, response: requests.Response, raw: bool = False) -> None:
Expand Down
14 changes: 10 additions & 4 deletions databricks/sdk/service/apps.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion databricks/sdk/service/billing.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7580ecf

Please sign in to comment.