Skip to content
Merged
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
4 changes: 4 additions & 0 deletions google/genai/_gaos/_hooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@

from .sdkhooks import *
from .types import *
from .asynctypes import *
from .asyncsdkhooks import *
from .adapters import *
from .registration import *
from .asyncregistration import *
218 changes: 218 additions & 0 deletions google/genai/_gaos/_hooks/adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# pyformat: disable

"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""

from .asynctypes import (
AsyncAfterErrorHook,
AsyncAfterParseErrorHook,
AsyncAfterSuccessHook,
AsyncBeforeRequestHook,
)
from .types import (
AfterErrorContext,
AfterErrorHook,
AfterParseErrorContext,
AfterParseErrorHook,
AfterSuccessContext,
AfterSuccessHook,
BeforeRequestContext,
BeforeRequestHook,
)
import asyncio
import httpx
from typing import Optional, Tuple, Union

# ============================================================================
# Sync to Async Adapters
# ============================================================================
# PERFORMANCE CONSIDERATIONS:
# - Sync-to-Async adapters use asyncio.to_thread() / run_in_executor() to run
# sync hooks in a thread pool. This adds threading overhead and context
# switching costs.
# - Async-to-Sync adapters use asyncio.run() which creates a new event loop
# for each invocation. This has significant overhead.
# - For optimal performance, register hooks that match the execution context:
# * Use native async hooks (AsyncBeforeRequestHook, etc.) for async methods
# * Use native sync hooks (BeforeRequestHook, etc.) for sync methods
# - Adapters are provided for convenience and backward compatibility, but
# native implementations are strongly recommended for production use.
# ============================================================================


class SyncToAsyncBeforeRequestAdapter(AsyncBeforeRequestHook):
"""Adapts a synchronous BeforeRequestHook to work in async contexts.

PERFORMANCE WARNING: Uses asyncio.to_thread() to run the sync hook in a thread pool.
This introduces:
- Thread pool overhead for spawning/managing worker threads
- Context switching between the async event loop and worker threads
- Memory overhead for thread-local storage
- Potential thread pool exhaustion under high concurrency

For performance-critical applications, prefer registering native AsyncBeforeRequestHook
implementations instead of relying on this adapter.
"""

def __init__(self, sync_hook: BeforeRequestHook):
self._sync_hook = sync_hook

async def before_request(
self, hook_ctx: BeforeRequestContext, request: httpx.Request
) -> Union[httpx.Request, Exception]:
return await asyncio.to_thread(
self._sync_hook.before_request, hook_ctx, request
)


class SyncToAsyncAfterSuccessAdapter(AsyncAfterSuccessHook):
"""Adapts a synchronous AfterSuccessHook to work in async contexts.

PERFORMANCE WARNING: Uses asyncio.to_thread() to run the sync hook in a thread pool.
This introduces threading overhead and context switching costs. Prefer native
AsyncAfterSuccessHook implementations for better performance.
"""

def __init__(self, sync_hook: AfterSuccessHook):
self._sync_hook = sync_hook

async def after_success(
self, hook_ctx: AfterSuccessContext, response: httpx.Response
) -> Union[httpx.Response, Exception]:
return await asyncio.to_thread(
self._sync_hook.after_success, hook_ctx, response
)


class SyncToAsyncAfterErrorAdapter(AsyncAfterErrorHook):
"""Adapts a synchronous AfterErrorHook to work in async contexts.

PERFORMANCE WARNING: Uses asyncio.to_thread() to run the sync hook in a thread pool.
This introduces threading overhead and context switching costs. Prefer native
AsyncAfterErrorHook implementations for better performance.
"""

def __init__(self, sync_hook: AfterErrorHook):
self._sync_hook = sync_hook

async def after_error(
self,
hook_ctx: AfterErrorContext,
response: Optional[httpx.Response],
error: Optional[Exception],
) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]:
return await asyncio.to_thread(
self._sync_hook.after_error, hook_ctx, response, error
)


class SyncToAsyncAfterParseErrorAdapter(AsyncAfterParseErrorHook):
"""Adapts a synchronous AfterParseErrorHook to work in async contexts."""

def __init__(self, sync_hook: AfterParseErrorHook):
self._sync_hook = sync_hook

async def after_parse_error(
self,
hook_ctx: AfterParseErrorContext,
response: httpx.Response,
error: Exception,
) -> Exception:
return await asyncio.to_thread(
self._sync_hook.after_parse_error, hook_ctx, response, error
)


# ============================================================================
# Async to Sync Adapters
# ============================================================================


class AsyncToSyncBeforeRequestAdapter(BeforeRequestHook):
"""Adapts an async BeforeRequestHook to work in sync contexts.

PERFORMANCE WARNING: Uses asyncio.run() which creates and tears down a new event
loop for each invocation. This has significant overhead:
- Event loop creation/destruction costs
- Loss of connection pooling and keep-alive benefits
- Cannot leverage existing event loop infrastructure
- Not suitable for high-frequency operations

This adapter is primarily for compatibility in mixed sync/async codebases.
Strongly prefer native sync hooks (BeforeRequestHook) for production use.
"""

def __init__(self, async_hook: AsyncBeforeRequestHook):
self._async_hook = async_hook

def before_request(
self, hook_ctx: BeforeRequestContext, request: httpx.Request
) -> Union[httpx.Request, Exception]:
return asyncio.run(self._async_hook.before_request(hook_ctx, request))


class AsyncToSyncAfterSuccessAdapter(AfterSuccessHook):
"""Adapts an async AfterSuccessHook to work in sync contexts.

PERFORMANCE WARNING: Uses asyncio.run() which creates and tears down a new event
loop for each invocation. This has significant overhead. Strongly prefer native
sync hooks (AfterSuccessHook) for production use.
"""

def __init__(self, async_hook: AsyncAfterSuccessHook):
self._async_hook = async_hook

def after_success(
self, hook_ctx: AfterSuccessContext, response: httpx.Response
) -> Union[httpx.Response, Exception]:
return asyncio.run(self._async_hook.after_success(hook_ctx, response))


class AsyncToSyncAfterErrorAdapter(AfterErrorHook):
"""Adapts an async AfterErrorHook to work in sync contexts.

PERFORMANCE WARNING: Uses asyncio.run() which creates and tears down a new event
loop for each invocation. This has significant overhead. Strongly prefer native
sync hooks (AfterErrorHook) for production use.
"""

def __init__(self, async_hook: AsyncAfterErrorHook):
self._async_hook = async_hook

def after_error(
self,
hook_ctx: AfterErrorContext,
response: Optional[httpx.Response],
error: Optional[Exception],
) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]:
return asyncio.run(self._async_hook.after_error(hook_ctx, response, error))


class AsyncToSyncAfterParseErrorAdapter(AfterParseErrorHook):
"""Adapts an async AfterParseErrorHook to work in sync contexts."""

def __init__(self, async_hook: AsyncAfterParseErrorHook):
self._async_hook = async_hook

def after_parse_error(
self,
hook_ctx: AfterParseErrorContext,
response: httpx.Response,
error: Exception,
) -> Exception:
return asyncio.run(
self._async_hook.after_parse_error(hook_ctx, response, error)
)
66 changes: 66 additions & 0 deletions google/genai/_gaos/_hooks/asyncregistration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# pyformat: disable

from .asynctypes import AsyncHooks
from .adapters import (
SyncToAsyncAfterErrorAdapter,
SyncToAsyncAfterParseErrorAdapter,
SyncToAsyncBeforeRequestAdapter,
)
from .google_genai_auth import GoogleGenAIAuthHook
from ..lib.compat_errors import CompatErrorHook
from typing import cast
from .types import AfterErrorHook, AfterParseErrorHook


# This file is only ever generated once on the first generation and then is free to be modified.
# Any async hooks you wish to add should be registered in the init_async_hooks function.
# Feel free to define them in this file or in separate files in the hooks folder.


def init_async_hooks(hooks: AsyncHooks):
# pylint: disable=unused-argument
"""Add async hooks by calling hooks.register_{sdk_init/before_request/after_success/after_error}_hook
with an instance of a hook that implements that specific AsyncHook interface.
Async hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance.

Async hooks are invoked in async contexts and allow you to use async/await for non-blocking I/O operations.

Example:
from .asynctypes import AsyncBeforeRequestHook, BeforeRequestContext
import httpx

class MyAsyncHook(AsyncBeforeRequestHook):
async def before_request(self, hook_ctx: BeforeRequestContext, request: httpx.Request):
# Perform async operations
token = await fetch_token_from_external_service()

# Modify request
headers = dict(request.headers)
headers["Authorization"] = f"Bearer {token}"
return httpx.Request(request.method, request.url, headers=headers, content=request.content)

hooks.register_before_request_hook(MyAsyncHook())
"""
hooks.register_before_request_hook(
SyncToAsyncBeforeRequestAdapter(GoogleGenAIAuthHook())
)
hooks.register_after_error_hook(
SyncToAsyncAfterErrorAdapter(cast(AfterErrorHook, CompatErrorHook()))
)
hooks.register_after_parse_error_hook(
SyncToAsyncAfterParseErrorAdapter(cast(AfterParseErrorHook, CompatErrorHook()))
)
97 changes: 97 additions & 0 deletions google/genai/_gaos/_hooks/asyncsdkhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# pyformat: disable

"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""

import httpx
from .asynctypes import (
AsyncBeforeRequestHook,
AsyncAfterSuccessHook,
AsyncAfterErrorHook,
AsyncAfterParseErrorHook,
AsyncHooks,
)
from .types import (
BeforeRequestContext,
AfterSuccessContext,
AfterErrorContext,
AfterParseErrorContext,
)
from typing import List, Optional, Tuple


class AsyncSDKHooks(AsyncHooks):
def __init__(self) -> None:
self.before_request_hooks: List[AsyncBeforeRequestHook] = []
self.after_success_hooks: List[AsyncAfterSuccessHook] = []
self.after_error_hooks: List[AsyncAfterErrorHook] = []
self.after_parse_error_hooks: List[AsyncAfterParseErrorHook] = []

def register_before_request_hook(self, hook: AsyncBeforeRequestHook) -> None:
self.before_request_hooks.append(hook)

def register_after_success_hook(self, hook: AsyncAfterSuccessHook) -> None:
self.after_success_hooks.append(hook)

def register_after_error_hook(self, hook: AsyncAfterErrorHook) -> None:
self.after_error_hooks.append(hook)

def register_after_parse_error_hook(self, hook: AsyncAfterParseErrorHook) -> None:
self.after_parse_error_hooks.append(hook)

async def before_request(
self, hook_ctx: BeforeRequestContext, request: httpx.Request
) -> httpx.Request:
for hook in self.before_request_hooks:
out = await hook.before_request(hook_ctx, request)
if isinstance(out, Exception):
raise out
request = out

return request

async def after_success(
self, hook_ctx: AfterSuccessContext, response: httpx.Response
) -> httpx.Response:
for hook in self.after_success_hooks:
out = await hook.after_success(hook_ctx, response)
if isinstance(out, Exception):
raise out
response = out
return response

async def after_error(
self,
hook_ctx: AfterErrorContext,
response: Optional[httpx.Response],
error: Optional[Exception],
) -> Tuple[Optional[httpx.Response], Optional[Exception]]:
for hook in self.after_error_hooks:
result = await hook.after_error(hook_ctx, response, error)
if isinstance(result, Exception):
raise result
response, error = result
return response, error

async def after_parse_error(
self,
hook_ctx: AfterParseErrorContext,
response: httpx.Response,
error: Exception,
) -> Exception:
for hook in self.after_parse_error_hooks:
error = await hook.after_parse_error(hook_ctx, response, error)
return error
Loading
Loading