From a21e315a1a4393e2a21528e23a2eb1de12ac5bde Mon Sep 17 00:00:00 2001 From: Felipe Fumero Date: Mon, 28 Sep 2026 11:27:55 -0600 Subject: [PATCH] fix: retry request timeout upload responses --- b2sdk/_internal/exception.py | 6 ++++++ changelog.d/605.fixed.md | 1 + test/unit/test_exception.py | 8 +++++++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 changelog.d/605.fixed.md diff --git a/b2sdk/_internal/exception.py b/b2sdk/_internal/exception.py index aef410f37..15bf2d899 100644 --- a/b2sdk/_internal/exception.py +++ b/b2sdk/_internal/exception.py @@ -434,6 +434,10 @@ class ServiceError(TransientErrorMixin, B2Error): """ +class RequestTimeout(TransientErrorMixin, B2Error): + """Used for HTTP status code 408 (request timeout).""" + + class CapExceeded(B2Error): def __str__(self): return 'Cap exceeded.' @@ -750,6 +754,8 @@ def interpret_b2_error( return Conflict() elif status == 416 and code == 'range_not_satisfiable': return UnsatisfiableRange() + elif status == 408: + return RequestTimeout('%d %s %s' % (status, code, message)) elif status == 429: return TooManyRequests(retry_after_seconds=response_headers.get('retry-after')) elif 500 <= status < 600: diff --git a/changelog.d/605.fixed.md b/changelog.d/605.fixed.md new file mode 100644 index 000000000..47d11f87e --- /dev/null +++ b/changelog.d/605.fixed.md @@ -0,0 +1 @@ +Retry uploads after a B2 request-timeout response (HTTP 408). diff --git a/test/unit/test_exception.py b/test/unit/test_exception.py index 258e301a4..19e2ce819 100644 --- a/test/unit/test_exception.py +++ b/test/unit/test_exception.py @@ -35,7 +35,7 @@ interpret_b2_error, ) -from b2sdk._internal.exception import ResourceNotFound +from b2sdk._internal.exception import RequestTimeout, ResourceNotFound class TestB2Error: @@ -161,6 +161,12 @@ def test_service_error(self): assert isinstance(error, ServiceError) assert '500 code message' == str(error) + def test_request_timeout(self): + error = interpret_b2_error(408, 'request_timeout', 'request timeout', {}) + assert isinstance(error, RequestTimeout) + assert error.should_retry_upload() + assert error.should_retry_http() + def test_unknown_error(self): error = interpret_b2_error(499, 'code', 'message', {}) assert isinstance(error, UnknownError)