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
14 changes: 12 additions & 2 deletions google/genai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@

"""Google Gen AI SDK"""

from . import interactions
import importlib
from typing import Any

from . import types
from . import version
from .client import Client


__version__ = version.__version__

__all__ = ['Client']
__all__ = ['Client', 'interactions', 'types']


def __getattr__(name: str) -> Any:
if name == 'interactions':
module = importlib.import_module('.interactions', __name__)
globals()[name] = module
return module
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
49 changes: 32 additions & 17 deletions google/genai/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
# limitations under the License.
#

from __future__ import annotations

import asyncio
import os
from typing import Any, Optional, Union
from typing import Any, Optional, TYPE_CHECKING, Union
import warnings

import google.auth
import pydantic

from . import _common
from ._api_client import BaseApiClient
from ._base_url import get_base_url
from ._replay_api_client import ReplayApiClient
Expand All @@ -35,22 +39,17 @@
from .tunings import AsyncTunings, Tunings
from .types import HttpOptions, HttpOptionsDict, HttpRetryOptions

import warnings

from . import _common

from ._gaos.google_genai import (
AsyncGeminiNextGenAgents,
AsyncGeminiNextGenInteractions,
AsyncGeminiNextGenWebhooks,
GeminiNextGenAgents,
GeminiNextGenInteractions,
GeminiNextGenWebhooks,
build_google_genai_async_client,
build_google_genai_client,
)
from ._gaos.sdk import AsyncGenAI as AsyncGeminiNextGenAPI
from ._gaos.sdk import GenAI as GeminiNextGenAPI
if TYPE_CHECKING:
from ._gaos.google_genai import (
AsyncGeminiNextGenAgents,
AsyncGeminiNextGenInteractions,
AsyncGeminiNextGenWebhooks,
GeminiNextGenAgents,
GeminiNextGenInteractions,
GeminiNextGenWebhooks,
)
from ._gaos.sdk import AsyncGenAI as AsyncGeminiNextGenAPI
from ._gaos.sdk import GenAI as GeminiNextGenAPI

_agent_experimental_warned = False

Expand All @@ -77,6 +76,8 @@ def __init__(self, api_client: BaseApiClient):

@property
def _nextgen_client(self) -> AsyncGeminiNextGenAPI:
from ._gaos.google_genai import build_google_genai_async_client

if self._nextgen_client_instance is None:
self._nextgen_client_instance = build_google_genai_async_client(
self._api_client
Expand All @@ -85,18 +86,24 @@ def _nextgen_client(self) -> AsyncGeminiNextGenAPI:

@property
def interactions(self) -> AsyncGeminiNextGenInteractions:
from ._gaos.google_genai import AsyncGeminiNextGenInteractions

if self._interactions is None:
self._interactions = AsyncGeminiNextGenInteractions(self._api_client)
return self._interactions

@property
def webhooks(self) -> AsyncGeminiNextGenWebhooks:
from ._gaos.google_genai import AsyncGeminiNextGenWebhooks

if self._webhooks is None:
self._webhooks = AsyncGeminiNextGenWebhooks(self._api_client)
return self._webhooks

@property
def agents(self) -> AsyncGeminiNextGenAgents:
from ._gaos.google_genai import AsyncGeminiNextGenAgents

global _agent_experimental_warned
if not _agent_experimental_warned:
_agent_experimental_warned = True
Expand Down Expand Up @@ -400,6 +407,8 @@ def _get_api_client(

@property
def _nextgen_client(self) -> GeminiNextGenAPI:
from ._gaos.google_genai import build_google_genai_client

if self._nextgen_client_instance is None:
self._nextgen_client_instance = build_google_genai_client(
self._api_client
Expand All @@ -408,18 +417,24 @@ def _nextgen_client(self) -> GeminiNextGenAPI:

@property
def interactions(self) -> GeminiNextGenInteractions:
from ._gaos.google_genai import GeminiNextGenInteractions

if self._interactions is None:
self._interactions = GeminiNextGenInteractions(self._api_client)
return self._interactions

@property
def webhooks(self) -> GeminiNextGenWebhooks:
from ._gaos.google_genai import GeminiNextGenWebhooks

if self._webhooks is None:
self._webhooks = GeminiNextGenWebhooks(self._api_client)
return self._webhooks

@property
def agents(self) -> GeminiNextGenAgents:
from ._gaos.google_genai import GeminiNextGenAgents

global _agent_experimental_warned
if not _agent_experimental_warned:
_agent_experimental_warned = True
Expand Down
130 changes: 130 additions & 0 deletions google/genai/tests/client/test_lazy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Copyright 2025 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.
#
# pylint: disable=protected-access


"""Tests for the deferred import of the NextGen backends and interactions.

The client exposes the interactions/webhooks/agents/nextgen backends through
properties that import the heavy ``_gaos`` backend only on first access, and the
top-level package exposes ``interactions`` as a lazy module attribute. These
tests exercise those deferred-import paths: each accessor builds the correct
backend, memoizes it, and (for agents) emits the experimental warning.
"""

import sys

import pytest

from ... import Client
from ... import client as client_module
from ..._gaos.google_genai import AsyncGeminiNextGenAgents
from ..._gaos.google_genai import AsyncGeminiNextGenInteractions
from ..._gaos.google_genai import AsyncGeminiNextGenWebhooks
from ..._gaos.google_genai import GeminiNextGenAgents
from ..._gaos.google_genai import GeminiNextGenInteractions
from ..._gaos.google_genai import GeminiNextGenWebhooks
from ..._gaos.sdk import AsyncGenAI
from ..._gaos.sdk import GenAI

_GENAI_PACKAGE = Client.__module__.rsplit('.', 1)[0]


@pytest.fixture(autouse=True)
def _fresh_env(monkeypatch):
monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
for var in (
'GOOGLE_CLOUD_PROJECT',
'GEMINI_API_KEY',
'GOOGLE_CLOUD_LOCATION',
):
monkeypatch.delenv(var, raising=False)
# ``agents`` warns once per process via a module global; reset it so every
# test observes deterministic warning behavior regardless of test order.
monkeypatch.setattr(client_module, '_agent_experimental_warned', False)


def test_sync_nextgen_client_is_lazy_and_cached():
client = Client()
nextgen = client._nextgen_client
assert isinstance(nextgen, GenAI)
assert client._nextgen_client is nextgen


def test_sync_interactions_is_lazy_and_cached():
client = Client()
interactions = client.interactions
assert isinstance(interactions, GeminiNextGenInteractions)
assert client.interactions is interactions


def test_sync_webhooks_is_lazy_and_cached():
client = Client()
webhooks = client.webhooks
assert isinstance(webhooks, GeminiNextGenWebhooks)
assert client.webhooks is webhooks


def test_sync_agents_is_lazy_cached_and_warns():
client = Client()
with pytest.warns(UserWarning, match='experimental'):
agents = client.agents
assert isinstance(agents, GeminiNextGenAgents)
assert client.agents is agents


def test_async_nextgen_client_is_lazy_and_cached():
client = Client()
nextgen = client.aio._nextgen_client
assert isinstance(nextgen, AsyncGenAI)
assert client.aio._nextgen_client is nextgen


def test_async_interactions_is_lazy_and_cached():
client = Client()
interactions = client.aio.interactions
assert isinstance(interactions, AsyncGeminiNextGenInteractions)
assert client.aio.interactions is interactions


def test_async_webhooks_is_lazy_and_cached():
client = Client()
webhooks = client.aio.webhooks
assert isinstance(webhooks, AsyncGeminiNextGenWebhooks)
assert client.aio.webhooks is webhooks


def test_async_agents_is_lazy_cached_and_warns():
client = Client()
with pytest.warns(UserWarning, match='experimental'):
agents = client.aio.agents
assert isinstance(agents, AsyncGeminiNextGenAgents)
assert client.aio.agents is agents


@pytest.mark.skipif(
"config.getoption('--private')",
reason=(
'Package-level lazy interactions attribute is a public-SDK-only'
' feature.'
),
)
def test_package_lazily_exposes_interactions_submodule():
package = sys.modules[_GENAI_PACKAGE]
# Drop any memoized attribute so the lazy ``__getattr__`` path runs.
package.__dict__.pop('interactions', None)
interactions = package.interactions
assert interactions.__name__ == f'{_GENAI_PACKAGE}.interactions'
assert interactions is sys.modules[f'{_GENAI_PACKAGE}.interactions']
Loading