Skip to content

fix(toolbox-core)!: Standardize on ValueError for improved error consistency #219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 12, 2025
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
2 changes: 1 addition & 1 deletion packages/toolbox-core/src/toolbox_core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ async def load_tool(
# parse the provided definition to a tool
if name not in manifest.tools:
# TODO: Better exception
raise Exception(f"Tool '{name}' not found!")
raise ValueError(f"Tool '{name}' not found!")
tool, used_auth_keys, used_bound_keys = self.__parse_tool(
name,
manifest.tools[name],
Expand Down
6 changes: 4 additions & 2 deletions packages/toolbox-core/src/toolbox_core/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ async def __call__(self, *args: Any, **kwargs: Any) -> str:
req_auth_services = set()
for s in self.__required_authn_params.values():
req_auth_services.update(s)
raise Exception(
raise ValueError(
f"One or more of the following authn services are required to invoke this tool"
f": {','.join(req_auth_services)}"
)
Expand Down Expand Up @@ -329,7 +329,9 @@ def bind_params(
)

if name not in param_names:
raise Exception(f"unable to bind parameters: no parameter named {name}")
raise ValueError(
f"unable to bind parameters: no parameter named {name}"
)

new_params = []
for p in self.__params:
Expand Down
137 changes: 4 additions & 133 deletions packages/toolbox-core/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,9 @@ async def test_load_tool_not_found_in_manifest(aioresponses, test_tool_str):
)

async with ToolboxClient(TEST_BASE_URL) as client:
with pytest.raises(Exception, match=f"Tool '{REQUESTED_TOOL_NAME}' not found!"):
with pytest.raises(
ValueError, match=f"Tool '{REQUESTED_TOOL_NAME}' not found!"
):
await client.load_tool(REQUESTED_TOOL_NAME)

aioresponses.assert_called_once_with(
Expand Down Expand Up @@ -474,7 +476,7 @@ async def test_bind_params_fail(self, tool_name, client):
assert len(tool.__signature__.parameters) == 2
assert "argA" in tool.__signature__.parameters

with pytest.raises(Exception) as e:
with pytest.raises(ValueError) as e:
tool.bind_params({"argC": lambda: 5})
assert "unable to bind parameters: no parameter named argC" in str(e.value)

Expand Down Expand Up @@ -605,138 +607,7 @@ async def test_bind_param_fail(self, tool_name, client):
assert len(tool.__signature__.parameters) == 2
assert "argA" in tool.__signature__.parameters

with pytest.raises(Exception) as e:
tool.bind_param("argC", lambda: 5)
assert "unable to bind parameters: no parameter named argC" in str(e.value)

@pytest.mark.asyncio
async def test_rebind_param_fail(self, tool_name, client):
"""
Tests that 'bind_param' fails when attempting to re-bind a
parameter that has already been bound.
"""
tool = await client.load_tool(tool_name)

assert len(tool.__signature__.parameters) == 2
assert "argA" in tool.__signature__.parameters

tool_with_bound_param = tool.bind_param("argA", lambda: 10)

assert len(tool_with_bound_param.__signature__.parameters) == 1
assert "argA" not in tool_with_bound_param.__signature__.parameters

with pytest.raises(ValueError) as e:
tool_with_bound_param.bind_param("argA", lambda: 20)

assert "cannot re-bind parameter: parameter 'argA' is already bound" in str(
e.value
)

@pytest.mark.asyncio
async def test_bind_param_static_value_success(self, tool_name, client):
"""
Tests bind_param method with a static value.
"""

bound_value = "Test value"

tool = await client.load_tool(tool_name)
bound_tool = tool.bind_param("argB", bound_value)

assert bound_tool is not tool
assert "argB" not in bound_tool.__signature__.parameters
assert "argA" in bound_tool.__signature__.parameters

passed_value_a = 42
res_payload = await bound_tool(argA=passed_value_a)

assert res_payload == {"argA": passed_value_a, "argB": bound_value}

@pytest.mark.asyncio
async def test_bind_param_sync_callable_value_success(self, tool_name, client):
"""
Tests bind_param method with a sync callable value.
"""

bound_value_result = True
bound_sync_callable = Mock(return_value=bound_value_result)

tool = await client.load_tool(tool_name)
bound_tool = tool.bind_param("argB", bound_sync_callable)

assert bound_tool is not tool
assert "argB" not in bound_tool.__signature__.parameters
assert "argA" in bound_tool.__signature__.parameters

passed_value_a = 42
res_payload = await bound_tool(argA=passed_value_a)

assert res_payload == {"argA": passed_value_a, "argB": bound_value_result}
bound_sync_callable.assert_called_once()

@pytest.mark.asyncio
async def test_bind_param_async_callable_value_success(self, tool_name, client):
"""
Tests bind_param method with an async callable value.
"""

bound_value_result = True
bound_async_callable = AsyncMock(return_value=bound_value_result)

tool = await client.load_tool(tool_name)
bound_tool = tool.bind_param("argB", bound_async_callable)

assert bound_tool is not tool
assert "argB" not in bound_tool.__signature__.parameters
assert "argA" in bound_tool.__signature__.parameters

passed_value_a = 42
res_payload = await bound_tool(argA=passed_value_a)

assert res_payload == {"argA": passed_value_a, "argB": bound_value_result}
bound_async_callable.assert_awaited_once()

@pytest.mark.asyncio
async def test_bind_param_success(self, tool_name, client):
"""Tests 'bind_param' with a bound parameter specified."""
tool = await client.load_tool(tool_name)

assert len(tool.__signature__.parameters) == 2
assert "argA" in tool.__signature__.parameters

tool = tool.bind_param("argA", 5)

assert len(tool.__signature__.parameters) == 1
assert "argA" not in tool.__signature__.parameters

res = await tool(True)
assert "argA" in res

@pytest.mark.asyncio
async def test_bind_callable_param_success(self, tool_name, client):
"""Tests 'bind_param' with a bound parameter specified."""
tool = await client.load_tool(tool_name)

assert len(tool.__signature__.parameters) == 2
assert "argA" in tool.__signature__.parameters

tool = tool.bind_param("argA", lambda: 5)

assert len(tool.__signature__.parameters) == 1
assert "argA" not in tool.__signature__.parameters

res = await tool(True)
assert "argA" in res

@pytest.mark.asyncio
async def test_bind_param_fail(self, tool_name, client):
"""Tests 'bind_param' with a bound parameter that doesn't exist."""
tool = await client.load_tool(tool_name)

assert len(tool.__signature__.parameters) == 2
assert "argA" in tool.__signature__.parameters

with pytest.raises(Exception) as e:
tool.bind_param("argC", lambda: 5)
assert "unable to bind parameters: no parameter named argC" in str(e.value)

Expand Down
2 changes: 1 addition & 1 deletion packages/toolbox-core/tests/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ async def test_run_tool_param_auth_no_auth(self, toolbox: ToolboxClient):
"""Tests running a tool with a param requiring auth, without auth."""
tool = await toolbox.load_tool("get-row-by-email-auth")
with pytest.raises(
Exception,
ValueError,
match="One or more of the following authn services are required to invoke this tool: my-test-auth",
):
await tool()
Expand Down
2 changes: 1 addition & 1 deletion packages/toolbox-core/tests/test_sync_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def test_run_tool_param_auth_no_auth(self, toolbox: ToolboxSyncClient):
"""Tests running a tool with a param requiring auth, without auth."""
tool = toolbox.load_tool("get-row-by-email-auth")
with pytest.raises(
Exception,
ValueError,
match="One or more of the following authn services are required to invoke this tool: my-test-auth",
):
tool()
Expand Down