diff --git a/CHANGELOG.md b/CHANGELOG.md index c0f7d729..faac2163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.42.12 + +### Enhancements + +### Features + +### Fixes +* Retry on all `httpx.TransportError` subclasses (including `ReadError`, `WriteError`, `ConnectError`, `RemoteProtocolError`, and all timeout types) when `retry_connection_errors=True`. Previously only `ConnectError`, `RemoteProtocolError`, and `TimeoutException` were retried — `ReadError` (TCP connection reset mid-response) was treated as permanent. + ## 0.42.11 ### Enhancements diff --git a/RELEASES.md b/RELEASES.md index 2dbc5a78..502af03e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1201,3 +1201,13 @@ Based on: - [python v0.42.11] . ### Releases - [PyPI v0.42.11] https://pypi.org/project/unstructured-client/0.42.11 - . + +## 2026-03-25 20:30:00 +### Changes +Based on: +- OpenAPI Doc +- Speakeasy CLI 1.601.0 (2.680.0) https://github.com/speakeasy-api/speakeasy +### Generated +- [python v0.42.12] . +### Releases +- [PyPI v0.42.12] https://pypi.org/project/unstructured-client/0.42.12 - . diff --git a/_test_unstructured_client/unit/test_retries.py b/_test_unstructured_client/unit/test_retries.py index f1f4c571..fffbba8e 100644 --- a/_test_unstructured_client/unit/test_retries.py +++ b/_test_unstructured_client/unit/test_retries.py @@ -1,4 +1,4 @@ -"""Tests for retry logic, specifically covering RemoteProtocolError retry behavior.""" +"""Tests for retry logic covering all TransportError subclasses.""" import asyncio from unittest.mock import MagicMock @@ -32,12 +32,22 @@ def _make_retries(retry_connection_errors: bool) -> Retries: ) -class TestRemoteProtocolErrorRetry: - """Test that RemoteProtocolError (e.g. 'Server disconnected without sending a response') - is retried when retry_connection_errors=True.""" +# All TransportError subclasses that should be retried +TRANSPORT_ERRORS = [ + (httpx.ConnectError, "Connection refused"), + (httpx.RemoteProtocolError, "Server disconnected without sending a response."), + (httpx.ReadError, ""), + (httpx.WriteError, ""), + (httpx.ConnectTimeout, "Timed out"), + (httpx.ReadTimeout, "Timed out"), +] - def test_remote_protocol_error_retried_when_enabled(self): - """RemoteProtocolError should be retried and succeed on subsequent attempt.""" + +class TestTransportErrorRetry: + """All httpx.TransportError subclasses should be retried when retry_connection_errors=True.""" + + @pytest.mark.parametrize("exc_class,msg", TRANSPORT_ERRORS) + def test_transport_error_retried_when_enabled(self, exc_class, msg): retries_config = _make_retries(retry_connection_errors=True) mock_response = MagicMock(spec=httpx.Response) @@ -49,53 +59,29 @@ def func(): nonlocal call_count call_count += 1 if call_count == 1: - raise httpx.RemoteProtocolError( - "Server disconnected without sending a response." - ) + raise exc_class(msg) return mock_response result = retry(func, retries_config) assert result.status_code == 200 assert call_count == 2 - def test_remote_protocol_error_not_retried_when_disabled(self): - """RemoteProtocolError should raise PermanentError when retry_connection_errors=False.""" + @pytest.mark.parametrize("exc_class,msg", TRANSPORT_ERRORS) + def test_transport_error_not_retried_when_disabled(self, exc_class, msg): retries_config = _make_retries(retry_connection_errors=False) def func(): - raise httpx.RemoteProtocolError( - "Server disconnected without sending a response." - ) + raise exc_class(msg) - with pytest.raises(httpx.RemoteProtocolError): + with pytest.raises(exc_class): retry(func, retries_config) - def test_connect_error_still_retried(self): - """Existing ConnectError retry behavior should be preserved.""" - retries_config = _make_retries(retry_connection_errors=True) - - mock_response = MagicMock(spec=httpx.Response) - mock_response.status_code = 200 - - call_count = 0 - - def func(): - nonlocal call_count - call_count += 1 - if call_count == 1: - raise httpx.ConnectError("Connection refused") - return mock_response - - result = retry(func, retries_config) - assert result.status_code == 200 - assert call_count == 2 - -class TestRemoteProtocolErrorRetryAsync: - """Async versions of the RemoteProtocolError retry tests.""" +class TestTransportErrorRetryAsync: + """Async: All httpx.TransportError subclasses should be retried.""" - def test_remote_protocol_error_retried_async(self): - """Async: RemoteProtocolError should be retried when retry_connection_errors=True.""" + @pytest.mark.parametrize("exc_class,msg", TRANSPORT_ERRORS) + def test_transport_error_retried_async(self, exc_class, msg): retries_config = _make_retries(retry_connection_errors=True) mock_response = MagicMock(spec=httpx.Response) @@ -107,23 +93,19 @@ async def func(): nonlocal call_count call_count += 1 if call_count == 1: - raise httpx.RemoteProtocolError( - "Server disconnected without sending a response." - ) + raise exc_class(msg) return mock_response result = asyncio.run(retry_async(func, retries_config)) assert result.status_code == 200 assert call_count == 2 - def test_remote_protocol_error_not_retried_async_when_disabled(self): - """Async: RemoteProtocolError should not be retried when retry_connection_errors=False.""" + @pytest.mark.parametrize("exc_class,msg", TRANSPORT_ERRORS) + def test_transport_error_not_retried_async_when_disabled(self, exc_class, msg): retries_config = _make_retries(retry_connection_errors=False) async def func(): - raise httpx.RemoteProtocolError( - "Server disconnected without sending a response." - ) + raise exc_class(msg) - with pytest.raises(httpx.RemoteProtocolError): + with pytest.raises(exc_class): asyncio.run(retry_async(func, retries_config)) diff --git a/gen.yaml b/gen.yaml index d2237fd4..72a01b12 100644 --- a/gen.yaml +++ b/gen.yaml @@ -23,7 +23,7 @@ generation: schemas: allOfMergeStrategy: shallowMerge python: - version: 0.42.11 + version: 0.42.12 additionalDependencies: dev: deepdiff: '>=6.0' diff --git a/pyproject.toml b/pyproject.toml index 9b14f828..f9923ed0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "unstructured-client" -version = "0.42.11" +version = "0.42.12" description = "Python Client SDK for Unstructured API" authors = [{ name = "Unstructured" },] readme = "README-PYPI.md" diff --git a/src/unstructured_client/_version.py b/src/unstructured_client/_version.py index 4d52e6b0..7b4111f0 100644 --- a/src/unstructured_client/_version.py +++ b/src/unstructured_client/_version.py @@ -3,10 +3,10 @@ import importlib.metadata __title__: str = "unstructured-client" -__version__: str = "0.42.11" +__version__: str = "0.42.12" __openapi_doc_version__: str = "1.2.31" __gen_version__: str = "2.680.0" -__user_agent__: str = "speakeasy-sdk/python 0.42.11 2.680.0 1.2.31 unstructured-client" +__user_agent__: str = "speakeasy-sdk/python 0.42.12 2.680.0 1.2.31 unstructured-client" try: if __package__ is not None: diff --git a/src/unstructured_client/utils/retries.py b/src/unstructured_client/utils/retries.py index f8a4b7ed..375d5e0a 100644 --- a/src/unstructured_client/utils/retries.py +++ b/src/unstructured_client/utils/retries.py @@ -84,17 +84,7 @@ def do_request() -> httpx.Response: if res.status_code == parsed_code: raise TemporaryError(res) - except httpx.ConnectError as exception: - if retries.config.retry_connection_errors: - raise - - raise PermanentError(exception) from exception - except httpx.RemoteProtocolError as exception: - if retries.config.retry_connection_errors: - raise - - raise PermanentError(exception) from exception - except httpx.TimeoutException as exception: + except httpx.TransportError as exception: if retries.config.retry_connection_errors: raise @@ -138,17 +128,7 @@ async def do_request() -> httpx.Response: if res.status_code == parsed_code: raise TemporaryError(res) - except httpx.ConnectError as exception: - if retries.config.retry_connection_errors: - raise - - raise PermanentError(exception) from exception - except httpx.RemoteProtocolError as exception: - if retries.config.retry_connection_errors: - raise - - raise PermanentError(exception) from exception - except httpx.TimeoutException as exception: + except httpx.TransportError as exception: if retries.config.retry_connection_errors: raise