Skip to content

Commit cdfaaf8

Browse files
authored
fix(toolbox-core)!: Standardize on ValueError for improved error consistency (#219)
* chore: Add unit test coverage. * chore: Consolidate auth header creation logic Post adding the feature of adding client-level auth headers (#178), we have the logic for creating an auth header, from the given auth token getter name, in 3 different places. This PR unifies all of that logic into a single helper to improve maintenance, and make it easier to change the way we add suffix/prefix, and reduces WET code. * chore(toolbox-core): Standardize on ValueError for improved error consistency This PR updates instances where generic `Exception` objects were thrown in `toolbox-core` to use `ValueError` instead. This aligns with the prevalent use of `ValueError` for argument-related issues elsewhere in the package, leading to more specific, predictable, and robust error handling for consumers of `toolbox-core`. * chore: Delint * chore: Remove duplicate test
1 parent a10964f commit cdfaaf8

File tree

5 files changed

+11
-138
lines changed

5 files changed

+11
-138
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ async def load_tool(
176176
# parse the provided definition to a tool
177177
if name not in manifest.tools:
178178
# TODO: Better exception
179-
raise Exception(f"Tool '{name}' not found!")
179+
raise ValueError(f"Tool '{name}' not found!")
180180
tool, used_auth_keys, used_bound_keys = self.__parse_tool(
181181
name,
182182
manifest.tools[name],

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ async def __call__(self, *args: Any, **kwargs: Any) -> str:
212212
req_auth_services = set()
213213
for s in self.__required_authn_params.values():
214214
req_auth_services.update(s)
215-
raise Exception(
215+
raise ValueError(
216216
f"One or more of the following authn services are required to invoke this tool"
217217
f": {','.join(req_auth_services)}"
218218
)
@@ -329,7 +329,9 @@ def bind_params(
329329
)
330330

331331
if name not in param_names:
332-
raise Exception(f"unable to bind parameters: no parameter named {name}")
332+
raise ValueError(
333+
f"unable to bind parameters: no parameter named {name}"
334+
)
333335

334336
new_params = []
335337
for p in self.__params:

packages/toolbox-core/tests/test_client.py

Lines changed: 4 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ async def test_load_tool_not_found_in_manifest(aioresponses, test_tool_str):
266266
)
267267

268268
async with ToolboxClient(TEST_BASE_URL) as client:
269-
with pytest.raises(Exception, match=f"Tool '{REQUESTED_TOOL_NAME}' not found!"):
269+
with pytest.raises(
270+
ValueError, match=f"Tool '{REQUESTED_TOOL_NAME}' not found!"
271+
):
270272
await client.load_tool(REQUESTED_TOOL_NAME)
271273

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

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

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

608-
with pytest.raises(Exception) as e:
609-
tool.bind_param("argC", lambda: 5)
610-
assert "unable to bind parameters: no parameter named argC" in str(e.value)
611-
612-
@pytest.mark.asyncio
613-
async def test_rebind_param_fail(self, tool_name, client):
614-
"""
615-
Tests that 'bind_param' fails when attempting to re-bind a
616-
parameter that has already been bound.
617-
"""
618-
tool = await client.load_tool(tool_name)
619-
620-
assert len(tool.__signature__.parameters) == 2
621-
assert "argA" in tool.__signature__.parameters
622-
623-
tool_with_bound_param = tool.bind_param("argA", lambda: 10)
624-
625-
assert len(tool_with_bound_param.__signature__.parameters) == 1
626-
assert "argA" not in tool_with_bound_param.__signature__.parameters
627-
628610
with pytest.raises(ValueError) as e:
629-
tool_with_bound_param.bind_param("argA", lambda: 20)
630-
631-
assert "cannot re-bind parameter: parameter 'argA' is already bound" in str(
632-
e.value
633-
)
634-
635-
@pytest.mark.asyncio
636-
async def test_bind_param_static_value_success(self, tool_name, client):
637-
"""
638-
Tests bind_param method with a static value.
639-
"""
640-
641-
bound_value = "Test value"
642-
643-
tool = await client.load_tool(tool_name)
644-
bound_tool = tool.bind_param("argB", bound_value)
645-
646-
assert bound_tool is not tool
647-
assert "argB" not in bound_tool.__signature__.parameters
648-
assert "argA" in bound_tool.__signature__.parameters
649-
650-
passed_value_a = 42
651-
res_payload = await bound_tool(argA=passed_value_a)
652-
653-
assert res_payload == {"argA": passed_value_a, "argB": bound_value}
654-
655-
@pytest.mark.asyncio
656-
async def test_bind_param_sync_callable_value_success(self, tool_name, client):
657-
"""
658-
Tests bind_param method with a sync callable value.
659-
"""
660-
661-
bound_value_result = True
662-
bound_sync_callable = Mock(return_value=bound_value_result)
663-
664-
tool = await client.load_tool(tool_name)
665-
bound_tool = tool.bind_param("argB", bound_sync_callable)
666-
667-
assert bound_tool is not tool
668-
assert "argB" not in bound_tool.__signature__.parameters
669-
assert "argA" in bound_tool.__signature__.parameters
670-
671-
passed_value_a = 42
672-
res_payload = await bound_tool(argA=passed_value_a)
673-
674-
assert res_payload == {"argA": passed_value_a, "argB": bound_value_result}
675-
bound_sync_callable.assert_called_once()
676-
677-
@pytest.mark.asyncio
678-
async def test_bind_param_async_callable_value_success(self, tool_name, client):
679-
"""
680-
Tests bind_param method with an async callable value.
681-
"""
682-
683-
bound_value_result = True
684-
bound_async_callable = AsyncMock(return_value=bound_value_result)
685-
686-
tool = await client.load_tool(tool_name)
687-
bound_tool = tool.bind_param("argB", bound_async_callable)
688-
689-
assert bound_tool is not tool
690-
assert "argB" not in bound_tool.__signature__.parameters
691-
assert "argA" in bound_tool.__signature__.parameters
692-
693-
passed_value_a = 42
694-
res_payload = await bound_tool(argA=passed_value_a)
695-
696-
assert res_payload == {"argA": passed_value_a, "argB": bound_value_result}
697-
bound_async_callable.assert_awaited_once()
698-
699-
@pytest.mark.asyncio
700-
async def test_bind_param_success(self, tool_name, client):
701-
"""Tests 'bind_param' with a bound parameter specified."""
702-
tool = await client.load_tool(tool_name)
703-
704-
assert len(tool.__signature__.parameters) == 2
705-
assert "argA" in tool.__signature__.parameters
706-
707-
tool = tool.bind_param("argA", 5)
708-
709-
assert len(tool.__signature__.parameters) == 1
710-
assert "argA" not in tool.__signature__.parameters
711-
712-
res = await tool(True)
713-
assert "argA" in res
714-
715-
@pytest.mark.asyncio
716-
async def test_bind_callable_param_success(self, tool_name, client):
717-
"""Tests 'bind_param' with a bound parameter specified."""
718-
tool = await client.load_tool(tool_name)
719-
720-
assert len(tool.__signature__.parameters) == 2
721-
assert "argA" in tool.__signature__.parameters
722-
723-
tool = tool.bind_param("argA", lambda: 5)
724-
725-
assert len(tool.__signature__.parameters) == 1
726-
assert "argA" not in tool.__signature__.parameters
727-
728-
res = await tool(True)
729-
assert "argA" in res
730-
731-
@pytest.mark.asyncio
732-
async def test_bind_param_fail(self, tool_name, client):
733-
"""Tests 'bind_param' with a bound parameter that doesn't exist."""
734-
tool = await client.load_tool(tool_name)
735-
736-
assert len(tool.__signature__.parameters) == 2
737-
assert "argA" in tool.__signature__.parameters
738-
739-
with pytest.raises(Exception) as e:
740611
tool.bind_param("argC", lambda: 5)
741612
assert "unable to bind parameters: no parameter named argC" in str(e.value)
742613

packages/toolbox-core/tests/test_e2e.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ async def test_run_tool_param_auth_no_auth(self, toolbox: ToolboxClient):
188188
"""Tests running a tool with a param requiring auth, without auth."""
189189
tool = await toolbox.load_tool("get-row-by-email-auth")
190190
with pytest.raises(
191-
Exception,
191+
ValueError,
192192
match="One or more of the following authn services are required to invoke this tool: my-test-auth",
193193
):
194194
await tool()

packages/toolbox-core/tests/test_sync_e2e.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def test_run_tool_param_auth_no_auth(self, toolbox: ToolboxSyncClient):
156156
"""Tests running a tool with a param requiring auth, without auth."""
157157
tool = toolbox.load_tool("get-row-by-email-auth")
158158
with pytest.raises(
159-
Exception,
159+
ValueError,
160160
match="One or more of the following authn services are required to invoke this tool: my-test-auth",
161161
):
162162
tool()

0 commit comments

Comments
 (0)