Skip to content

Commit 2d89a9e

Browse files
wanlin31copybara-github
authored andcommitted
feat: Add Triggers resource to GoogleGenAI client.
PiperOrigin-RevId: 937597086
1 parent 0ab9525 commit 2d89a9e

5 files changed

Lines changed: 242 additions & 0 deletions

File tree

google/genai/_gaos/google_genai.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
from .types import interactions
4949
from .types.security import Security
5050
from .utils import BackoffStrategy, RetryConfig, eventstreaming
51+
from .triggers import AsyncTriggers as GeneratedAsyncTriggers
52+
from .triggers import Triggers as GeneratedTriggers
5153
from .webhooks import AsyncWebhooks as GeneratedAsyncWebhooks
5254
from .webhooks import Webhooks as GeneratedWebhooks
5355

@@ -667,6 +669,88 @@ async def delete(self, *args: Any, **kwargs: Any) -> Any:
667669
return await async_wrap_sdk_call(super().delete, *args, **kwargs)
668670

669671

672+
class GeminiNextGenTriggers(GeneratedTriggers):
673+
"""Public triggers resource backed by the NextGen client.
674+
675+
Subclasses the generated resource so every public method is wrapped in
676+
`wrap_sdk_call`, translating per-operation `GenAiError` raises into the
677+
status-code `APIError` hierarchy exposed at the
678+
`google.genai._interactions` import surface.
679+
"""
680+
681+
def __init__(self, api_client: Any):
682+
sdk = build_google_genai_client(api_client)
683+
super().__init__(sdk.sdk_configuration, parent_ref=sdk)
684+
685+
if not TYPE_CHECKING:
686+
@property
687+
def with_raw_response(self):
688+
return _RawResponseAccessorProxy(super().with_raw_response)
689+
690+
@property
691+
def with_streaming_response(self):
692+
return _RawResponseAccessorProxy(super().with_streaming_response)
693+
694+
def create(self, *args: Any, **kwargs: Any) -> Any:
695+
return wrap_sdk_call(super().create, *args, **kwargs)
696+
697+
def list(self, *args: Any, **kwargs: Any) -> Any:
698+
return wrap_sdk_call(super().list, *args, **kwargs)
699+
700+
def get(self, *args: Any, **kwargs: Any) -> Any:
701+
return wrap_sdk_call(super().get, *args, **kwargs)
702+
703+
def update(self, *args: Any, **kwargs: Any) -> Any:
704+
return wrap_sdk_call(super().update, *args, **kwargs)
705+
706+
def delete(self, *args: Any, **kwargs: Any) -> Any:
707+
return wrap_sdk_call(super().delete, *args, **kwargs)
708+
709+
def run(self, *args: Any, **kwargs: Any) -> Any:
710+
return wrap_sdk_call(super().run, *args, **kwargs)
711+
712+
def list_executions(self, *args: Any, **kwargs: Any) -> Any:
713+
return wrap_sdk_call(super().list_executions, *args, **kwargs)
714+
715+
716+
class AsyncGeminiNextGenTriggers(GeneratedAsyncTriggers):
717+
"""Async public triggers resource backed by the NextGen client."""
718+
719+
def __init__(self, api_client: Any):
720+
sdk = build_google_genai_async_client(api_client)
721+
super().__init__(sdk.sdk_configuration, parent_ref=sdk)
722+
723+
if not TYPE_CHECKING:
724+
@property
725+
def with_raw_response(self):
726+
return _AsyncRawResponseAccessorProxy(super().with_raw_response)
727+
728+
@property
729+
def with_streaming_response(self):
730+
return _AsyncRawResponseAccessorProxy(super().with_streaming_response)
731+
732+
async def create(self, *args: Any, **kwargs: Any) -> Any:
733+
return await async_wrap_sdk_call(super().create, *args, **kwargs)
734+
735+
async def list(self, *args: Any, **kwargs: Any) -> Any:
736+
return await async_wrap_sdk_call(super().list, *args, **kwargs)
737+
738+
async def get(self, *args: Any, **kwargs: Any) -> Any:
739+
return await async_wrap_sdk_call(super().get, *args, **kwargs)
740+
741+
async def update(self, *args: Any, **kwargs: Any) -> Any:
742+
return await async_wrap_sdk_call(super().update, *args, **kwargs)
743+
744+
async def delete(self, *args: Any, **kwargs: Any) -> Any:
745+
return await async_wrap_sdk_call(super().delete, *args, **kwargs)
746+
747+
async def run(self, *args: Any, **kwargs: Any) -> Any:
748+
return await async_wrap_sdk_call(super().run, *args, **kwargs)
749+
750+
async def list_executions(self, *args: Any, **kwargs: Any) -> Any:
751+
return await async_wrap_sdk_call(super().list_executions, *args, **kwargs)
752+
753+
670754
def _add_output_properties_if_interaction(value: Any) -> Any:
671755
normalized = _normalize_interaction_shape(value)
672756
if normalized is None:

google/genai/client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@
4242
from ._gaos.google_genai import (
4343
AsyncGeminiNextGenAgents,
4444
AsyncGeminiNextGenInteractions,
45+
AsyncGeminiNextGenTriggers,
4546
AsyncGeminiNextGenWebhooks,
4647
GeminiNextGenAgents,
4748
GeminiNextGenInteractions,
49+
GeminiNextGenTriggers,
4850
GeminiNextGenWebhooks,
4951
build_google_genai_async_client,
5052
build_google_genai_client,
@@ -53,6 +55,7 @@
5355
from ._gaos.sdk import GenAI as GeminiNextGenAPI
5456

5557
_agent_experimental_warned = False
58+
_trigger_experimental_warned = False
5659

5760

5861
class AsyncClient:
@@ -74,6 +77,7 @@ def __init__(self, api_client: BaseApiClient):
7477
self._agents: Optional[AsyncGeminiNextGenAgents] = None
7578
self._interactions: Optional[AsyncGeminiNextGenInteractions] = None
7679
self._webhooks: Optional[AsyncGeminiNextGenWebhooks] = None
80+
self._triggers: Optional[AsyncGeminiNextGenTriggers] = None
7781

7882
@property
7983
def _nextgen_client(self) -> AsyncGeminiNextGenAPI:
@@ -109,6 +113,20 @@ def agents(self) -> AsyncGeminiNextGenAgents:
109113
self._agents = AsyncGeminiNextGenAgents(self._api_client)
110114
return self._agents
111115

116+
@property
117+
def triggers(self) -> AsyncGeminiNextGenTriggers:
118+
global _trigger_experimental_warned
119+
if not _trigger_experimental_warned:
120+
_trigger_experimental_warned = True
121+
warnings.warn(
122+
'Triggers usage is experimental and may change in future versions.',
123+
category=UserWarning,
124+
stacklevel=1,
125+
)
126+
if self._triggers is None:
127+
self._triggers = AsyncGeminiNextGenTriggers(self._api_client)
128+
return self._triggers
129+
112130
@property
113131
def models(self) -> AsyncModels:
114132
return self._models
@@ -361,6 +379,7 @@ def __init__(
361379
self._agents: Optional[GeminiNextGenAgents] = None
362380
self._interactions: Optional[GeminiNextGenInteractions] = None
363381
self._webhooks: Optional[GeminiNextGenWebhooks] = None
382+
self._triggers: Optional[GeminiNextGenTriggers] = None
364383

365384
@staticmethod
366385
def _get_api_client(
@@ -432,6 +451,20 @@ def agents(self) -> GeminiNextGenAgents:
432451
self._agents = GeminiNextGenAgents(self._api_client)
433452
return self._agents
434453

454+
@property
455+
def triggers(self) -> GeminiNextGenTriggers:
456+
global _trigger_experimental_warned
457+
if not _trigger_experimental_warned:
458+
_trigger_experimental_warned = True
459+
warnings.warn(
460+
'Triggers usage is experimental and may change in future versions.',
461+
category=UserWarning,
462+
stacklevel=2,
463+
)
464+
if self._triggers is None:
465+
self._triggers = GeminiNextGenTriggers(self._api_client)
466+
return self._triggers
467+
435468
@property
436469
def chats(self) -> Chats:
437470
return Chats(modules=self.models)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# gaos tests package
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2026 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+
"""Lifecycle tests for Triggers API."""
15+
16+
from __future__ import annotations
17+
18+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
19+
import json
20+
import threading
21+
22+
from ... import Client
23+
24+
TRIGGER_BODY = {
25+
"id": "projects/my-project/locations/my-location/triggers/svc_abc",
26+
"schedule": "0 0 * * *",
27+
"time_zone": "UTC",
28+
"interaction": {
29+
"agent": "projects/my-project/locations/my-location/agents/my-agent",
30+
},
31+
}
32+
33+
34+
class _RecordingHandler(BaseHTTPRequestHandler):
35+
captured: list[str] = []
36+
37+
def _record_and_respond(self) -> None:
38+
self.captured.append(f"{self.command} {self.path}")
39+
payload = json.dumps(TRIGGER_BODY).encode()
40+
self.send_response(200)
41+
self.send_header("content-type", "application/json")
42+
self.send_header("content-length", str(len(payload)))
43+
self.end_headers()
44+
self.wfile.write(payload)
45+
46+
do_GET = _record_and_respond
47+
do_POST = _record_and_respond
48+
do_PATCH = _record_and_respond
49+
do_DELETE = _record_and_respond
50+
51+
def log_message(self, *args) -> None:
52+
pass
53+
54+
55+
def test_python_triggers_lifecycle_routes_through_google_genai_client(
56+
monkeypatch,
57+
):
58+
monkeypatch.delenv("GOOGLE_GENAI_USE_VERTEXAI", raising=False)
59+
captured: list[str] = []
60+
handler = type("Handler", (_RecordingHandler,), {"captured": captured})
61+
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
62+
thread = threading.Thread(target=server.serve_forever, daemon=True)
63+
thread.start()
64+
try:
65+
client = Client(
66+
api_key="test-api-key",
67+
http_options={
68+
"api_version": "v1beta",
69+
"base_url": f"http://127.0.0.1:{server.server_port}",
70+
},
71+
)
72+
73+
trigger = client.triggers.create(
74+
interaction={
75+
"agent": (
76+
"projects/my-project/locations/my-location/agents/my-agent"
77+
),
78+
},
79+
schedule="0 0 * * *",
80+
time_zone="UTC",
81+
)
82+
client.triggers.list(filter_="some-filter", page_size=10)
83+
fetched = client.triggers.get(id="svc_abc")
84+
client.triggers.update(
85+
id="svc_abc",
86+
display_name="updated-name",
87+
status="paused",
88+
)
89+
client.triggers.delete(id="svc_abc")
90+
client.triggers.run(id="svc_abc")
91+
client.triggers.list_executions(id="svc_abc", page_size=5)
92+
93+
assert trigger.schedule == "0 0 * * *"
94+
assert fetched.schedule == "0 0 * * *"
95+
assert captured == [
96+
"POST /v1beta/triggers",
97+
"GET /v1beta/triggers?filter=some-filter&page_size=10",
98+
"GET /v1beta/triggers/svc_abc",
99+
"PATCH /v1beta/triggers/svc_abc",
100+
"DELETE /v1beta/triggers/svc_abc",
101+
"POST /v1beta/triggers/svc_abc/executions",
102+
"GET /v1beta/triggers/svc_abc/executions?page_size=5",
103+
]
104+
finally:
105+
server.shutdown()
106+
thread.join()
107+
server.server_close()

google/genai/triggers.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2026 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+
"""Expose Google GenAI triggers types."""
16+
17+
from ._gaos.types.triggers import *

0 commit comments

Comments
 (0)