Skip to content

Commit bf97ed0

Browse files
committed
fix(tools): remove deprecated ToolLoader methods
Remove deprecated singular tool loading methods that were marked for removal in Strands SDK 2.0. These methods were deprecated in PR strands-agents#742 in favor of the plural variants that return lists. Removed methods: - ToolLoader.load_python_tool() - use load_python_tools() instead - ToolLoader.load_tool() - use load_tools() instead Also removed: - warnings import (no longer needed) - All tests specific to the deprecated methods The plural methods (load_python_tools and load_tools) remain and should be used instead as they properly handle files with multiple decorated tool functions. Fixes strands-agents#828
1 parent 2a26ffa commit bf97ed0

2 files changed

Lines changed: 0 additions & 280 deletions

File tree

src/strands/tools/loader.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import logging
55
import os
66
import sys
7-
import warnings
87
from pathlib import Path
98
from typing import List, cast
109

@@ -99,45 +98,6 @@ def load_python_tools(tool_path: str, tool_name: str) -> List[AgentTool]:
9998
logger.exception("tool_name=<%s>, sys_path=<%s> | failed to load python tool(s)", tool_name, sys.path)
10099
raise
101100

102-
@staticmethod
103-
def load_python_tool(tool_path: str, tool_name: str) -> AgentTool:
104-
"""DEPRECATED: Load a Python tool module and return a single AgentTool for backwards compatibility.
105-
106-
Use `load_python_tools` to retrieve all tools defined in a .py file (returns a list).
107-
This function will emit a `DeprecationWarning` and return the first discovered tool.
108-
"""
109-
warnings.warn(
110-
"ToolLoader.load_python_tool is deprecated and will be removed in Strands SDK 2.0. "
111-
"Use ToolLoader.load_python_tools(...) which always returns a list of AgentTool.",
112-
DeprecationWarning,
113-
stacklevel=2,
114-
)
115-
116-
tools = ToolLoader.load_python_tools(tool_path, tool_name)
117-
if not tools:
118-
raise RuntimeError(f"No tools found in {tool_path} for {tool_name}")
119-
return tools[0]
120-
121-
@classmethod
122-
def load_tool(cls, tool_path: str, tool_name: str) -> AgentTool:
123-
"""DEPRECATED: Load a single tool based on its file extension for backwards compatibility.
124-
125-
Use `load_tools` to retrieve all tools defined in a file (returns a list).
126-
This function will emit a `DeprecationWarning` and return the first discovered tool.
127-
"""
128-
warnings.warn(
129-
"ToolLoader.load_tool is deprecated and will be removed in Strands SDK 2.0. "
130-
"Use ToolLoader.load_tools(...) which always returns a list of AgentTool.",
131-
DeprecationWarning,
132-
stacklevel=2,
133-
)
134-
135-
tools = ToolLoader.load_tools(tool_path, tool_name)
136-
if not tools:
137-
raise RuntimeError(f"No tools found in {tool_path} for {tool_name}")
138-
139-
return tools[0]
140-
141101
@classmethod
142102
def load_tools(cls, tool_path: str, tool_name: str) -> list[AgentTool]:
143103
"""Load tools from a file based on its file extension.

tests/strands/tools/test_loader.py

Lines changed: 0 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -32,220 +32,6 @@ def tool_module(tool_path):
3232
return ".".join(os.path.splitext(tool_path)[0].split(os.sep)[-2:])
3333

3434

35-
@pytest.mark.parametrize(
36-
"tool_path",
37-
[
38-
textwrap.dedent("""
39-
import strands
40-
41-
@strands.tools.tool
42-
def identity(a: int):
43-
return a
44-
""")
45-
],
46-
indirect=True,
47-
)
48-
def test_load_python_tool_path_function_based(tool_path):
49-
tool = ToolLoader.load_python_tool(tool_path, "identity")
50-
51-
assert isinstance(tool, DecoratedFunctionTool)
52-
53-
54-
@pytest.mark.parametrize(
55-
"tool_path",
56-
[
57-
textwrap.dedent("""
58-
TOOL_SPEC = {
59-
"name": "identity",
60-
"description": "identity tool",
61-
"inputSchema": {
62-
"type": "object",
63-
"properties": {
64-
"a": {
65-
"type": "integer",
66-
},
67-
},
68-
},
69-
}
70-
71-
def identity(a: int):
72-
return a
73-
""")
74-
],
75-
indirect=True,
76-
)
77-
def test_load_python_tool_path_module_based(tool_path):
78-
tool = ToolLoader.load_python_tool(tool_path, "identity")
79-
80-
assert isinstance(tool, PythonAgentTool)
81-
82-
83-
def test_load_python_tool_path_invalid():
84-
with pytest.raises(ImportError, match="Could not create spec for identity"):
85-
ToolLoader.load_python_tool("invalid", "identity")
86-
87-
88-
@pytest.mark.parametrize(
89-
"tool_path",
90-
[
91-
textwrap.dedent("""
92-
def no_spec():
93-
return
94-
""")
95-
],
96-
indirect=True,
97-
)
98-
def test_load_python_tool_path_no_spec(tool_path):
99-
with pytest.raises(AttributeError, match="Tool no_spec missing TOOL_SPEC"):
100-
ToolLoader.load_python_tool(tool_path, "no_spec")
101-
102-
103-
@pytest.mark.parametrize(
104-
"tool_path",
105-
[
106-
textwrap.dedent("""
107-
TOOL_SPEC = {"name": "no_function"}
108-
""")
109-
],
110-
indirect=True,
111-
)
112-
def test_load_python_tool_path_no_function(tool_path):
113-
with pytest.raises(AttributeError, match="Tool no_function missing function"):
114-
ToolLoader.load_python_tool(tool_path, "no_function")
115-
116-
117-
@pytest.mark.parametrize(
118-
"tool_path",
119-
[
120-
textwrap.dedent("""
121-
TOOL_SPEC = {"name": "no_callable"}
122-
123-
no_callable = "not callable"
124-
""")
125-
],
126-
indirect=True,
127-
)
128-
def test_load_python_tool_path_no_callable(tool_path):
129-
with pytest.raises(TypeError, match="Tool no_callable function is not callable"):
130-
ToolLoader.load_python_tool(tool_path, "no_callable")
131-
132-
133-
@pytest.mark.parametrize(
134-
"tool_path",
135-
[
136-
textwrap.dedent("""
137-
import strands
138-
139-
@strands.tools.tool
140-
def identity(a: int):
141-
return a
142-
""")
143-
],
144-
indirect=True,
145-
)
146-
def test_load_python_tool_dot_function_based(tool_path, tool_module):
147-
_ = tool_path
148-
tool_module = f"{tool_module}:identity"
149-
150-
tool = ToolLoader.load_python_tool(tool_module, "identity")
151-
152-
assert isinstance(tool, DecoratedFunctionTool)
153-
154-
155-
@pytest.mark.parametrize(
156-
"tool_path",
157-
[
158-
textwrap.dedent("""
159-
TOOL_SPEC = {"name": "no_function"}
160-
""")
161-
],
162-
indirect=True,
163-
)
164-
def test_load_python_tool_dot_no_function(tool_path, tool_module):
165-
_ = tool_path
166-
167-
with pytest.raises(AttributeError, match=re.escape(f"Module {tool_module} has no function named no_function")):
168-
ToolLoader.load_python_tool(f"{tool_module}:no_function", "no_function")
169-
170-
171-
@pytest.mark.parametrize(
172-
"tool_path",
173-
[
174-
textwrap.dedent("""
175-
def no_decorator():
176-
return
177-
""")
178-
],
179-
indirect=True,
180-
)
181-
def test_load_python_tool_dot_no_decorator(tool_path, tool_module):
182-
_ = tool_path
183-
184-
with pytest.raises(ValueError, match=re.escape(f"Function no_decorator in {tool_module} is not a valid tool")):
185-
ToolLoader.load_python_tool(f"{tool_module}:no_decorator", "no_decorator")
186-
187-
188-
def test_load_python_tool_dot_missing():
189-
with pytest.raises(ImportError, match="Failed to import module missing"):
190-
ToolLoader.load_python_tool("missing:function", "function")
191-
192-
193-
@pytest.mark.parametrize(
194-
"tool_path",
195-
[
196-
textwrap.dedent("""
197-
import strands
198-
199-
@strands.tools.tool
200-
def identity(a: int):
201-
return a
202-
""")
203-
],
204-
indirect=True,
205-
)
206-
def test_load_tool(tool_path):
207-
tool = ToolLoader.load_tool(tool_path, "identity")
208-
209-
assert isinstance(tool, DecoratedFunctionTool)
210-
211-
212-
def test_load_tool_missing():
213-
with pytest.raises(FileNotFoundError, match="Tool file not found"):
214-
ToolLoader.load_tool("missing", "function")
215-
216-
217-
def test_load_tool_invalid_ext(tmp_path):
218-
tool_path = tmp_path / "tool.txt"
219-
tool_path.touch()
220-
221-
with pytest.raises(ValueError, match="Unsupported tool file type: .txt"):
222-
ToolLoader.load_tool(str(tool_path), "function")
223-
224-
225-
@pytest.mark.parametrize(
226-
"tool_path",
227-
[
228-
textwrap.dedent("""
229-
def no_spec():
230-
return
231-
""")
232-
],
233-
indirect=True,
234-
)
235-
def test_load_tool_no_spec(tool_path):
236-
with pytest.raises(AttributeError, match="Tool no_spec missing TOOL_SPEC"):
237-
ToolLoader.load_tool(tool_path, "no_spec")
238-
239-
with pytest.raises(AttributeError, match="Tool no_spec missing TOOL_SPEC"):
240-
ToolLoader.load_tools(tool_path, "no_spec")
241-
242-
with pytest.raises(AttributeError, match="Tool no_spec missing TOOL_SPEC"):
243-
ToolLoader.load_python_tool(tool_path, "no_spec")
244-
245-
with pytest.raises(AttributeError, match="Tool no_spec missing TOOL_SPEC"):
246-
ToolLoader.load_python_tools(tool_path, "no_spec")
247-
248-
24935
@pytest.mark.parametrize(
25036
"tool_path",
25137
[
@@ -284,29 +70,3 @@ def test_load_python_tool_path_multiple_function_based(tool_path):
28470
assert names == {"alpha", "bravo"}
28571

28672

287-
@pytest.mark.parametrize(
288-
"tool_path",
289-
[
290-
textwrap.dedent(
291-
"""
292-
import strands
293-
294-
@strands.tools.tool
295-
def alpha():
296-
return "alpha"
297-
298-
@strands.tools.tool
299-
def bravo():
300-
return "bravo"
301-
"""
302-
)
303-
],
304-
indirect=True,
305-
)
306-
def test_load_tool_path_returns_single_tool(tool_path):
307-
# loaded_python_tool and loaded_tool returns single item
308-
loaded_python_tool = ToolLoader.load_python_tool(tool_path, "alpha")
309-
loaded_tool = ToolLoader.load_tool(tool_path, "alpha")
310-
311-
assert loaded_python_tool.tool_name == "alpha"
312-
assert loaded_tool.tool_name == "alpha"

0 commit comments

Comments
 (0)