From 79bb1ff10629bcbed73002eba7165156856cf3d9 Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Thu, 10 Sep 2026 10:08:50 -0700 Subject: [PATCH] feat(interactions): Configure Speakeasy generator for httpx2 support Enable httpClientLibrary: httpx2 in Speakeasy gen.yaml and document generator migration requirements and runtime implications. https://github.com/googleapis/python-genai/issues/2680 FUTURE_COPYBARA_INTEGRATE_REVIEW=https://github.com/googleapis/python-genai/pull/3180 from googleapis:release-please--branches--main 4386e38cea5553127ad557ebf8b296e725317fec PiperOrigin-RevId: 979252016 --- google/genai/_gaos/_hooks/adapters.py | 2 +- .../genai/_gaos/_hooks/asyncregistration.py | 2 +- google/genai/_gaos/_hooks/asyncsdkhooks.py | 2 +- google/genai/_gaos/_hooks/asynctypes.py | 2 +- .../genai/_gaos/_hooks/google_genai_auth.py | 2 +- google/genai/_gaos/_hooks/sdkhooks.py | 2 +- google/genai/_gaos/_hooks/types.py | 2 +- google/genai/_gaos/_internal.py | 2 +- google/genai/_gaos/agents.py | 2 +- google/genai/_gaos/basesdk.py | 20 +- google/genai/_gaos/credentials.py | 2 +- google/genai/_gaos/environments.py | 2 +- .../_gaos/errors/cancelinteractionbyid.py | 2 +- .../genai/_gaos/errors/createinteraction.py | 2 +- .../genai/_gaos/errors/deleteinteraction.py | 2 +- .../genai/_gaos/errors/genaidefaulterror.py | 2 +- google/genai/_gaos/errors/genaierror.py | 2 +- .../genai/_gaos/errors/getinteractionbyid.py | 2 +- .../_gaos/errors/responsevalidationerror.py | 2 +- google/genai/_gaos/files.py | 2 +- google/genai/_gaos/google_genai.py | 2 +- google/genai/_gaos/httpclient.py | 2 +- google/genai/_gaos/interactions.py | 2 +- google/genai/_gaos/lib/compat_errors.py | 20 +- google/genai/_gaos/sdk.py | 2 +- google/genai/_gaos/triggers.py | 2 +- google/genai/_gaos/utils/eventstreaming.py | 2 +- google/genai/_gaos/utils/headers.py | 2 +- google/genai/_gaos/utils/logger.py | 2 +- google/genai/_gaos/utils/response_helpers.py | 2 +- google/genai/_gaos/utils/retries.py | 2 +- google/genai/_gaos/utils/serializers.py | 2 +- .../_gaos/utils/unmarshal_json_response.py | 2 +- google/genai/_gaos/utils/values.py | 2 +- google/genai/_gaos/voices.py | 2 +- google/genai/_gaos/webhooks.py | 2 +- .../tests/interactions/test_httpx_compat.py | 245 ++++++++++++++++++ 37 files changed, 302 insertions(+), 51 deletions(-) diff --git a/google/genai/_gaos/_hooks/adapters.py b/google/genai/_gaos/_hooks/adapters.py index 5a9c41c70e..85520e9c04 100644 --- a/google/genai/_gaos/_hooks/adapters.py +++ b/google/genai/_gaos/_hooks/adapters.py @@ -34,7 +34,7 @@ BeforeRequestHook, ) import asyncio -import httpx +import httpx2 as httpx from typing import Optional, Tuple, Union # ============================================================================ diff --git a/google/genai/_gaos/_hooks/asyncregistration.py b/google/genai/_gaos/_hooks/asyncregistration.py index 5612ebaa67..1009594b11 100644 --- a/google/genai/_gaos/_hooks/asyncregistration.py +++ b/google/genai/_gaos/_hooks/asyncregistration.py @@ -42,7 +42,7 @@ def init_async_hooks(hooks: AsyncHooks): Example: from .asynctypes import AsyncBeforeRequestHook, BeforeRequestContext - import httpx + import httpx2 as httpx class MyAsyncHook(AsyncBeforeRequestHook): async def before_request(self, hook_ctx: BeforeRequestContext, request: httpx.Request): diff --git a/google/genai/_gaos/_hooks/asyncsdkhooks.py b/google/genai/_gaos/_hooks/asyncsdkhooks.py index ad81b1d067..6ed1a56f09 100644 --- a/google/genai/_gaos/_hooks/asyncsdkhooks.py +++ b/google/genai/_gaos/_hooks/asyncsdkhooks.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -import httpx +import httpx2 as httpx from .asynctypes import ( AsyncBeforeRequestHook, AsyncAfterSuccessHook, diff --git a/google/genai/_gaos/_hooks/asynctypes.py b/google/genai/_gaos/_hooks/asynctypes.py index 0869abee1f..a8b53b060c 100644 --- a/google/genai/_gaos/_hooks/asynctypes.py +++ b/google/genai/_gaos/_hooks/asynctypes.py @@ -24,7 +24,7 @@ BeforeRequestContext, ) from abc import ABC, abstractmethod -import httpx +import httpx2 as httpx from typing import Optional, Tuple, Union diff --git a/google/genai/_gaos/_hooks/google_genai_auth.py b/google/genai/_gaos/_hooks/google_genai_auth.py index 7ec30b9d87..4faeab80b0 100644 --- a/google/genai/_gaos/_hooks/google_genai_auth.py +++ b/google/genai/_gaos/_hooks/google_genai_auth.py @@ -21,7 +21,7 @@ from typing import Any, Callable, Mapping, Optional, Union, cast -import httpx +import httpx2 as httpx from .. import types, utils from .types import BeforeRequestContext, BeforeRequestHook diff --git a/google/genai/_gaos/_hooks/sdkhooks.py b/google/genai/_gaos/_hooks/sdkhooks.py index 5dc2b7bc32..f872d2361f 100644 --- a/google/genai/_gaos/_hooks/sdkhooks.py +++ b/google/genai/_gaos/_hooks/sdkhooks.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -import httpx +import httpx2 as httpx from .types import ( SDKInitHook, BeforeRequestContext, diff --git a/google/genai/_gaos/_hooks/types.py b/google/genai/_gaos/_hooks/types.py index fdee868291..80a6091668 100644 --- a/google/genai/_gaos/_hooks/types.py +++ b/google/genai/_gaos/_hooks/types.py @@ -19,7 +19,7 @@ from ..sdkconfiguration import SDKConfiguration from abc import ABC, abstractmethod -import httpx +import httpx2 as httpx from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union diff --git a/google/genai/_gaos/_internal.py b/google/genai/_gaos/_internal.py index abd8077f0f..bea4a1ba83 100644 --- a/google/genai/_gaos/_internal.py +++ b/google/genai/_gaos/_internal.py @@ -22,7 +22,7 @@ from .basesdk import AsyncBaseSDK, BaseSDK from .types import OptionalNullable, UNSET from .utils import get_security_from_env, response_helpers -import httpx +import httpx2 as httpx from typing import Any, Mapping, Optional, Union, cast diff --git a/google/genai/_gaos/agents.py b/google/genai/_gaos/agents.py index 1118eeed94..f386634629 100644 --- a/google/genai/_gaos/agents.py +++ b/google/genai/_gaos/agents.py @@ -24,7 +24,7 @@ from .types.agents import agent as agents_agent, agenttool as agents_agenttool from .utils import get_security_from_env, response_helpers from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, Iterable, List, Mapping, Optional, Union, cast diff --git a/google/genai/_gaos/basesdk.py b/google/genai/_gaos/basesdk.py index 0306654c11..36562971ad 100644 --- a/google/genai/_gaos/basesdk.py +++ b/google/genai/_gaos/basesdk.py @@ -26,18 +26,24 @@ ) from .sdkconfiguration import SDKConfiguration from .utils import RetryConfig, SerializedRequestBody, get_body_content -import httpx +import httpx2 as httpx try: - import httpx2 + import httpx as httpx1 except ImportError: - httpx2 = None + httpx1 = None _HTTPX_TIMEOUT_TYPES = ( (httpx.Timeout,) - if httpx2 is None - else (httpx.Timeout, httpx2.Timeout) + if httpx1 is None + else (httpx.Timeout, httpx1.Timeout) ) + + +def _default_timeout(client): + if httpx1 is not None and isinstance(client, (httpx1.Client, httpx1.AsyncClient)): + return httpx1.USE_CLIENT_DEFAULT + return httpx.USE_CLIENT_DEFAULT from typing import Any, Callable, List, Mapping, Optional, Tuple from urllib.parse import parse_qs, urlparse @@ -234,7 +240,7 @@ def _build_request_with_client( data=serialized_request_body.data, files=serialized_request_body.files, headers=headers, - timeout=timeout if timeout is not None else httpx.USE_CLIENT_DEFAULT, + timeout=timeout if timeout is not None else _default_timeout(client), ) def do_request( @@ -501,7 +507,7 @@ def _build_request_with_client( data=serialized_request_body.data, files=serialized_request_body.files, headers=headers, - timeout=timeout if timeout is not None else httpx.USE_CLIENT_DEFAULT, + timeout=timeout if timeout is not None else _default_timeout(client), ) async def do_request_async( diff --git a/google/genai/_gaos/credentials.py b/google/genai/_gaos/credentials.py index f132a9d5dd..fd712fd00a 100644 --- a/google/genai/_gaos/credentials.py +++ b/google/genai/_gaos/credentials.py @@ -23,7 +23,7 @@ from .types import BaseModel, OptionalNullable, UNSET, credentials, interactions from .utils import get_security_from_env, response_helpers from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, List, Literal, Mapping, Optional, Union, cast, overload diff --git a/google/genai/_gaos/environments.py b/google/genai/_gaos/environments.py index 92846eb14b..273ac3d827 100644 --- a/google/genai/_gaos/environments.py +++ b/google/genai/_gaos/environments.py @@ -30,7 +30,7 @@ from .types.interactions import source as interactions_source from .utils import get_security_from_env, response_helpers from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, Iterable, List, Mapping, Optional, Union, cast diff --git a/google/genai/_gaos/errors/cancelinteractionbyid.py b/google/genai/_gaos/errors/cancelinteractionbyid.py index 8fe786cbfd..3b86900f7f 100644 --- a/google/genai/_gaos/errors/cancelinteractionbyid.py +++ b/google/genai/_gaos/errors/cancelinteractionbyid.py @@ -22,7 +22,7 @@ from ..types import BaseModel from ..types.interactions import error as interactions_error from dataclasses import dataclass, field -import httpx +import httpx2 as httpx from typing import Optional diff --git a/google/genai/_gaos/errors/createinteraction.py b/google/genai/_gaos/errors/createinteraction.py index 776c4ed298..e7b16b8495 100644 --- a/google/genai/_gaos/errors/createinteraction.py +++ b/google/genai/_gaos/errors/createinteraction.py @@ -22,7 +22,7 @@ from ..types import BaseModel from ..types.interactions import error as interactions_error from dataclasses import dataclass, field -import httpx +import httpx2 as httpx from typing import Optional diff --git a/google/genai/_gaos/errors/deleteinteraction.py b/google/genai/_gaos/errors/deleteinteraction.py index 05f2536ef9..1351ed4d4d 100644 --- a/google/genai/_gaos/errors/deleteinteraction.py +++ b/google/genai/_gaos/errors/deleteinteraction.py @@ -22,7 +22,7 @@ from ..types import BaseModel from ..types.interactions import error as interactions_error from dataclasses import dataclass, field -import httpx +import httpx2 as httpx from typing import Optional diff --git a/google/genai/_gaos/errors/genaidefaulterror.py b/google/genai/_gaos/errors/genaidefaulterror.py index 318be10505..6fccd8f492 100644 --- a/google/genai/_gaos/errors/genaidefaulterror.py +++ b/google/genai/_gaos/errors/genaidefaulterror.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -import httpx +import httpx2 as httpx from typing import Optional from dataclasses import dataclass diff --git a/google/genai/_gaos/errors/genaierror.py b/google/genai/_gaos/errors/genaierror.py index 516f13a5dd..01d2788d39 100644 --- a/google/genai/_gaos/errors/genaierror.py +++ b/google/genai/_gaos/errors/genaierror.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -import httpx +import httpx2 as httpx from typing import Optional from dataclasses import dataclass, field diff --git a/google/genai/_gaos/errors/getinteractionbyid.py b/google/genai/_gaos/errors/getinteractionbyid.py index a29d02716d..1a0bb79939 100644 --- a/google/genai/_gaos/errors/getinteractionbyid.py +++ b/google/genai/_gaos/errors/getinteractionbyid.py @@ -22,7 +22,7 @@ from ..types import BaseModel from ..types.interactions import error as interactions_error from dataclasses import dataclass, field -import httpx +import httpx2 as httpx from typing import Optional diff --git a/google/genai/_gaos/errors/responsevalidationerror.py b/google/genai/_gaos/errors/responsevalidationerror.py index 24fe08c107..64049787e7 100644 --- a/google/genai/_gaos/errors/responsevalidationerror.py +++ b/google/genai/_gaos/errors/responsevalidationerror.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -import httpx +import httpx2 as httpx from typing import Optional from dataclasses import dataclass diff --git a/google/genai/_gaos/files.py b/google/genai/_gaos/files.py index 6155254a35..58918f4f9b 100644 --- a/google/genai/_gaos/files.py +++ b/google/genai/_gaos/files.py @@ -23,7 +23,7 @@ from .types import OptionalNullable, UNSET, environments from .utils import get_security_from_env, response_helpers from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, Mapping, Optional, Union, cast diff --git a/google/genai/_gaos/google_genai.py b/google/genai/_gaos/google_genai.py index 95c9fdf7c8..95aeb152da 100644 --- a/google/genai/_gaos/google_genai.py +++ b/google/genai/_gaos/google_genai.py @@ -31,7 +31,7 @@ import mimetypes import os -import httpx +import httpx2 as httpx from ._hooks.google_genai_auth import ( GOOGLE_GENAI_API_REVISION as _GOOGLE_GENAI_API_REVISION, diff --git a/google/genai/_gaos/httpclient.py b/google/genai/_gaos/httpclient.py index 0e1a8dcd74..a2aefcfa04 100644 --- a/google/genai/_gaos/httpclient.py +++ b/google/genai/_gaos/httpclient.py @@ -20,7 +20,7 @@ # pyright: reportReturnType = false import asyncio from typing_extensions import Protocol, runtime_checkable -import httpx +import httpx2 as httpx from typing import Any, Optional, Union diff --git a/google/genai/_gaos/interactions.py b/google/genai/_gaos/interactions.py index d8951ad442..bf2d64e415 100644 --- a/google/genai/_gaos/interactions.py +++ b/google/genai/_gaos/interactions.py @@ -24,7 +24,7 @@ from .utils import get_security_from_env, response_helpers from .utils.eventstreaming import AsyncStream, Stream from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, Dict, List, Literal, Mapping, Optional, Union, cast, overload diff --git a/google/genai/_gaos/lib/compat_errors.py b/google/genai/_gaos/lib/compat_errors.py index 7bd524927e..c2f378117b 100644 --- a/google/genai/_gaos/lib/compat_errors.py +++ b/google/genai/_gaos/lib/compat_errors.py @@ -29,22 +29,22 @@ import json from typing import Any, Awaitable, Callable, Optional, TypeVar, cast +import httpx2 as httpx + try: - import httpx2 + import httpx as httpx1 except ImportError: - httpx2 = None - -import httpx + httpx1 = None _HTTPX_TIMEOUT_ERRORS = ( (httpx.TimeoutException,) - if httpx2 is None - else (httpx.TimeoutException, httpx2.TimeoutException) + if httpx1 is None + else (httpx.TimeoutException, httpx1.TimeoutException) ) _HTTPX_HTTP_ERRORS = ( (httpx.HTTPError,) - if httpx2 is None - else (httpx.HTTPError, httpx2.HTTPError) + if httpx1 is None + else (httpx.HTTPError, httpx1.HTTPError) ) from ..errors.genaierror import GenAiError @@ -336,8 +336,8 @@ def wrap_sdk_error(error: BaseException) -> BaseException: _WRAP_EXCEPTIONS = ( (GenAiError, NoResponseError, httpx.HTTPError) - if httpx2 is None - else (GenAiError, NoResponseError, httpx.HTTPError, httpx2.HTTPError) + if httpx1 is None + else (GenAiError, NoResponseError, httpx.HTTPError, httpx1.HTTPError) ) diff --git a/google/genai/_gaos/sdk.py b/google/genai/_gaos/sdk.py index 2f493dcf71..2b926a9fd8 100644 --- a/google/genai/_gaos/sdk.py +++ b/google/genai/_gaos/sdk.py @@ -31,7 +31,7 @@ from .types import OptionalNullable, UNSET from .utils.logger import Logger, get_default_logger from .utils.retries import RetryConfig -import httpx +import httpx2 as httpx import importlib import importlib.util import sys diff --git a/google/genai/_gaos/triggers.py b/google/genai/_gaos/triggers.py index df24c69207..900c81abb6 100644 --- a/google/genai/_gaos/triggers.py +++ b/google/genai/_gaos/triggers.py @@ -27,7 +27,7 @@ from .types.triggers import triggerupdate as triggers_triggerupdate from .utils import get_security_from_env, response_helpers from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, Mapping, Optional, Union, cast diff --git a/google/genai/_gaos/utils/eventstreaming.py b/google/genai/_gaos/utils/eventstreaming.py index 423689b29a..17830f4df6 100644 --- a/google/genai/_gaos/utils/eventstreaming.py +++ b/google/genai/_gaos/utils/eventstreaming.py @@ -31,7 +31,7 @@ AsyncGenerator, Tuple, ) -import httpx +import httpx2 as httpx T = TypeVar("T") diff --git a/google/genai/_gaos/utils/headers.py b/google/genai/_gaos/utils/headers.py index b95606938a..ba52a76ac1 100644 --- a/google/genai/_gaos/utils/headers.py +++ b/google/genai/_gaos/utils/headers.py @@ -23,7 +23,7 @@ List, Optional, ) -from httpx import Headers +from httpx2 import Headers from pydantic import BaseModel from pydantic.fields import FieldInfo diff --git a/google/genai/_gaos/utils/logger.py b/google/genai/_gaos/utils/logger.py index 1085e0b046..05e77d4119 100644 --- a/google/genai/_gaos/utils/logger.py +++ b/google/genai/_gaos/utils/logger.py @@ -17,7 +17,7 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -import httpx +import httpx2 as httpx import logging import os from typing import Any, Protocol diff --git a/google/genai/_gaos/utils/response_helpers.py b/google/genai/_gaos/utils/response_helpers.py index bbb844d39a..88d78ee961 100644 --- a/google/genai/_gaos/utils/response_helpers.py +++ b/google/genai/_gaos/utils/response_helpers.py @@ -46,7 +46,7 @@ overload, ) -import httpx +import httpx2 as httpx from pydantic import BaseModel from typing_extensions import TypeAliasType diff --git a/google/genai/_gaos/utils/retries.py b/google/genai/_gaos/utils/retries.py index 1dc0099962..5d957a2364 100644 --- a/google/genai/_gaos/utils/retries.py +++ b/google/genai/_gaos/utils/retries.py @@ -24,7 +24,7 @@ from email.utils import parsedate_to_datetime from typing import List, Optional -import httpx +import httpx2 as httpx class BackoffStrategy: diff --git a/google/genai/_gaos/utils/serializers.py b/google/genai/_gaos/utils/serializers.py index 99747b4f68..cd469f6c35 100644 --- a/google/genai/_gaos/utils/serializers.py +++ b/google/genai/_gaos/utils/serializers.py @@ -25,7 +25,7 @@ import typing_extensions from typing_extensions import get_origin -import httpx +import httpx2 as httpx from pydantic import ConfigDict, create_model from pydantic_core import from_json diff --git a/google/genai/_gaos/utils/unmarshal_json_response.py b/google/genai/_gaos/utils/unmarshal_json_response.py index 4ba1a154be..216d28f12f 100644 --- a/google/genai/_gaos/utils/unmarshal_json_response.py +++ b/google/genai/_gaos/utils/unmarshal_json_response.py @@ -19,7 +19,7 @@ from typing import Any, Optional, Type, TypeVar, overload -import httpx +import httpx2 as httpx from .serializers import unmarshal_json, construct_unvalidated from .. import errors diff --git a/google/genai/_gaos/utils/values.py b/google/genai/_gaos/utils/values.py index cc953332d6..2c8fe8a2e8 100644 --- a/google/genai/_gaos/utils/values.py +++ b/google/genai/_gaos/utils/values.py @@ -24,7 +24,7 @@ import os from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union, cast -from httpx import Response +from httpx2 import Response from pydantic import BaseModel from pydantic.fields import FieldInfo diff --git a/google/genai/_gaos/voices.py b/google/genai/_gaos/voices.py index 454f466f56..cf1db9978f 100644 --- a/google/genai/_gaos/voices.py +++ b/google/genai/_gaos/voices.py @@ -24,7 +24,7 @@ from .types.voices import voice_input as voices_voice_input from .utils import get_security_from_env, response_helpers from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, Iterable, List, Mapping, Optional, Union, cast diff --git a/google/genai/_gaos/webhooks.py b/google/genai/_gaos/webhooks.py index 145a392590..d07f82e274 100644 --- a/google/genai/_gaos/webhooks.py +++ b/google/genai/_gaos/webhooks.py @@ -29,7 +29,7 @@ ) from .utils import get_security_from_env, response_helpers from .utils.unmarshal_json_response import unmarshal_json_response -import httpx +import httpx2 as httpx from typing import Any, Iterable, List, Mapping, Optional, Union, cast diff --git a/google/genai/tests/interactions/test_httpx_compat.py b/google/genai/tests/interactions/test_httpx_compat.py index ea867f6a04..1b656ca145 100644 --- a/google/genai/tests/interactions/test_httpx_compat.py +++ b/google/genai/tests/interactions/test_httpx_compat.py @@ -409,3 +409,248 @@ def handler(request: httpx2.Request) -> httpx2.Response: assert len(recorded_requests) == 1 assert isinstance(recorded_requests[0], httpx2.Request) assert result.status == "completed" + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +def test_interactions_api_error_handling_with_httpx2(): + def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response( + 404, + headers={"content-type": "application/json"}, + content=b'{"error": {"code": 404, "message": "Interaction not found"}}', + ) + + http_client = httpx2.Client(transport=httpx2.MockTransport(handler)) + try: + client = client_lib.Client( + api_key="fake-key", + http_options={"httpx_client": http_client}, + ) + with pytest.raises(compat_errors.NotFoundError) as exc_info: + client.interactions.create( + model="gemini-1.5-flash", + input="Hello", + ) + assert exc_info.value.status_code == 404 + assert isinstance(exc_info.value.response, httpx2.Response) + assert isinstance(exc_info.value.request, httpx2.Request) + assert exc_info.value.response.status_code == 404 + finally: + http_client.close() + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +@pytest.mark.asyncio +async def test_async_interactions_api_error_handling_with_httpx2(): + def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response( + 400, + headers={"content-type": "application/json"}, + content=b'{"error": {"code": 400, "message": "Invalid request"}}', + ) + + http_async_client = httpx2.AsyncClient( + transport=httpx2.MockTransport(handler) + ) + try: + client = client_lib.Client( + api_key="fake-key", + http_options={"httpx_async_client": http_async_client}, + ) + with pytest.raises(compat_errors.BadRequestError) as exc_info: + await client.aio.interactions.create( + model="gemini-1.5-flash", + input="Hello", + ) + assert exc_info.value.status_code == 400 + assert isinstance(exc_info.value.response, httpx2.Response) + assert isinstance(exc_info.value.request, httpx2.Request) + finally: + await http_async_client.aclose() + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +def test_interactions_with_raw_response_httpx2(): + def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response( + 200, + headers={"content-type": "application/json"}, + content=b'{"id": "raw-interaction-id", "status": "completed"}', + ) + + http_client = httpx2.Client(transport=httpx2.MockTransport(handler)) + try: + client = client_lib.Client( + api_key="fake-key", + http_options={"httpx_client": http_client}, + ) + raw = client.interactions.with_raw_response.create( + model="gemini-1.5-flash", + input="Hello", + ) + assert isinstance(raw.http_response, httpx2.Response) + assert raw.http_response.status_code == 200 + parsed = raw.parse() + assert parsed.id == "raw-interaction-id" + assert parsed.status == "completed" + finally: + http_client.close() + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +@pytest.mark.asyncio +async def test_async_interactions_with_raw_response_httpx2(): + def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response( + 200, + headers={"content-type": "application/json"}, + content=b'{"id": "async-raw-id", "status": "completed"}', + ) + + http_async_client = httpx2.AsyncClient( + transport=httpx2.MockTransport(handler) + ) + try: + client = client_lib.Client( + api_key="fake-key", + http_options={"httpx_async_client": http_async_client}, + ) + raw = await client.aio.interactions.with_raw_response.create( + model="gemini-1.5-flash", + input="Hello", + ) + assert isinstance(raw.http_response, httpx2.Response) + assert raw.http_response.status_code == 200 + parsed = await raw.parse() + assert parsed.id == "async-raw-id" + assert parsed.status == "completed" + finally: + await http_async_client.aclose() + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +def test_interactions_streaming_events_with_httpx2(): + def handler(request: httpx2.Request) -> httpx2.Response: + sse_content = ( + b'data: {"event_id": "e1", "interaction": {"id": "int-1", "status": "in_progress"}}\n\n' + b'data: [DONE]\n\n' + ) + return httpx2.Response( + 200, + headers={"content-type": "text/event-stream"}, + content=sse_content, + ) + + http_client = httpx2.Client(transport=httpx2.MockTransport(handler)) + try: + client = client_lib.Client( + api_key="fake-key", + http_options={"httpx_client": http_client}, + ) + stream = client.interactions.create( + model="gemini-1.5-flash", + input="Hello", + stream=True, + ) + events = list(stream) + assert len(events) == 1 + assert events[0].event_id == "e1" + assert events[0].interaction["id"] == "int-1" + finally: + http_client.close() + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +@pytest.mark.asyncio +async def test_async_interactions_streaming_events_with_httpx2(): + def handler(request: httpx2.Request) -> httpx2.Response: + sse_content = ( + b'data: {"event_id": "e1", "interaction": {"id": "int-async-1", "status": "in_progress"}}\n\n' + b'data: [DONE]\n\n' + ) + return httpx2.Response( + 200, + headers={"content-type": "text/event-stream"}, + content=sse_content, + ) + + http_async_client = httpx2.AsyncClient( + transport=httpx2.MockTransport(handler) + ) + try: + client = client_lib.Client( + api_key="fake-key", + http_options={"httpx_async_client": http_async_client}, + ) + stream = await client.aio.interactions.create( + model="gemini-1.5-flash", + input="Hello", + stream=True, + ) + events = [event async for event in stream] + assert len(events) == 1 + assert events[0].event_id == "e1" + assert events[0].interaction["id"] == "int-async-1" + finally: + await http_async_client.aclose() + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +def test_genaierror_raw_response_typing_httpx2(): + from ..._gaos.errors.genaierror import GenAiError + + resp = httpx2.Response( + 400, + headers={"x-test-header": "test-val"}, + content=b'{"error": "bad request"}', + ) + err = GenAiError("bad request error", raw_response=resp) + assert isinstance(err.raw_response, httpx2.Response) + assert isinstance(err.headers, httpx2.Headers) + assert err.status_code == 400 + assert err.body == '{"error": "bad request"}' + assert err.headers.get("x-test-header") == "test-val" + + +@pytest.mark.skipif( + httpx2 is None, reason="httpx2 not installed in this environment" +) +def test_gaos_works_when_httpx1_not_installed(monkeypatch): + from ..._gaos import basesdk + + monkeypatch.delenv("GOOGLE_GENAI_HTTP_CLIENT", raising=False) + monkeypatch.setattr(basesdk, "httpx1", None) + monkeypatch.setattr(compat_errors, "httpx1", None) + + def handler(request: httpx2.Request) -> httpx2.Response: + return httpx2.Response( + 200, + headers={"content-type": "application/json"}, + content=b'{"id": "no-httpx1-id", "status": "completed"}', + ) + + client = client_lib.Client( + api_key="fake-key", + http_options={"client_args": {"transport": httpx2.MockTransport(handler)}}, + ) + result = client.interactions.create( + model="gemini-1.5-flash", + input="Hello without httpx1", + ) + assert result.status == "completed" + +