Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion google/genai/_gaos/google_genai.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
38 changes: 38 additions & 0 deletions google/genai/tests/interactions/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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')
Expand Down
Loading