diff --git a/google/genai/_gaos/google_genai.py b/google/genai/_gaos/google_genai.py index dc9b3e44c..61bf43844 100644 --- a/google/genai/_gaos/google_genai.py +++ b/google/genai/_gaos/google_genai.py @@ -128,6 +128,17 @@ def _get_google_genai_security(api_client: Any) -> Optional[Any]: _DEFAULT_MAX_INTERVAL_MS = 8000 _DEFAULT_EXPONENT = 2 _MAX_ELAPSED_TIME_MS = 30000 +# Align with google.genai._api_client._RETRY_HTTP_STATUS_CODES. Speakeasy +# operations default to retrying the entire "5XX" class, which incorrectly +# retries permanent failures such as HTTP 501 Not Implemented (see #2803). +_DEFAULT_RETRY_HTTP_STATUS_CODES = ( + '408', # Request timeout. + '429', # Too many requests. + '500', # Internal server error. + '502', # Bad gateway. + '503', # Service unavailable. + '504', # Gateway timeout. +) def _translate_retry_config(http_options: Any) -> RetryConfig: @@ -147,7 +158,7 @@ def _translate_retry_config(http_options: Any) -> RetryConfig: max_interval = _DEFAULT_MAX_INTERVAL_MS exponent = _DEFAULT_EXPONENT jitter_ms = None - status_codes_override = None + status_codes_override = list(_DEFAULT_RETRY_HTTP_STATUS_CODES) if options is not None: if options.initial_delay is not None: diff --git a/google/genai/tests/interactions/test_auth.py b/google/genai/tests/interactions/test_auth.py index c96e7592f..f5c9eed77 100644 --- a/google/genai/tests/interactions/test_auth.py +++ b/google/genai/tests/interactions/test_auth.py @@ -83,6 +83,26 @@ def test_interactions_gemini_retry(monkeypatch): client.interactions.create(model='gemini-1.5-flash', input='Hello') assert mock_send.call_count == 3 + +def test_interactions_delete_does_not_retry_501(monkeypatch): + """HTTP 501 Not Implemented is permanent; do not retry like transient 5XX. + + Regression coverage for https://github.com/googleapis/python-genai/issues/2803. + """ + monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key') + client = Client() + + with mock.patch.object(HTTPClient, "send") as mock_send: + mock_send.return_value = Response( + 501, + request=Request('DELETE', ''), + headers={'content-type': 'application/json'}, + content='{"error": {"message": "Not Implemented", "code": "not_implemented"}}', + ) + with pytest.raises(Exception): + client.interactions.delete(id='test-interaction-id') + assert mock_send.call_count == 1 + def test_interactions_gemini_extra_headers(monkeypatch): monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key') client = Client() @@ -307,6 +327,24 @@ async def test_async_interactions_gemini_retry(monkeypatch): await client.aio.interactions.create(model='gemini-1.5-flash', input='Hello') assert mock_send.call_count == 3 + +@pytest.mark.asyncio +async def test_async_interactions_delete_does_not_retry_501(monkeypatch): + """HTTP 501 Not Implemented is permanent; do not retry like transient 5XX.""" + monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key') + client = Client() + + with mock.patch.object(AsyncHttpxClient, "send") as mock_send: + mock_send.return_value = Response( + 501, + request=Request('DELETE', ''), + headers={'content-type': 'application/json'}, + content='{"error": {"message": "Not Implemented", "code": "not_implemented"}}', + ) + with pytest.raises(Exception): + await client.aio.interactions.delete(id='test-interaction-id') + assert mock_send.call_count == 1 + @pytest.mark.asyncio async def test_async_interactions_gemini_extra_headers(monkeypatch): monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')