Skip to content

Commit 3e6336f

Browse files
committed
create sync tool
1 parent 827129f commit 3e6336f

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
import asyncio
16+
from asyncio import AbstractEventLoop
17+
from threading import Thread
18+
from typing import Any, Awaitable, Callable, Mapping, TypeVar, Union
19+
20+
from .tool import ToolboxTool
21+
22+
T = TypeVar("T")
23+
24+
25+
class ToolboxSyncTool:
26+
"""
27+
A synchronous wrapper around an asynchronous ToolboxTool instance.
28+
29+
This class allows calling the underlying async tool's __call__ method
30+
synchronously. It also wraps methods like `add_auth_token_getters` and
31+
`bind_parameters` to ensure they return new instances of this synchronous
32+
wrapper.
33+
"""
34+
35+
def __init__(
36+
self, async_tool: ToolboxTool, loop: AbstractEventLoop, thread: Thread
37+
):
38+
"""
39+
Initializes the synchronous wrapper.
40+
41+
Args:
42+
async_tool: An instance of the asynchronous ToolboxTool.
43+
"""
44+
if not isinstance(async_tool, ToolboxTool):
45+
raise TypeError("async_tool must be an instance of ToolboxTool")
46+
47+
self.__async_tool = async_tool
48+
self.__loop = loop
49+
self.__thread = thread
50+
51+
# Delegate introspection attributes to the wrapped async tool
52+
self.__name__ = self.__async_tool.__name__
53+
self.__doc__ = self.__async_tool.__doc__
54+
self.__signature__ = self.__async_tool.__signature__
55+
self.__annotations__ = self.__async_tool.__annotations__
56+
# TODO: self.__qualname__ ?? (Consider if needed)
57+
58+
def __run_as_sync(self, coro: Awaitable[T]) -> T:
59+
"""Run an async coroutine synchronously"""
60+
if not self.__loop:
61+
raise Exception(
62+
"Cannot call synchronous methods before the background loop is initialized."
63+
)
64+
return asyncio.run_coroutine_threadsafe(coro, self.__loop).result()
65+
66+
async def __run_as_async(self, coro: Awaitable[T]) -> T:
67+
"""Run an async coroutine asynchronously"""
68+
69+
# If a loop has not been provided, attempt to run in current thread.
70+
if not self.__loop:
71+
return await coro
72+
73+
# Otherwise, run in the background thread.
74+
return await asyncio.wrap_future(
75+
asyncio.run_coroutine_threadsafe(coro, self.__loop)
76+
)
77+
78+
def __call__(self, *args: Any, **kwargs: Any) -> str:
79+
"""
80+
Synchronously calls the underlying remote tool.
81+
82+
This method blocks until the asynchronous call completes and returns
83+
the result.
84+
85+
Args:
86+
*args: Positional arguments for the tool.
87+
**kwargs: Keyword arguments for the tool.
88+
89+
Returns:
90+
The string result returned by the remote tool execution.
91+
92+
Raises:
93+
Any exception raised by the underlying async tool's __call__ method
94+
or during asyncio execution.
95+
"""
96+
return self.__run_as_sync(self.__async_tool(**kwargs))
97+
98+
def add_auth_token_getters(
99+
self,
100+
auth_token_getters: Mapping[str, Callable[[], str]],
101+
) -> "ToolboxSyncTool":
102+
"""
103+
Registers auth token getters and returns a new SyncToolboxTool instance.
104+
105+
Args:
106+
auth_token_getters: A mapping of authentication service names to
107+
callables that return the corresponding authentication token.
108+
109+
Returns:
110+
A new SyncToolboxTool instance wrapping the updated async tool.
111+
"""
112+
new_async_tool = self.__async_tool.add_auth_token_getters(auth_token_getters)
113+
return ToolboxSyncTool(new_async_tool, self.__loop, self.__thread)
114+
115+
def bind_parameters(
116+
self, bound_params: Mapping[str, Union[Callable[[], Any], Any]]
117+
) -> "ToolboxSyncTool":
118+
"""
119+
Binds parameters and returns a new SyncToolboxTool instance.
120+
121+
Args:
122+
bound_params: A mapping of parameter names to values or callables that
123+
produce values.
124+
125+
Returns:
126+
A new SyncToolboxTool instance wrapping the updated async tool.
127+
"""
128+
new_async_tool = self.__async_tool.bind_parameters(bound_params)
129+
return ToolboxSyncTool(new_async_tool, self.__loop, self.__thread)

0 commit comments

Comments
 (0)