google-genai 1.54.0__py3-none-any.whl → 1.55.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- google/genai/__init__.py +1 -0
- google/genai/_interactions/__init__.py +117 -0
- google/genai/_interactions/_base_client.py +2019 -0
- google/genai/_interactions/_client.py +511 -0
- google/genai/_interactions/_compat.py +234 -0
- google/genai/_interactions/_constants.py +29 -0
- google/genai/_interactions/_exceptions.py +122 -0
- google/genai/_interactions/_files.py +139 -0
- google/genai/_interactions/_models.py +873 -0
- google/genai/_interactions/_qs.py +165 -0
- google/genai/_interactions/_resource.py +58 -0
- google/genai/_interactions/_response.py +847 -0
- google/genai/_interactions/_streaming.py +354 -0
- google/genai/_interactions/_types.py +276 -0
- google/genai/_interactions/_utils/__init__.py +79 -0
- google/genai/_interactions/_utils/_compat.py +61 -0
- google/genai/_interactions/_utils/_datetime_parse.py +151 -0
- google/genai/_interactions/_utils/_logs.py +40 -0
- google/genai/_interactions/_utils/_proxy.py +80 -0
- google/genai/_interactions/_utils/_reflection.py +57 -0
- google/genai/_interactions/_utils/_resources_proxy.py +39 -0
- google/genai/_interactions/_utils/_streams.py +27 -0
- google/genai/_interactions/_utils/_sync.py +73 -0
- google/genai/_interactions/_utils/_transform.py +472 -0
- google/genai/_interactions/_utils/_typing.py +172 -0
- google/genai/_interactions/_utils/_utils.py +437 -0
- google/genai/_interactions/_version.py +18 -0
- google/genai/_interactions/resources/__init__.py +34 -0
- google/genai/_interactions/resources/interactions.py +1350 -0
- google/genai/_interactions/types/__init__.py +107 -0
- google/genai/_interactions/types/allowed_tools.py +33 -0
- google/genai/_interactions/types/allowed_tools_param.py +35 -0
- google/genai/_interactions/types/annotation.py +42 -0
- google/genai/_interactions/types/annotation_param.py +42 -0
- google/genai/_interactions/types/audio_content.py +38 -0
- google/genai/_interactions/types/audio_content_param.py +45 -0
- google/genai/_interactions/types/audio_mime_type.py +25 -0
- google/genai/_interactions/types/audio_mime_type_param.py +27 -0
- google/genai/_interactions/types/code_execution_call_arguments.py +33 -0
- google/genai/_interactions/types/code_execution_call_arguments_param.py +32 -0
- google/genai/_interactions/types/code_execution_call_content.py +37 -0
- google/genai/_interactions/types/code_execution_call_content_param.py +37 -0
- google/genai/_interactions/types/code_execution_result_content.py +42 -0
- google/genai/_interactions/types/code_execution_result_content_param.py +41 -0
- google/genai/_interactions/types/content_delta.py +358 -0
- google/genai/_interactions/types/content_start.py +79 -0
- google/genai/_interactions/types/content_stop.py +35 -0
- google/genai/_interactions/types/deep_research_agent_config.py +33 -0
- google/genai/_interactions/types/deep_research_agent_config_param.py +32 -0
- google/genai/_interactions/types/document_content.py +36 -0
- google/genai/_interactions/types/document_content_param.py +43 -0
- google/genai/_interactions/types/dynamic_agent_config.py +44 -0
- google/genai/_interactions/types/dynamic_agent_config_param.py +33 -0
- google/genai/_interactions/types/error_event.py +46 -0
- google/genai/_interactions/types/file_search_result_content.py +46 -0
- google/genai/_interactions/types/file_search_result_content_param.py +46 -0
- google/genai/_interactions/types/function.py +38 -0
- google/genai/_interactions/types/function_call_content.py +39 -0
- google/genai/_interactions/types/function_call_content_param.py +39 -0
- google/genai/_interactions/types/function_param.py +37 -0
- google/genai/_interactions/types/function_result_content.py +52 -0
- google/genai/_interactions/types/function_result_content_param.py +54 -0
- google/genai/_interactions/types/generation_config.py +57 -0
- google/genai/_interactions/types/generation_config_param.py +59 -0
- google/genai/_interactions/types/google_search_call_arguments.py +29 -0
- google/genai/_interactions/types/google_search_call_arguments_param.py +31 -0
- google/genai/_interactions/types/google_search_call_content.py +37 -0
- google/genai/_interactions/types/google_search_call_content_param.py +37 -0
- google/genai/_interactions/types/google_search_result.py +35 -0
- google/genai/_interactions/types/google_search_result_content.py +43 -0
- google/genai/_interactions/types/google_search_result_content_param.py +44 -0
- google/genai/_interactions/types/google_search_result_param.py +35 -0
- google/genai/_interactions/types/image_content.py +41 -0
- google/genai/_interactions/types/image_content_param.py +48 -0
- google/genai/_interactions/types/image_mime_type.py +23 -0
- google/genai/_interactions/types/image_mime_type_param.py +25 -0
- google/genai/_interactions/types/interaction.py +165 -0
- google/genai/_interactions/types/interaction_create_params.py +212 -0
- google/genai/_interactions/types/interaction_event.py +37 -0
- google/genai/_interactions/types/interaction_get_params.py +46 -0
- google/genai/_interactions/types/interaction_sse_event.py +32 -0
- google/genai/_interactions/types/interaction_status_update.py +37 -0
- google/genai/_interactions/types/mcp_server_tool_call_content.py +42 -0
- google/genai/_interactions/types/mcp_server_tool_call_content_param.py +42 -0
- google/genai/_interactions/types/mcp_server_tool_result_content.py +52 -0
- google/genai/_interactions/types/mcp_server_tool_result_content_param.py +54 -0
- google/genai/_interactions/types/model.py +36 -0
- google/genai/_interactions/types/model_param.py +38 -0
- google/genai/_interactions/types/speech_config.py +35 -0
- google/genai/_interactions/types/speech_config_param.py +35 -0
- google/genai/_interactions/types/text_content.py +37 -0
- google/genai/_interactions/types/text_content_param.py +38 -0
- google/genai/_interactions/types/thinking_level.py +22 -0
- google/genai/_interactions/types/thought_content.py +41 -0
- google/genai/_interactions/types/thought_content_param.py +47 -0
- google/genai/_interactions/types/tool.py +100 -0
- google/genai/_interactions/types/tool_choice.py +26 -0
- google/genai/_interactions/types/tool_choice_config.py +28 -0
- google/genai/_interactions/types/tool_choice_config_param.py +29 -0
- google/genai/_interactions/types/tool_choice_param.py +28 -0
- google/genai/_interactions/types/tool_choice_type.py +22 -0
- google/genai/_interactions/types/tool_param.py +97 -0
- google/genai/_interactions/types/turn.py +76 -0
- google/genai/_interactions/types/turn_param.py +73 -0
- google/genai/_interactions/types/url_context_call_arguments.py +29 -0
- google/genai/_interactions/types/url_context_call_arguments_param.py +31 -0
- google/genai/_interactions/types/url_context_call_content.py +37 -0
- google/genai/_interactions/types/url_context_call_content_param.py +37 -0
- google/genai/_interactions/types/url_context_result.py +33 -0
- google/genai/_interactions/types/url_context_result_content.py +43 -0
- google/genai/_interactions/types/url_context_result_content_param.py +44 -0
- google/genai/_interactions/types/url_context_result_param.py +32 -0
- google/genai/_interactions/types/usage.py +106 -0
- google/genai/_interactions/types/usage_param.py +106 -0
- google/genai/_interactions/types/video_content.py +41 -0
- google/genai/_interactions/types/video_content_param.py +48 -0
- google/genai/_interactions/types/video_mime_type.py +36 -0
- google/genai/_interactions/types/video_mime_type_param.py +38 -0
- google/genai/_live_converters.py +31 -0
- google/genai/_tokens_converters.py +5 -0
- google/genai/batches.py +7 -0
- google/genai/client.py +223 -0
- google/genai/interactions.py +17 -0
- google/genai/live.py +4 -3
- google/genai/models.py +12 -0
- google/genai/tests/__init__.py +21 -0
- google/genai/tests/afc/__init__.py +21 -0
- google/genai/tests/afc/test_convert_if_exist_pydantic_model.py +309 -0
- google/genai/tests/afc/test_convert_number_values_for_function_call_args.py +63 -0
- google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py +240 -0
- google/genai/tests/afc/test_generate_content_stream_afc.py +530 -0
- google/genai/tests/afc/test_generate_content_stream_afc_thoughts.py +77 -0
- google/genai/tests/afc/test_get_function_map.py +176 -0
- google/genai/tests/afc/test_get_function_response_parts.py +277 -0
- google/genai/tests/afc/test_get_max_remote_calls_for_afc.py +130 -0
- google/genai/tests/afc/test_invoke_function_from_dict_args.py +241 -0
- google/genai/tests/afc/test_raise_error_for_afc_incompatible_config.py +159 -0
- google/genai/tests/afc/test_should_append_afc_history.py +53 -0
- google/genai/tests/afc/test_should_disable_afc.py +214 -0
- google/genai/tests/batches/__init__.py +17 -0
- google/genai/tests/batches/test_cancel.py +77 -0
- google/genai/tests/batches/test_create.py +78 -0
- google/genai/tests/batches/test_create_with_bigquery.py +113 -0
- google/genai/tests/batches/test_create_with_file.py +82 -0
- google/genai/tests/batches/test_create_with_gcs.py +125 -0
- google/genai/tests/batches/test_create_with_inlined_requests.py +255 -0
- google/genai/tests/batches/test_delete.py +86 -0
- google/genai/tests/batches/test_embedding.py +157 -0
- google/genai/tests/batches/test_get.py +78 -0
- google/genai/tests/batches/test_list.py +79 -0
- google/genai/tests/caches/__init__.py +17 -0
- google/genai/tests/caches/constants.py +29 -0
- google/genai/tests/caches/test_create.py +210 -0
- google/genai/tests/caches/test_create_custom_url.py +105 -0
- google/genai/tests/caches/test_delete.py +54 -0
- google/genai/tests/caches/test_delete_custom_url.py +52 -0
- google/genai/tests/caches/test_get.py +94 -0
- google/genai/tests/caches/test_get_custom_url.py +52 -0
- google/genai/tests/caches/test_list.py +68 -0
- google/genai/tests/caches/test_update.py +70 -0
- google/genai/tests/caches/test_update_custom_url.py +58 -0
- google/genai/tests/chats/__init__.py +1 -0
- google/genai/tests/chats/test_get_history.py +597 -0
- google/genai/tests/chats/test_send_message.py +844 -0
- google/genai/tests/chats/test_validate_response.py +90 -0
- google/genai/tests/client/__init__.py +17 -0
- google/genai/tests/client/test_async_stream.py +427 -0
- google/genai/tests/client/test_client_close.py +197 -0
- google/genai/tests/client/test_client_initialization.py +1687 -0
- google/genai/tests/client/test_client_requests.py +355 -0
- google/genai/tests/client/test_custom_client.py +77 -0
- google/genai/tests/client/test_http_options.py +178 -0
- google/genai/tests/client/test_replay_client_equality.py +168 -0
- google/genai/tests/client/test_retries.py +846 -0
- google/genai/tests/client/test_upload_errors.py +136 -0
- google/genai/tests/common/__init__.py +17 -0
- google/genai/tests/common/test_common.py +954 -0
- google/genai/tests/conftest.py +162 -0
- google/genai/tests/documents/__init__.py +17 -0
- google/genai/tests/documents/test_delete.py +51 -0
- google/genai/tests/documents/test_get.py +85 -0
- google/genai/tests/documents/test_list.py +72 -0
- google/genai/tests/errors/__init__.py +1 -0
- google/genai/tests/errors/test_api_error.py +417 -0
- google/genai/tests/file_search_stores/__init__.py +17 -0
- google/genai/tests/file_search_stores/test_create.py +66 -0
- google/genai/tests/file_search_stores/test_delete.py +64 -0
- google/genai/tests/file_search_stores/test_get.py +94 -0
- google/genai/tests/file_search_stores/test_import_file.py +112 -0
- google/genai/tests/file_search_stores/test_list.py +57 -0
- google/genai/tests/file_search_stores/test_upload_to_file_search_store.py +141 -0
- google/genai/tests/files/__init__.py +17 -0
- google/genai/tests/files/test_delete.py +46 -0
- google/genai/tests/files/test_download.py +85 -0
- google/genai/tests/files/test_get.py +46 -0
- google/genai/tests/files/test_list.py +72 -0
- google/genai/tests/files/test_upload.py +255 -0
- google/genai/tests/imports/test_no_optional_imports.py +28 -0
- google/genai/tests/interactions/__init__.py +0 -0
- google/genai/tests/interactions/test_integration.py +80 -0
- google/genai/tests/live/__init__.py +16 -0
- google/genai/tests/live/test_live.py +2177 -0
- google/genai/tests/live/test_live_music.py +362 -0
- google/genai/tests/live/test_live_response.py +163 -0
- google/genai/tests/live/test_send_client_content.py +147 -0
- google/genai/tests/live/test_send_realtime_input.py +268 -0
- google/genai/tests/live/test_send_tool_response.py +222 -0
- google/genai/tests/local_tokenizer/__init__.py +17 -0
- google/genai/tests/local_tokenizer/test_local_tokenizer.py +343 -0
- google/genai/tests/local_tokenizer/test_local_tokenizer_loader.py +235 -0
- google/genai/tests/mcp/__init__.py +17 -0
- google/genai/tests/mcp/test_has_mcp_tool_usage.py +89 -0
- google/genai/tests/mcp/test_mcp_to_gemini_tools.py +191 -0
- google/genai/tests/mcp/test_parse_config_for_mcp_sessions.py +201 -0
- google/genai/tests/mcp/test_parse_config_for_mcp_usage.py +130 -0
- google/genai/tests/mcp/test_set_mcp_usage_header.py +72 -0
- google/genai/tests/models/__init__.py +17 -0
- google/genai/tests/models/constants.py +8 -0
- google/genai/tests/models/test_compute_tokens.py +120 -0
- google/genai/tests/models/test_count_tokens.py +159 -0
- google/genai/tests/models/test_delete.py +107 -0
- google/genai/tests/models/test_edit_image.py +264 -0
- google/genai/tests/models/test_embed_content.py +94 -0
- google/genai/tests/models/test_function_call_streaming.py +442 -0
- google/genai/tests/models/test_generate_content.py +2502 -0
- google/genai/tests/models/test_generate_content_cached_content.py +132 -0
- google/genai/tests/models/test_generate_content_config_zero_value.py +103 -0
- google/genai/tests/models/test_generate_content_from_apikey.py +44 -0
- google/genai/tests/models/test_generate_content_http_options.py +40 -0
- google/genai/tests/models/test_generate_content_image_generation.py +143 -0
- google/genai/tests/models/test_generate_content_mcp.py +343 -0
- google/genai/tests/models/test_generate_content_media_resolution.py +97 -0
- google/genai/tests/models/test_generate_content_model.py +139 -0
- google/genai/tests/models/test_generate_content_part.py +821 -0
- google/genai/tests/models/test_generate_content_thought.py +76 -0
- google/genai/tests/models/test_generate_content_tools.py +1761 -0
- google/genai/tests/models/test_generate_images.py +191 -0
- google/genai/tests/models/test_generate_videos.py +759 -0
- google/genai/tests/models/test_get.py +104 -0
- google/genai/tests/models/test_list.py +233 -0
- google/genai/tests/models/test_recontext_image.py +189 -0
- google/genai/tests/models/test_segment_image.py +148 -0
- google/genai/tests/models/test_update.py +95 -0
- google/genai/tests/models/test_upscale_image.py +157 -0
- google/genai/tests/operations/__init__.py +17 -0
- google/genai/tests/operations/test_get.py +38 -0
- google/genai/tests/public_samples/__init__.py +17 -0
- google/genai/tests/public_samples/test_gemini_text_only.py +34 -0
- google/genai/tests/pytest_helper.py +229 -0
- google/genai/tests/shared/__init__.py +16 -0
- google/genai/tests/shared/batches/__init__.py +14 -0
- google/genai/tests/shared/batches/test_create_delete.py +57 -0
- google/genai/tests/shared/batches/test_create_get_cancel.py +56 -0
- google/genai/tests/shared/batches/test_list.py +40 -0
- google/genai/tests/shared/caches/__init__.py +14 -0
- google/genai/tests/shared/caches/test_create_get_delete.py +67 -0
- google/genai/tests/shared/caches/test_create_update_get.py +71 -0
- google/genai/tests/shared/caches/test_list.py +40 -0
- google/genai/tests/shared/chats/__init__.py +14 -0
- google/genai/tests/shared/chats/test_send_message.py +48 -0
- google/genai/tests/shared/chats/test_send_message_stream.py +50 -0
- google/genai/tests/shared/files/__init__.py +14 -0
- google/genai/tests/shared/files/test_list.py +41 -0
- google/genai/tests/shared/files/test_upload_get_delete.py +54 -0
- google/genai/tests/shared/models/__init__.py +14 -0
- google/genai/tests/shared/models/test_compute_tokens.py +41 -0
- google/genai/tests/shared/models/test_count_tokens.py +40 -0
- google/genai/tests/shared/models/test_edit_image.py +67 -0
- google/genai/tests/shared/models/test_embed.py +40 -0
- google/genai/tests/shared/models/test_generate_content.py +39 -0
- google/genai/tests/shared/models/test_generate_content_stream.py +54 -0
- google/genai/tests/shared/models/test_generate_images.py +40 -0
- google/genai/tests/shared/models/test_generate_videos.py +38 -0
- google/genai/tests/shared/models/test_list.py +37 -0
- google/genai/tests/shared/models/test_recontext_image.py +55 -0
- google/genai/tests/shared/models/test_segment_image.py +52 -0
- google/genai/tests/shared/models/test_upscale_image.py +52 -0
- google/genai/tests/shared/tunings/__init__.py +16 -0
- google/genai/tests/shared/tunings/test_create.py +46 -0
- google/genai/tests/shared/tunings/test_create_get_cancel.py +56 -0
- google/genai/tests/shared/tunings/test_list.py +39 -0
- google/genai/tests/tokens/__init__.py +16 -0
- google/genai/tests/tokens/test_create.py +154 -0
- google/genai/tests/transformers/__init__.py +17 -0
- google/genai/tests/transformers/test_blobs.py +71 -0
- google/genai/tests/transformers/test_bytes.py +15 -0
- google/genai/tests/transformers/test_duck_type.py +96 -0
- google/genai/tests/transformers/test_function_responses.py +72 -0
- google/genai/tests/transformers/test_schema.py +653 -0
- google/genai/tests/transformers/test_t_batch.py +286 -0
- google/genai/tests/transformers/test_t_content.py +160 -0
- google/genai/tests/transformers/test_t_contents.py +398 -0
- google/genai/tests/transformers/test_t_part.py +85 -0
- google/genai/tests/transformers/test_t_parts.py +87 -0
- google/genai/tests/transformers/test_t_tool.py +157 -0
- google/genai/tests/transformers/test_t_tools.py +195 -0
- google/genai/tests/tunings/__init__.py +16 -0
- google/genai/tests/tunings/test_cancel.py +39 -0
- google/genai/tests/tunings/test_end_to_end.py +106 -0
- google/genai/tests/tunings/test_get.py +67 -0
- google/genai/tests/tunings/test_list.py +75 -0
- google/genai/tests/tunings/test_tune.py +268 -0
- google/genai/tests/types/__init__.py +16 -0
- google/genai/tests/types/test_bytes_internal.py +271 -0
- google/genai/tests/types/test_bytes_type.py +152 -0
- google/genai/tests/types/test_future.py +101 -0
- google/genai/tests/types/test_optional_types.py +36 -0
- google/genai/tests/types/test_part_type.py +616 -0
- google/genai/tests/types/test_schema_from_json_schema.py +417 -0
- google/genai/tests/types/test_schema_json_schema.py +468 -0
- google/genai/tests/types/test_types.py +2903 -0
- google/genai/types.py +72 -0
- google/genai/version.py +1 -1
- {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/METADATA +3 -1
- google_genai-1.55.0.dist-info/RECORD +345 -0
- google_genai-1.54.0.dist-info/RECORD +0 -41
- {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/WHEEL +0 -0
- {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/licenses/LICENSE +0 -0
- {google_genai-1.54.0.dist-info → google_genai-1.55.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
"""Tests for http retries."""
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
import datetime
|
|
21
|
+
from unittest import mock
|
|
22
|
+
import pytest
|
|
23
|
+
try:
|
|
24
|
+
import aiohttp
|
|
25
|
+
AIOHTTP_NOT_INSTALLED = False
|
|
26
|
+
except ImportError:
|
|
27
|
+
AIOHTTP_NOT_INSTALLED = True
|
|
28
|
+
aiohttp = mock.MagicMock()
|
|
29
|
+
|
|
30
|
+
from google.oauth2 import credentials
|
|
31
|
+
import httpx
|
|
32
|
+
import tenacity
|
|
33
|
+
|
|
34
|
+
from ... import _api_client as api_client
|
|
35
|
+
from ... import errors
|
|
36
|
+
from ... import types
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
requires_aiohttp = pytest.mark.skipif(
|
|
40
|
+
AIOHTTP_NOT_INSTALLED, reason="aiohttp is not installed, skipping test."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_RETRIED_CODES = (
|
|
45
|
+
408, # Request timeout.
|
|
46
|
+
429, # Too many requests.
|
|
47
|
+
500, # Internal server error.
|
|
48
|
+
502, # Bad gateway.
|
|
49
|
+
503, # Service unavailable.
|
|
50
|
+
504, # Gateway timeout.
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.fixture(autouse=True)
|
|
55
|
+
def reset_has_aiohttp():
|
|
56
|
+
yield
|
|
57
|
+
api_client.has_aiohttp = False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _final_codes(retried_codes: Sequence[int] = _RETRIED_CODES):
|
|
61
|
+
return [code for code in range(100, 600) if code not in retried_codes]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _httpx_response(code: int):
|
|
65
|
+
return httpx.Response(
|
|
66
|
+
status_code=code,
|
|
67
|
+
headers={'status-code': str(code)},
|
|
68
|
+
content=b'',
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Args
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_retry_args_disabled():
|
|
76
|
+
args = api_client.retry_args(None)
|
|
77
|
+
|
|
78
|
+
assert set(args.keys()) == {'stop', 'reraise'}
|
|
79
|
+
assert args['stop'].max_attempt_number == 1
|
|
80
|
+
assert args['reraise']
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_retry_args_enabled_with_defaults():
|
|
84
|
+
# Empty options means use the default values whereas None means no retries.
|
|
85
|
+
args = api_client.retry_args(types.HttpRetryOptions())
|
|
86
|
+
|
|
87
|
+
assert set(args.keys()) == {
|
|
88
|
+
'stop',
|
|
89
|
+
'retry',
|
|
90
|
+
'wait',
|
|
91
|
+
'reraise',
|
|
92
|
+
'before_sleep',
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
assert args['stop'].max_attempt_number == 5
|
|
96
|
+
|
|
97
|
+
wait = args['wait']
|
|
98
|
+
assert wait.exp_base == 2
|
|
99
|
+
assert wait.initial == 1
|
|
100
|
+
assert wait.jitter == 1
|
|
101
|
+
assert wait.max == 60
|
|
102
|
+
|
|
103
|
+
retry = args['retry']
|
|
104
|
+
for code in _RETRIED_CODES:
|
|
105
|
+
try:
|
|
106
|
+
errors.APIError.raise_for_response(_httpx_response(code))
|
|
107
|
+
assert False, 'Expected APIError to be raised.'
|
|
108
|
+
except errors.APIError as e:
|
|
109
|
+
assert retry.predicate(e)
|
|
110
|
+
|
|
111
|
+
for code in _final_codes():
|
|
112
|
+
try:
|
|
113
|
+
errors.APIError.raise_for_response(_httpx_response(code))
|
|
114
|
+
# Does not raise for some codes.
|
|
115
|
+
except errors.APIError as e:
|
|
116
|
+
# Does not retry for error codes outside of the retried codes list.
|
|
117
|
+
assert not retry.predicate(e)
|
|
118
|
+
|
|
119
|
+
assert args['reraise']
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_retry_wait():
|
|
123
|
+
timestamps = []
|
|
124
|
+
|
|
125
|
+
def fn():
|
|
126
|
+
now = datetime.datetime.now()
|
|
127
|
+
timestamps.append(now)
|
|
128
|
+
raise errors.APIError.raise_for_response(_httpx_response(429))
|
|
129
|
+
|
|
130
|
+
retrying = tenacity.Retrying(
|
|
131
|
+
**api_client.retry_args(types.HttpRetryOptions())
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
retrying(fn)
|
|
136
|
+
assert False, 'Expected APIError to be raised.'
|
|
137
|
+
except errors.APIError:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
assert len(timestamps) == 5
|
|
141
|
+
assert timestamps[1] - timestamps[0] >= datetime.timedelta(seconds=1)
|
|
142
|
+
assert timestamps[2] - timestamps[1] >= datetime.timedelta(seconds=2)
|
|
143
|
+
assert timestamps[3] - timestamps[2] >= datetime.timedelta(seconds=4)
|
|
144
|
+
assert timestamps[4] - timestamps[3] >= datetime.timedelta(seconds=8)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_retry_args_enabled_with_custom_values_are_not_overridden():
|
|
148
|
+
options = types.HttpRetryOptions(
|
|
149
|
+
attempts=10,
|
|
150
|
+
initial_delay=10,
|
|
151
|
+
max_delay=100,
|
|
152
|
+
exp_base=1.5,
|
|
153
|
+
jitter=0.5,
|
|
154
|
+
http_status_codes=[408, 429],
|
|
155
|
+
)
|
|
156
|
+
retry_args = api_client.retry_args(options)
|
|
157
|
+
assert retry_args['stop'].max_attempt_number == 10
|
|
158
|
+
|
|
159
|
+
wait = retry_args['wait']
|
|
160
|
+
assert wait.initial == 10
|
|
161
|
+
assert wait.max == 100
|
|
162
|
+
assert wait.exp_base == 1.5
|
|
163
|
+
assert wait.jitter == 0.5
|
|
164
|
+
|
|
165
|
+
retry = retry_args['retry']
|
|
166
|
+
for code in [408, 429]:
|
|
167
|
+
try:
|
|
168
|
+
errors.APIError.raise_for_response(_httpx_response(code))
|
|
169
|
+
assert False, 'Expected APIError to be raised.'
|
|
170
|
+
except errors.APIError as e:
|
|
171
|
+
assert retry.predicate(e)
|
|
172
|
+
|
|
173
|
+
for code in _final_codes([408, 429]):
|
|
174
|
+
try:
|
|
175
|
+
errors.APIError.raise_for_response(_httpx_response(code))
|
|
176
|
+
# Does not raise for some codes.
|
|
177
|
+
except errors.APIError as e:
|
|
178
|
+
# Does not retry for error codes outside of the retried codes list.
|
|
179
|
+
assert not retry.predicate(e)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _patch_auth_default():
|
|
183
|
+
return mock.patch(
|
|
184
|
+
'google.auth.default',
|
|
185
|
+
return_value=(credentials.Credentials('magic_token'), 'test_project'),
|
|
186
|
+
autospec=True,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _transport_options(http_options=None, transport=None, async_transport=None):
|
|
191
|
+
http_options = http_options or types.HttpOptions()
|
|
192
|
+
http_options.client_args = {'transport': transport}
|
|
193
|
+
http_options.async_client_args = {'transport': async_transport}
|
|
194
|
+
return http_options
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# Sync
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_disabled_retries_successful_request_executes_once():
|
|
201
|
+
mock_transport = mock.Mock(spec=httpx.BaseTransport)
|
|
202
|
+
mock_transport.handle_request.return_value = _httpx_response(200)
|
|
203
|
+
|
|
204
|
+
client = api_client.BaseApiClient(
|
|
205
|
+
vertexai=True,
|
|
206
|
+
project='test_project',
|
|
207
|
+
location='global',
|
|
208
|
+
http_options=_transport_options(transport=mock_transport),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
with _patch_auth_default():
|
|
212
|
+
response = client.request(http_method='GET', path='path', request_dict={})
|
|
213
|
+
mock_transport.handle_request.assert_called_once()
|
|
214
|
+
assert response.headers['status-code'] == '200'
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_disabled_retries_failed_request_executes_once():
|
|
218
|
+
mock_transport = mock.Mock(spec=httpx.BaseTransport)
|
|
219
|
+
mock_transport.handle_request.return_value = _httpx_response(429)
|
|
220
|
+
|
|
221
|
+
client = api_client.BaseApiClient(
|
|
222
|
+
vertexai=True,
|
|
223
|
+
project='test_project',
|
|
224
|
+
location='global',
|
|
225
|
+
http_options=_transport_options(transport=mock_transport),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
with _patch_auth_default():
|
|
229
|
+
try:
|
|
230
|
+
client.request(http_method='GET', path='path', request_dict={})
|
|
231
|
+
assert False, 'Expected APIError to be raised.'
|
|
232
|
+
except errors.APIError as e:
|
|
233
|
+
assert e.code == 429
|
|
234
|
+
mock_transport.handle_request.assert_called_once()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
_RETRY_OPTIONS = types.HttpRetryOptions(
|
|
238
|
+
attempts=2,
|
|
239
|
+
initial_delay=0,
|
|
240
|
+
max_delay=1,
|
|
241
|
+
exp_base=0.1,
|
|
242
|
+
jitter=0.1,
|
|
243
|
+
http_status_codes=[429, 504],
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_retries_successful_request_executes_once():
|
|
248
|
+
mock_transport = mock.Mock(spec=httpx.BaseTransport)
|
|
249
|
+
mock_transport.handle_request.return_value = _httpx_response(200)
|
|
250
|
+
|
|
251
|
+
client = api_client.BaseApiClient(
|
|
252
|
+
vertexai=True,
|
|
253
|
+
project='test_project',
|
|
254
|
+
location='global',
|
|
255
|
+
http_options=_transport_options(
|
|
256
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
257
|
+
transport=mock_transport,
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
with _patch_auth_default():
|
|
262
|
+
response = client.request(http_method='GET', path='path', request_dict={})
|
|
263
|
+
mock_transport.handle_request.assert_called_once()
|
|
264
|
+
assert response.headers['status-code'] == '200'
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_retries_failed_request_retries_successfully():
|
|
268
|
+
mock_transport = mock.Mock(spec=httpx.BaseTransport)
|
|
269
|
+
mock_transport.handle_request.side_effect = (
|
|
270
|
+
_httpx_response(429),
|
|
271
|
+
_httpx_response(200),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
client = api_client.BaseApiClient(
|
|
275
|
+
vertexai=True,
|
|
276
|
+
project='test_project',
|
|
277
|
+
location='global',
|
|
278
|
+
http_options=_transport_options(
|
|
279
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
280
|
+
transport=mock_transport,
|
|
281
|
+
),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
with _patch_auth_default():
|
|
285
|
+
response = client.request(http_method='GET', path='path', request_dict={})
|
|
286
|
+
mock_transport.handle_request.assert_called()
|
|
287
|
+
assert response.headers['status-code'] == '200'
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_retries_failed_request_retries_successfully_at_request_level():
|
|
291
|
+
mock_transport = mock.Mock(spec=httpx.BaseTransport)
|
|
292
|
+
mock_transport.handle_request.side_effect = (
|
|
293
|
+
_httpx_response(429),
|
|
294
|
+
_httpx_response(200),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
client = api_client.BaseApiClient(
|
|
298
|
+
vertexai=True,
|
|
299
|
+
project='test_project',
|
|
300
|
+
location='global',
|
|
301
|
+
http_options=_transport_options(
|
|
302
|
+
transport=mock_transport,
|
|
303
|
+
),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
with _patch_auth_default():
|
|
307
|
+
response = client.request(
|
|
308
|
+
http_method='GET',
|
|
309
|
+
path='path',
|
|
310
|
+
request_dict={},
|
|
311
|
+
http_options=types.HttpOptions(
|
|
312
|
+
retry_options=_RETRY_OPTIONS
|
|
313
|
+
), # At request level.
|
|
314
|
+
)
|
|
315
|
+
mock_transport.handle_request.assert_called()
|
|
316
|
+
assert response.headers['status-code'] == '200'
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_retries_failed_request_retries_unsuccessfully():
|
|
320
|
+
mock_transport = mock.Mock(spec=httpx.BaseTransport)
|
|
321
|
+
mock_transport.handle_request.side_effect = (
|
|
322
|
+
_httpx_response(429),
|
|
323
|
+
_httpx_response(504),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
client = api_client.BaseApiClient(
|
|
327
|
+
vertexai=True,
|
|
328
|
+
project='test_project',
|
|
329
|
+
location='global',
|
|
330
|
+
http_options=_transport_options(
|
|
331
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
332
|
+
transport=mock_transport,
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
with _patch_auth_default():
|
|
337
|
+
try:
|
|
338
|
+
client.request(http_method='GET', path='path', request_dict={})
|
|
339
|
+
assert False, 'Expected APIError to be raised.'
|
|
340
|
+
except errors.APIError as e:
|
|
341
|
+
assert e.code == 504
|
|
342
|
+
mock_transport.handle_request.assert_called()
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_retries_failed_request_retries_unsuccessfully_at_request_level():
|
|
346
|
+
mock_transport = mock.Mock(spec=httpx.BaseTransport)
|
|
347
|
+
mock_transport.handle_request.side_effect = (
|
|
348
|
+
_httpx_response(429),
|
|
349
|
+
_httpx_response(504),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
client = api_client.BaseApiClient(
|
|
353
|
+
vertexai=True,
|
|
354
|
+
project='test_project',
|
|
355
|
+
location='global',
|
|
356
|
+
http_options=_transport_options(
|
|
357
|
+
transport=mock_transport,
|
|
358
|
+
),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
with _patch_auth_default():
|
|
362
|
+
try:
|
|
363
|
+
client.request(
|
|
364
|
+
http_method='GET',
|
|
365
|
+
path='path',
|
|
366
|
+
request_dict={},
|
|
367
|
+
http_options={'retry_options': _RETRY_OPTIONS}, # At request level.
|
|
368
|
+
)
|
|
369
|
+
assert False, 'Expected APIError to be raised.'
|
|
370
|
+
except errors.APIError as e:
|
|
371
|
+
assert e.code == 504
|
|
372
|
+
mock_transport.handle_request.assert_called()
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# Async httpx
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def test_async_disabled_retries_successful_request_executes_once():
|
|
379
|
+
api_client.has_aiohttp = False
|
|
380
|
+
|
|
381
|
+
async def run():
|
|
382
|
+
mock_transport = mock.Mock(spec=httpx.AsyncBaseTransport)
|
|
383
|
+
mock_transport.handle_async_request.return_value = _httpx_response(200)
|
|
384
|
+
|
|
385
|
+
client = api_client.BaseApiClient(
|
|
386
|
+
vertexai=True,
|
|
387
|
+
project='test_project',
|
|
388
|
+
location='global',
|
|
389
|
+
http_options=_transport_options(async_transport=mock_transport),
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
with _patch_auth_default():
|
|
393
|
+
response = await client.async_request(
|
|
394
|
+
http_method='GET', path='path', request_dict={}
|
|
395
|
+
)
|
|
396
|
+
mock_transport.handle_async_request.assert_called_once()
|
|
397
|
+
assert response.headers['status-code'] == '200'
|
|
398
|
+
|
|
399
|
+
asyncio.run(run())
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def test_async_disabled_retries_failed_request_executes_once():
|
|
403
|
+
api_client.has_aiohttp = False
|
|
404
|
+
|
|
405
|
+
async def run():
|
|
406
|
+
mock_transport = mock.Mock(spec=httpx.AsyncBaseTransport)
|
|
407
|
+
mock_transport.handle_async_request.return_value = _httpx_response(429)
|
|
408
|
+
|
|
409
|
+
client = api_client.BaseApiClient(
|
|
410
|
+
vertexai=True,
|
|
411
|
+
project='test_project',
|
|
412
|
+
location='global',
|
|
413
|
+
http_options=_transport_options(async_transport=mock_transport),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
with _patch_auth_default():
|
|
417
|
+
try:
|
|
418
|
+
await client.async_request(
|
|
419
|
+
http_method='GET', path='path', request_dict={}
|
|
420
|
+
)
|
|
421
|
+
assert False, 'Expected APIError to be raised.'
|
|
422
|
+
except errors.APIError as e:
|
|
423
|
+
assert e.code == 429
|
|
424
|
+
mock_transport.handle_async_request.assert_called_once()
|
|
425
|
+
|
|
426
|
+
asyncio.run(run())
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def test_async_retries_successful_request_executes_once():
|
|
430
|
+
api_client.has_aiohttp = False
|
|
431
|
+
|
|
432
|
+
async def run():
|
|
433
|
+
mock_transport = mock.Mock(spec=httpx.AsyncBaseTransport)
|
|
434
|
+
mock_transport.handle_async_request.return_value = _httpx_response(200)
|
|
435
|
+
|
|
436
|
+
client = api_client.BaseApiClient(
|
|
437
|
+
vertexai=True,
|
|
438
|
+
project='test_project',
|
|
439
|
+
location='global',
|
|
440
|
+
http_options=_transport_options(
|
|
441
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
442
|
+
async_transport=mock_transport,
|
|
443
|
+
),
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
with _patch_auth_default():
|
|
447
|
+
response = await client.async_request(
|
|
448
|
+
http_method='GET', path='path', request_dict={}
|
|
449
|
+
)
|
|
450
|
+
mock_transport.handle_async_request.assert_called_once()
|
|
451
|
+
assert response.headers['status-code'] == '200'
|
|
452
|
+
|
|
453
|
+
asyncio.run(run())
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def test_async_retries_failed_request_retries_successfully():
|
|
457
|
+
api_client.has_aiohttp = False
|
|
458
|
+
|
|
459
|
+
async def run():
|
|
460
|
+
mock_transport = mock.Mock(spec=httpx.AsyncBaseTransport)
|
|
461
|
+
mock_transport.handle_async_request.side_effect = (
|
|
462
|
+
_httpx_response(429),
|
|
463
|
+
_httpx_response(200),
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
client = api_client.BaseApiClient(
|
|
467
|
+
vertexai=True,
|
|
468
|
+
project='test_project',
|
|
469
|
+
location='global',
|
|
470
|
+
http_options=_transport_options(
|
|
471
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
472
|
+
async_transport=mock_transport,
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
with _patch_auth_default():
|
|
477
|
+
response = await client.async_request(
|
|
478
|
+
http_method='GET', path='path', request_dict={}
|
|
479
|
+
)
|
|
480
|
+
mock_transport.handle_async_request.assert_called()
|
|
481
|
+
assert response.headers['status-code'] == '200'
|
|
482
|
+
|
|
483
|
+
asyncio.run(run())
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def test_async_retries_failed_request_retries_successfully_at_request_level():
|
|
487
|
+
api_client.has_aiohttp = False
|
|
488
|
+
|
|
489
|
+
async def run():
|
|
490
|
+
mock_transport = mock.Mock(spec=httpx.AsyncBaseTransport)
|
|
491
|
+
mock_transport.handle_async_request.side_effect = (
|
|
492
|
+
_httpx_response(429),
|
|
493
|
+
_httpx_response(200),
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
client = api_client.BaseApiClient(
|
|
497
|
+
vertexai=True,
|
|
498
|
+
project='test_project',
|
|
499
|
+
location='global',
|
|
500
|
+
http_options=_transport_options(
|
|
501
|
+
async_transport=mock_transport,
|
|
502
|
+
),
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
with _patch_auth_default():
|
|
506
|
+
response = await client.async_request(
|
|
507
|
+
http_method='GET',
|
|
508
|
+
path='path',
|
|
509
|
+
request_dict={},
|
|
510
|
+
http_options=types.HttpOptions(
|
|
511
|
+
retry_options=_RETRY_OPTIONS
|
|
512
|
+
), # At request level.
|
|
513
|
+
)
|
|
514
|
+
mock_transport.handle_async_request.assert_called()
|
|
515
|
+
assert response.headers['status-code'] == '200'
|
|
516
|
+
|
|
517
|
+
asyncio.run(run())
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def test_async_retries_failed_request_retries_unsuccessfully():
|
|
521
|
+
api_client.has_aiohttp = False
|
|
522
|
+
|
|
523
|
+
async def run():
|
|
524
|
+
mock_transport = mock.Mock(spec=httpx.AsyncBaseTransport)
|
|
525
|
+
mock_transport.handle_async_request.side_effect = (
|
|
526
|
+
_httpx_response(429),
|
|
527
|
+
_httpx_response(504),
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
client = api_client.BaseApiClient(
|
|
531
|
+
vertexai=True,
|
|
532
|
+
project='test_project',
|
|
533
|
+
location='global',
|
|
534
|
+
http_options=_transport_options(
|
|
535
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
536
|
+
async_transport=mock_transport,
|
|
537
|
+
),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
with _patch_auth_default():
|
|
541
|
+
try:
|
|
542
|
+
await client.async_request(
|
|
543
|
+
http_method='GET', path='path', request_dict={}
|
|
544
|
+
)
|
|
545
|
+
assert False, 'Expected APIError to be raised.'
|
|
546
|
+
except errors.APIError as e:
|
|
547
|
+
assert e.code == 504
|
|
548
|
+
mock_transport.handle_async_request.assert_called()
|
|
549
|
+
|
|
550
|
+
asyncio.run(run())
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def test_async_retries_failed_request_retries_unsuccessfully_at_request_level():
|
|
554
|
+
api_client.has_aiohttp = False
|
|
555
|
+
|
|
556
|
+
async def run():
|
|
557
|
+
mock_transport = mock.Mock(spec=httpx.AsyncBaseTransport)
|
|
558
|
+
mock_transport.handle_async_request.side_effect = (
|
|
559
|
+
_httpx_response(429),
|
|
560
|
+
_httpx_response(504),
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
client = api_client.BaseApiClient(
|
|
564
|
+
vertexai=True,
|
|
565
|
+
project='test_project',
|
|
566
|
+
location='global',
|
|
567
|
+
http_options=_transport_options(
|
|
568
|
+
async_transport=mock_transport,
|
|
569
|
+
),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
with _patch_auth_default():
|
|
573
|
+
try:
|
|
574
|
+
await client.async_request(
|
|
575
|
+
http_method='GET',
|
|
576
|
+
path='path',
|
|
577
|
+
request_dict={},
|
|
578
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
579
|
+
)
|
|
580
|
+
assert False, 'Expected APIError to be raised.'
|
|
581
|
+
except errors.APIError as e:
|
|
582
|
+
assert e.code == 504
|
|
583
|
+
mock_transport.handle_async_request.assert_called()
|
|
584
|
+
|
|
585
|
+
asyncio.run(run())
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# Async aiohttp
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
async def _aiohttp_async_response(status: int):
|
|
592
|
+
"""Has to return a coroutine hence async."""
|
|
593
|
+
response = mock.Mock(spec=aiohttp.ClientResponse)
|
|
594
|
+
response.status = status
|
|
595
|
+
response.headers = {'status-code': str(status)}
|
|
596
|
+
response.json.return_value = {}
|
|
597
|
+
response.text.return_value = 'test'
|
|
598
|
+
return response
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
@requires_aiohttp
|
|
602
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
603
|
+
def test_aiohttp_disabled_retries_successful_request_executes_once(
|
|
604
|
+
mock_request,
|
|
605
|
+
):
|
|
606
|
+
api_client.has_aiohttp = True # Force aiohttp
|
|
607
|
+
|
|
608
|
+
async def run():
|
|
609
|
+
mock_request.return_value = _aiohttp_async_response(200)
|
|
610
|
+
|
|
611
|
+
client = api_client.BaseApiClient(
|
|
612
|
+
vertexai=True,
|
|
613
|
+
project='test_project',
|
|
614
|
+
location='global',
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
with _patch_auth_default():
|
|
618
|
+
response = await client.async_request(
|
|
619
|
+
http_method='GET', path='path', request_dict={}
|
|
620
|
+
)
|
|
621
|
+
mock_request.assert_called_once()
|
|
622
|
+
assert response.headers['status-code'] == '200'
|
|
623
|
+
|
|
624
|
+
asyncio.run(run())
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@requires_aiohttp
|
|
628
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
629
|
+
def test_aiohttp_disabled_retries_failed_request_executes_once(mock_request):
|
|
630
|
+
api_client.has_aiohttp = True
|
|
631
|
+
|
|
632
|
+
async def run():
|
|
633
|
+
mock_request.return_value = _aiohttp_async_response(429)
|
|
634
|
+
|
|
635
|
+
client = api_client.BaseApiClient(
|
|
636
|
+
vertexai=True,
|
|
637
|
+
project='test_project',
|
|
638
|
+
location='global',
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
with _patch_auth_default():
|
|
642
|
+
try:
|
|
643
|
+
await client.async_request(
|
|
644
|
+
http_method='GET', path='path', request_dict={}
|
|
645
|
+
)
|
|
646
|
+
assert False, 'Expected APIError to be raised.'
|
|
647
|
+
except errors.APIError as e:
|
|
648
|
+
assert e.code == 429
|
|
649
|
+
mock_request.assert_called_once()
|
|
650
|
+
|
|
651
|
+
asyncio.run(run())
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
@requires_aiohttp
|
|
655
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
656
|
+
def test_aiohttp_retries_successful_request_executes_once(mock_request):
|
|
657
|
+
api_client.has_aiohttp = True
|
|
658
|
+
|
|
659
|
+
async def run():
|
|
660
|
+
mock_request.return_value = _aiohttp_async_response(200)
|
|
661
|
+
|
|
662
|
+
client = api_client.BaseApiClient(
|
|
663
|
+
vertexai=True,
|
|
664
|
+
project='test_project',
|
|
665
|
+
location='global',
|
|
666
|
+
http_options=_transport_options(
|
|
667
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
668
|
+
),
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
with _patch_auth_default():
|
|
672
|
+
response = await client.async_request(
|
|
673
|
+
http_method='GET', path='path', request_dict={}
|
|
674
|
+
)
|
|
675
|
+
mock_request.assert_called_once()
|
|
676
|
+
assert response.headers['status-code'] == '200'
|
|
677
|
+
|
|
678
|
+
asyncio.run(run())
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
@requires_aiohttp
|
|
682
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
683
|
+
def test_aiohttp_retries_failed_request_retries_successfully(mock_request):
|
|
684
|
+
api_client.has_aiohttp = True
|
|
685
|
+
|
|
686
|
+
async def run():
|
|
687
|
+
mock_request.side_effect = (
|
|
688
|
+
_aiohttp_async_response(429),
|
|
689
|
+
_aiohttp_async_response(200),
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
client = api_client.BaseApiClient(
|
|
693
|
+
vertexai=True,
|
|
694
|
+
project='test_project',
|
|
695
|
+
location='global',
|
|
696
|
+
http_options=_transport_options(
|
|
697
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
698
|
+
),
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
with _patch_auth_default():
|
|
702
|
+
response = await client.async_request(
|
|
703
|
+
http_method='GET', path='path', request_dict={}
|
|
704
|
+
)
|
|
705
|
+
mock_request.assert_called()
|
|
706
|
+
assert response.headers['status-code'] == '200'
|
|
707
|
+
|
|
708
|
+
asyncio.run(run())
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
@requires_aiohttp
|
|
712
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
713
|
+
def test_aiohttp_retries_failed_request_retries_successfully_at_request_level(
|
|
714
|
+
mock_request,
|
|
715
|
+
):
|
|
716
|
+
api_client.has_aiohttp = True
|
|
717
|
+
|
|
718
|
+
async def run():
|
|
719
|
+
mock_request.side_effect = (
|
|
720
|
+
_aiohttp_async_response(429),
|
|
721
|
+
_aiohttp_async_response(200),
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
client = api_client.BaseApiClient(
|
|
725
|
+
vertexai=True,
|
|
726
|
+
project='test_project',
|
|
727
|
+
location='global',
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
with _patch_auth_default():
|
|
731
|
+
response = await client.async_request(
|
|
732
|
+
http_method='GET',
|
|
733
|
+
path='path',
|
|
734
|
+
request_dict={},
|
|
735
|
+
http_options=types.HttpOptions(
|
|
736
|
+
retry_options=_RETRY_OPTIONS
|
|
737
|
+
), # At request level.
|
|
738
|
+
)
|
|
739
|
+
mock_request.assert_called()
|
|
740
|
+
assert response.headers['status-code'] == '200'
|
|
741
|
+
|
|
742
|
+
asyncio.run(run())
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
@requires_aiohttp
|
|
746
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
747
|
+
def test_aiohttp_retries_failed_request_retries_unsuccessfully(mock_request):
|
|
748
|
+
api_client.has_aiohttp = True
|
|
749
|
+
|
|
750
|
+
async def run():
|
|
751
|
+
mock_request.side_effect = (
|
|
752
|
+
_aiohttp_async_response(429),
|
|
753
|
+
_aiohttp_async_response(504),
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
client = api_client.BaseApiClient(
|
|
757
|
+
vertexai=True,
|
|
758
|
+
project='test_project',
|
|
759
|
+
location='global',
|
|
760
|
+
http_options=_transport_options(
|
|
761
|
+
http_options=types.HttpOptions(retry_options=_RETRY_OPTIONS),
|
|
762
|
+
),
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
with _patch_auth_default():
|
|
766
|
+
try:
|
|
767
|
+
await client.async_request(
|
|
768
|
+
http_method='GET', path='path', request_dict={}
|
|
769
|
+
)
|
|
770
|
+
assert False, 'Expected APIError to be raised.'
|
|
771
|
+
except errors.APIError as e:
|
|
772
|
+
assert e.code == 504
|
|
773
|
+
mock_request.assert_called()
|
|
774
|
+
|
|
775
|
+
asyncio.run(run())
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
@requires_aiohttp
|
|
779
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
780
|
+
def test_aiohttp_retries_failed_request_retries_unsuccessfully_at_request_level(
|
|
781
|
+
mock_request,
|
|
782
|
+
):
|
|
783
|
+
api_client.has_aiohttp = True
|
|
784
|
+
|
|
785
|
+
async def run():
|
|
786
|
+
mock_request.side_effect = (
|
|
787
|
+
_aiohttp_async_response(429),
|
|
788
|
+
_aiohttp_async_response(504),
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
client = api_client.BaseApiClient(
|
|
792
|
+
vertexai=True,
|
|
793
|
+
project='test_project',
|
|
794
|
+
location='global',
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
with _patch_auth_default():
|
|
798
|
+
try:
|
|
799
|
+
await client.async_request(
|
|
800
|
+
http_method='GET',
|
|
801
|
+
path='path',
|
|
802
|
+
request_dict={},
|
|
803
|
+
http_options={'retry_options': _RETRY_OPTIONS}, # At request level.
|
|
804
|
+
)
|
|
805
|
+
assert False, 'Expected APIError to be raised.'
|
|
806
|
+
except errors.APIError as e:
|
|
807
|
+
assert e.code == 504
|
|
808
|
+
mock_request.assert_called()
|
|
809
|
+
|
|
810
|
+
asyncio.run(run())
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
@requires_aiohttp
|
|
814
|
+
@mock.patch.object(aiohttp.ClientSession, 'request', autospec=True)
|
|
815
|
+
def test_aiohttp_retries_client_connector_error_retries_successfully(
|
|
816
|
+
mock_request,
|
|
817
|
+
):
|
|
818
|
+
api_client.has_aiohttp = True
|
|
819
|
+
|
|
820
|
+
async def run():
|
|
821
|
+
mock_request.side_effect = (
|
|
822
|
+
aiohttp.ClientConnectorError(
|
|
823
|
+
connection_key=aiohttp.client_reqrep.ConnectionKey(
|
|
824
|
+
'localhost', 80, False, True, None, None, None
|
|
825
|
+
),
|
|
826
|
+
os_error=OSError,
|
|
827
|
+
),
|
|
828
|
+
_aiohttp_async_response(200),
|
|
829
|
+
)
|
|
830
|
+
# The request will be automatically retried once, if catching the
|
|
831
|
+
# ClientConnectorError.
|
|
832
|
+
|
|
833
|
+
client = api_client.BaseApiClient(
|
|
834
|
+
vertexai=True,
|
|
835
|
+
project='test_project',
|
|
836
|
+
location='global',
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
with _patch_auth_default():
|
|
840
|
+
response = await client.async_request(
|
|
841
|
+
http_method='GET', path='path', request_dict={}
|
|
842
|
+
)
|
|
843
|
+
mock_request.assert_called()
|
|
844
|
+
assert response.headers['status-code'] == '200'
|
|
845
|
+
|
|
846
|
+
asyncio.run(run())
|