Skip to content

Commit 3ec1cc3

Browse files
committed
test bind param method
1 parent ffd382c commit 3ec1cc3

File tree

2 files changed

+151
-109
lines changed

2 files changed

+151
-109
lines changed

packages/toolbox-core/src/toolbox_core/tool.py

Lines changed: 109 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -197,112 +197,112 @@ async def __call__(self, *args: Any, **kwargs: Any) -> str:
197197
"result", str(ret)
198198
) # Return string representation if 'result' key missing
199199

200-
# # --- Methods for adding state (return new instances) ---
201-
# def _copy_with_updates(
202-
# self: T,
203-
# *,
204-
# add_bound_params: dict[str, Union[Any, Callable[[], Any]]] | None = None,
205-
# ) -> T:
206-
# """Creates a new instance with updated bound params."""
207-
# new_bound_params = self.__bound_params.copy()
208-
# if add_bound_params:
209-
# new_bound_params.update(add_bound_params)
210-
#
211-
# return self.__class__(
212-
# session=self.__session,
213-
# base_url=self.__base_url,
214-
# name=self.__name__,
215-
# desc=self.__doc__ or "",
216-
# params=self.__original_params,
217-
# _bound_params=new_bound_params,
218-
# )
219-
#
220-
# def bind_params(
221-
# self: T,
222-
# params_to_bind: dict[str, Union[Any, Callable[[], Any]]],
223-
# strict: bool = True,
224-
# ) -> T:
225-
# """
226-
# Returns a *new* tool instance with the provided parameters bound.
227-
#
228-
# Bound parameters are pre-filled values or callables that resolve to values
229-
# when the tool is called. They are not part of the signature of the
230-
# returned tool instance.
231-
#
232-
# Args:
233-
# params_to_bind: A dictionary mapping parameter names to their
234-
# values or callables that return the value.
235-
# strict: If True (default), raises ValueError if attempting to bind
236-
# a parameter that doesn't exist in the original tool signature
237-
# or is already bound in this instance. If False, issues a warning.
238-
#
239-
# Returns:
240-
# A new ToolboxTool instance with the specified parameters bound.
241-
#
242-
# Raises:
243-
# ValueError: If strict is True and a parameter name is invalid or
244-
# already bound.
245-
# """
246-
# invalid_params: list[str] = []
247-
# duplicate_params: list[str] = []
248-
# original_param_names = {p.name for p in self.__original_params}
249-
#
250-
# for name in params_to_bind:
251-
# if name not in original_param_names:
252-
# invalid_params.append(name)
253-
# elif name in self.__bound_params:
254-
# duplicate_params.append(name)
255-
#
256-
# messages: list[str] = []
257-
# if invalid_params:
258-
# messages.append(
259-
# f"Parameter(s) {', '.join(invalid_params)} do not exist in the signature for tool '{self.__name__}'."
260-
# )
261-
# if duplicate_params:
262-
# messages.append(
263-
# f"Parameter(s) {', '.join(duplicate_params)} are already bound in this instance of tool '{self.__name__}'."
264-
# )
265-
#
266-
# if messages:
267-
# message = "\n".join(messages)
268-
# if strict:
269-
# raise ValueError(message)
270-
# else:
271-
# warnings.warn(message)
272-
# # Filter out problematic params if not strict
273-
# params_to_bind = {
274-
# k: v
275-
# for k, v in params_to_bind.items()
276-
# if k not in invalid_params and k not in duplicate_params
277-
# }
278-
#
279-
# if not params_to_bind:
280-
# return self
281-
#
282-
# return self._copy_with_updates(add_bound_params=params_to_bind)
283-
#
284-
# def bind_param(
285-
# self: T,
286-
# param_name: str,
287-
# param_value: Union[Any, Callable[[], Any]],
288-
# strict: bool = True,
289-
# ) -> T:
290-
# """
291-
# Returns a *new* tool instance with the provided parameter bound.
292-
#
293-
# Convenience method for binding a single parameter.
294-
#
295-
# Args:
296-
# param_name: The name of the parameter to bind.
297-
# param_value: The value or callable for the parameter.
298-
# strict: If True (default), raises ValueError if the parameter name
299-
# is invalid or already bound. If False, issues a warning.
300-
#
301-
# Returns:
302-
# A new ToolboxTool instance with the specified parameter bound.
303-
#
304-
# Raises:
305-
# ValueError: If strict is True and the parameter name is invalid or
306-
# already bound.
307-
# """
308-
# return self.bind_params({param_name: param_value}, strict=strict)
200+
# --- Methods for adding state (return new instances) ---
201+
def _copy_with_updates(
202+
self: T,
203+
*,
204+
add_bound_params: dict[str, Union[Any, Callable[[], Any]]] | None = None,
205+
) -> T:
206+
"""Creates a new instance with updated bound params."""
207+
new_bound_params = self.__bound_params.copy()
208+
if add_bound_params:
209+
new_bound_params.update(add_bound_params)
210+
211+
return self.__class__(
212+
session=self.__session,
213+
base_url=self.__base_url,
214+
name=self.__name__,
215+
desc=self.__doc__ or "",
216+
params=self.__original_params,
217+
bound_params=new_bound_params,
218+
)
219+
220+
def bind_params(
221+
self: T,
222+
params_to_bind: dict[str, Union[Any, Callable[[], Any]]],
223+
strict: bool = True,
224+
) -> T:
225+
"""
226+
Returns a *new* tool instance with the provided parameters bound.
227+
228+
Bound parameters are pre-filled values or callables that resolve to values
229+
when the tool is called. They are not part of the signature of the
230+
returned tool instance.
231+
232+
Args:
233+
params_to_bind: A dictionary mapping parameter names to their
234+
values or callables that return the value.
235+
strict: If True (default), raises ValueError if attempting to bind
236+
a parameter that doesn't exist in the original tool signature
237+
or is already bound in this instance. If False, issues a warning.
238+
239+
Returns:
240+
A new ToolboxTool instance with the specified parameters bound.
241+
242+
Raises:
243+
ValueError: If strict is True and a parameter name is invalid or
244+
already bound.
245+
"""
246+
invalid_params: list[str] = []
247+
duplicate_params: list[str] = []
248+
original_param_names = {p.name for p in self.__original_params}
249+
250+
for name in params_to_bind:
251+
if name not in original_param_names:
252+
invalid_params.append(name)
253+
elif name in self.__bound_params:
254+
duplicate_params.append(name)
255+
256+
messages: list[str] = []
257+
if invalid_params:
258+
messages.append(
259+
f"Parameter(s) {', '.join(invalid_params)} do not exist in the signature for tool '{self.__name__}'."
260+
)
261+
if duplicate_params:
262+
messages.append(
263+
f"Parameter(s) {', '.join(duplicate_params)} are already bound in this instance of tool '{self.__name__}'."
264+
)
265+
266+
if messages:
267+
message = "\n".join(messages)
268+
if strict:
269+
raise ValueError(message)
270+
else:
271+
warnings.warn(message)
272+
# Filter out problematic params if not strict
273+
params_to_bind = {
274+
k: v
275+
for k, v in params_to_bind.items()
276+
if k not in invalid_params and k not in duplicate_params
277+
}
278+
279+
if not params_to_bind:
280+
return self
281+
282+
return self._copy_with_updates(add_bound_params=params_to_bind)
283+
284+
def bind_param(
285+
self: T,
286+
param_name: str,
287+
param_value: Union[Any, Callable[[], Any]],
288+
strict: bool = True,
289+
) -> T:
290+
"""
291+
Returns a *new* tool instance with the provided parameter bound.
292+
293+
Convenience method for binding a single parameter.
294+
295+
Args:
296+
param_name: The name of the parameter to bind.
297+
param_value: The value or callable for the parameter.
298+
strict: If True (default), raises ValueError if the parameter name
299+
is invalid or already bound. If False, issues a warning.
300+
301+
Returns:
302+
A new ToolboxTool instance with the specified parameter bound.
303+
304+
Raises:
305+
ValueError: If strict is True and the parameter name is invalid or
306+
already bound.
307+
"""
308+
return self.bind_params({param_name: param_value}, strict=strict)

packages/toolbox-core/tests/test_tool.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,45 @@ async def test_bound_parameter_static_value_call(
193193
},
194194
)
195195
mock_session.post.return_value.__aenter__.return_value.json.assert_awaited_once()
196+
197+
@pytest.fixture
198+
def tool_with_bound_arg2(
199+
self,
200+
tool: ToolboxTool
201+
) -> ToolboxTool:
202+
new_tool = tool.bind_params({"opt_arg": 88})
203+
return new_tool
204+
205+
@pytest.mark.asyncio
206+
async def test_bound_parameter_static_value_call2(
207+
self,
208+
tool_with_bound_arg2: ToolboxTool,
209+
mock_session: MagicMock,
210+
tool_details: dict[str, Any],
211+
configure_mock_response: Callable,
212+
bound_arg1_value: str,
213+
):
214+
"""Test calling a tool with a statically bound parameter."""
215+
expected_result = "Bound call success!"
216+
configure_mock_response(json_data={"result": expected_result})
217+
218+
req_kwarg_val = True # The only remaining required arg
219+
220+
# Call *without* providing arg1, but provide the others
221+
result = await tool_with_bound_arg2(
222+
arg1="random_val", req_kwarg=req_kwarg_val
223+
)
224+
225+
assert result == expected_result
226+
mock_session.post.assert_called_once_with(
227+
tool_details["expected_url"],
228+
# Payload should include the bound value for arg1
229+
json={
230+
"arg1": "random_val",
231+
"opt_arg": 88,
232+
"req_kwarg": req_kwarg_val,
233+
},
234+
)
235+
mock_session.post.return_value.__aenter__.return_value.json.assert_awaited_once()
236+
237+

0 commit comments

Comments
 (0)