From b16641d3017ed7cbc8df1c485f355ae548858581 Mon Sep 17 00:00:00 2001 From: Olzhas Arystanov Date: Tue, 17 Feb 2026 00:33:47 +0500 Subject: [PATCH] Avoid http-level retries during upload requests Pass try_count=1 to b2http during upload requests to force token refresh on errors. This change also eliminates the need for a previous B2RequestTimeoutDuringUpload workaround, so it's removed. The exception itself is preserved, as it's a part of the public API. --- b2sdk/_internal/b2http.py | 24 +++++++++---------- b2sdk/_internal/raw_api.py | 16 ++++++++++--- .../+b2http-upload-no-retries.fixed.md | 1 + 3 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 changelog.d/+b2http-upload-no-retries.fixed.md diff --git a/b2sdk/_internal/b2http.py b/b2sdk/_internal/b2http.py index 2f0df66a..ddac7521 100644 --- a/b2sdk/_internal/b2http.py +++ b/b2sdk/_internal/b2http.py @@ -36,7 +36,6 @@ B2ConnectionError, B2Error, B2RequestTimeout, - B2RequestTimeoutDuringUpload, BadDateFormat, BrokenPipe, ClockSkew, @@ -254,7 +253,7 @@ def request( :param url: a URL to call :param headers: headers to send. :param data: raw bytes or a file-like object to send - :param try_count: a number of retries + :param try_count: a number of attempts :param params: a dict that will be converted to query string for GET requests or additional metadata for POST requests :param stream: if True, the response will be streamed :param _timeout: a timeout for the request in seconds if not default @@ -355,14 +354,9 @@ def post_content_return_json( :param data: a file-like object to send :return: a dict that is the decoded JSON """ - try: - return self.request_content_return_json( - 'POST', url, headers, data, try_count, post_params, _timeout=_timeout - ) - except B2RequestTimeout: - # this forces a token refresh, which is necessary if request is still alive - # on the server but has terminated for some reason on the client. See #79 - raise B2RequestTimeoutDuringUpload() + return self.request_content_return_json( + 'POST', url, headers, data, try_count, post_params, _timeout=_timeout + ) def post_json_return_json(self, url, headers, params, try_count: int = TRY_COUNT_OTHER): """ @@ -423,7 +417,7 @@ def get_content(self, url, headers, try_count: int = TRY_COUNT_DOWNLOAD): :param str url: a URL to call :param dict headers: headers to send - :param int try_count: a number or retries + :param int try_count: a number of attempts :return: Context manager that returns an object that supports iter_content() """ response = self.request( @@ -456,7 +450,7 @@ def head_content( :param str url: a URL to call :param dict headers: headers to send - :param int try_count: a number or retries + :param int try_count: a number of attempts :return: HTTP response """ return self.request('HEAD', url, headers=headers, try_count=try_count) @@ -586,9 +580,13 @@ def _translate_and_retry( the exception is a retryable B2Error. :param fcn: request function to call - :param try_count: a number of retries + :param try_count: a number of attempts :param post_params: request parameters """ + + if try_count < 1: + raise ValueError('try_count must be >= 1') + # For all but the last try, catch the exception. wait_time = 1.0 max_wait_time = 64 diff --git a/b2sdk/_internal/raw_api.py b/b2sdk/_internal/raw_api.py index 93446c92..639fc91a 100644 --- a/b2sdk/_internal/raw_api.py +++ b/b2sdk/_internal/raw_api.py @@ -564,7 +564,7 @@ def _get_json(self, base_url: str, endpoint: str, auth: str, **params) -> JSON: def authorize_account(self, realm_url, application_key_id, application_key): auth = ( - f"Basic {base64.b64encode(f'{application_key_id}:{application_key}'.encode()).decode()}" + f'Basic {base64.b64encode(f"{application_key_id}:{application_key}".encode()).decode()}' ) return self._post_json(realm_url, 'b2_authorize_account', auth) @@ -1071,7 +1071,12 @@ def upload_file( legal_hold=legal_hold, custom_upload_timestamp=custom_upload_timestamp, ) - return self.b2_http.post_content_return_json(upload_url, headers, data_stream) + return self.b2_http.post_content_return_json( + upload_url, + headers, + data_stream, + try_count=1, + ) def upload_part( self, @@ -1097,7 +1102,12 @@ def upload_part( ) server_side_encryption.add_to_upload_headers(headers) - return self.b2_http.post_content_return_json(upload_url, headers, data_stream) + return self.b2_http.post_content_return_json( + upload_url, + headers, + data_stream, + try_count=1, + ) def copy_file( self, diff --git a/changelog.d/+b2http-upload-no-retries.fixed.md b/changelog.d/+b2http-upload-no-retries.fixed.md new file mode 100644 index 00000000..7dbbfcd5 --- /dev/null +++ b/changelog.d/+b2http-upload-no-retries.fixed.md @@ -0,0 +1 @@ +Avoid http-level retries during upload requests. \ No newline at end of file