Skip to content

Commit 49461e5

Browse files
authored
refactor: Remove unused code (#326)
1 parent 75dbbad commit 49461e5

File tree

4 files changed

+60
-212
lines changed

4 files changed

+60
-212
lines changed

src/strands/tools/loader.py

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
"""Tool loading utilities."""
22

33
import importlib
4-
import inspect
54
import logging
65
import os
76
import sys
87
from pathlib import Path
9-
from typing import Any, Dict, List, Optional, cast
8+
from typing import cast
109

1110
from ..types.tools import AgentTool
1211
from .decorator import DecoratedFunctionTool
@@ -15,88 +14,6 @@
1514
logger = logging.getLogger(__name__)
1615

1716

18-
def load_function_tool(func: Any) -> Optional[DecoratedFunctionTool]:
19-
"""Load a function as a tool if it's decorated with @tool.
20-
21-
Args:
22-
func: The function to load.
23-
24-
Returns:
25-
FunctionTool if successful, None otherwise.
26-
"""
27-
logger.warning(
28-
"issue=<%s> | load_function_tool will be removed in a future version",
29-
"https://github.com/strands-agents/sdk-python/pull/258",
30-
)
31-
32-
if isinstance(func, DecoratedFunctionTool):
33-
return func
34-
else:
35-
return None
36-
37-
38-
def scan_module_for_tools(module: Any) -> List[DecoratedFunctionTool]:
39-
"""Scan a module for function-based tools.
40-
41-
Args:
42-
module: The module to scan.
43-
44-
Returns:
45-
List of FunctionTool instances found in the module.
46-
"""
47-
tools = []
48-
49-
for name, obj in inspect.getmembers(module):
50-
if isinstance(obj, DecoratedFunctionTool):
51-
# Create a function tool with correct name
52-
try:
53-
tools.append(obj)
54-
except Exception as e:
55-
logger.warning("tool_name=<%s> | failed to create function tool | %s", name, e)
56-
57-
return tools
58-
59-
60-
def scan_directory_for_tools(directory: Path) -> Dict[str, DecoratedFunctionTool]:
61-
"""Scan a directory for Python modules containing function-based tools.
62-
63-
Args:
64-
directory: The directory to scan.
65-
66-
Returns:
67-
Dictionary mapping tool names to FunctionTool instances.
68-
"""
69-
tools: Dict[str, DecoratedFunctionTool] = {}
70-
71-
if not directory.exists() or not directory.is_dir():
72-
return tools
73-
74-
for file_path in directory.glob("*.py"):
75-
if file_path.name.startswith("_"):
76-
continue
77-
78-
try:
79-
# Dynamically import the module
80-
module_name = file_path.stem
81-
spec = importlib.util.spec_from_file_location(module_name, file_path)
82-
if not spec or not spec.loader:
83-
continue
84-
85-
module = importlib.util.module_from_spec(spec)
86-
spec.loader.exec_module(module)
87-
88-
# Find tools in the module
89-
for attr_name in dir(module):
90-
attr = getattr(module, attr_name)
91-
if isinstance(attr, DecoratedFunctionTool):
92-
tools[attr.tool_name] = attr
93-
94-
except Exception as e:
95-
logger.warning("tool_path=<%s> | failed to load tools under path | %s", file_path, e)
96-
97-
return tools
98-
99-
10017
class ToolLoader:
10118
"""Handles loading of tools from different sources."""
10219

src/strands/tools/registry.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515

1616
from typing_extensions import TypedDict, cast
1717

18+
from strands.tools.decorator import DecoratedFunctionTool
19+
1820
from ..types.tools import AgentTool, Tool, ToolChoice, ToolChoiceAuto, ToolConfig, ToolSpec
19-
from .loader import scan_module_for_tools
2021
from .tools import PythonAgentTool, normalize_schema, normalize_tool_spec
2122

2223
logger = logging.getLogger(__name__)
@@ -84,7 +85,7 @@ def process_tools(self, tools: List[Any]) -> List[str]:
8485
self.load_tool_from_filepath(tool_name=tool_name, tool_path=module_path)
8586
tool_names.append(tool_name)
8687
else:
87-
function_tools = scan_module_for_tools(tool)
88+
function_tools = self._scan_module_for_tools(tool)
8889
for function_tool in function_tools:
8990
self.register_tool(function_tool)
9091
tool_names.append(function_tool.tool_name)
@@ -313,7 +314,7 @@ def reload_tool(self, tool_name: str) -> None:
313314

314315
# Look for function-based tools first
315316
try:
316-
function_tools = scan_module_for_tools(module)
317+
function_tools = self._scan_module_for_tools(module)
317318

318319
if function_tools:
319320
for function_tool in function_tools:
@@ -400,7 +401,7 @@ def initialize_tools(self, load_tools_from_directory: bool = True) -> None:
400401
if tool_path.suffix == ".py":
401402
# Check for decorated function tools first
402403
try:
403-
function_tools = scan_module_for_tools(module)
404+
function_tools = self._scan_module_for_tools(module)
404405

405406
if function_tools:
406407
for function_tool in function_tools:
@@ -592,3 +593,25 @@ def _update_tool_config(self, tool_config: Dict[str, Any], new_tool: NewToolDict
592593
else:
593594
tool_config["tools"].append(new_tool_entry)
594595
logger.debug("tool_name=<%s> | added new tool", new_tool_name)
596+
597+
def _scan_module_for_tools(self, module: Any) -> List[AgentTool]:
598+
"""Scan a module for function-based tools.
599+
600+
Args:
601+
module: The module to scan.
602+
603+
Returns:
604+
List of FunctionTool instances found in the module.
605+
"""
606+
tools: List[AgentTool] = []
607+
608+
for name, obj in inspect.getmembers(module):
609+
if isinstance(obj, DecoratedFunctionTool):
610+
# Create a function tool with correct name
611+
try:
612+
# Cast as AgentTool for mypy
613+
tools.append(cast(AgentTool, obj))
614+
except Exception as e:
615+
logger.warning("tool_name=<%s> | failed to create function tool | %s", name, e)
616+
617+
return tools

tests/strands/tools/test_loader.py

Lines changed: 0 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,14 @@
11
import os
2-
import pathlib
32
import re
43
import textwrap
5-
import unittest.mock
64

75
import pytest
86

9-
import strands
107
from strands.tools.decorator import DecoratedFunctionTool
118
from strands.tools.loader import ToolLoader
129
from strands.tools.tools import PythonAgentTool
1310

1411

15-
def test_load_function_tool():
16-
@strands.tools.tool
17-
def tool_function(a):
18-
return a
19-
20-
tool = strands.tools.loader.load_function_tool(tool_function)
21-
22-
assert isinstance(tool, DecoratedFunctionTool)
23-
24-
25-
def test_load_function_tool_no_function():
26-
tool = strands.tools.loader.load_function_tool("no_function")
27-
28-
assert tool is None
29-
30-
31-
def test_load_function_tool_no_spec():
32-
def tool_function(a):
33-
return a
34-
35-
tool = strands.tools.loader.load_function_tool(tool_function)
36-
37-
assert tool is None
38-
39-
40-
def test_load_function_tool_invalid():
41-
def tool_function(a):
42-
return a
43-
44-
tool_function.TOOL_SPEC = "invalid"
45-
46-
tool = strands.tools.loader.load_function_tool(tool_function)
47-
48-
assert tool is None
49-
50-
51-
def test_scan_module_for_tools():
52-
@strands.tools.tool
53-
def tool_function_1(a):
54-
return a
55-
56-
@strands.tools.tool
57-
def tool_function_2(b):
58-
return b
59-
60-
def tool_function_3(c):
61-
return c
62-
63-
def tool_function_4(d):
64-
return d
65-
66-
tool_function_4.tool_spec = "invalid"
67-
68-
mock_module = unittest.mock.MagicMock()
69-
mock_module.tool_function_1 = tool_function_1
70-
mock_module.tool_function_2 = tool_function_2
71-
mock_module.tool_function_3 = tool_function_3
72-
mock_module.tool_function_4 = tool_function_4
73-
74-
tools = strands.tools.loader.scan_module_for_tools(mock_module)
75-
76-
assert len(tools) == 2
77-
assert all(isinstance(tool, DecoratedFunctionTool) for tool in tools)
78-
79-
80-
def test_scan_directory_for_tools(tmp_path):
81-
tool_definition_1 = textwrap.dedent("""
82-
import strands
83-
84-
@strands.tools.tool
85-
def tool_function_1(a):
86-
return a
87-
""")
88-
tool_definition_2 = textwrap.dedent("""
89-
import strands
90-
91-
@strands.tools.tool
92-
def tool_function_2(b):
93-
return b
94-
""")
95-
tool_definition_3 = textwrap.dedent("""
96-
def tool_function_3(c):
97-
return c
98-
""")
99-
tool_definition_4 = textwrap.dedent("""
100-
def tool_function_4(d):
101-
return d
102-
""")
103-
tool_definition_5 = ""
104-
tool_definition_6 = "**invalid**"
105-
106-
tool_path_1 = tmp_path / "tool_1.py"
107-
tool_path_2 = tmp_path / "tool_2.py"
108-
tool_path_3 = tmp_path / "tool_3.py"
109-
tool_path_4 = tmp_path / "tool_4.py"
110-
tool_path_5 = tmp_path / "_tool_5.py"
111-
tool_path_6 = tmp_path / "tool_6.py"
112-
113-
tool_path_1.write_text(tool_definition_1)
114-
tool_path_2.write_text(tool_definition_2)
115-
tool_path_3.write_text(tool_definition_3)
116-
tool_path_4.write_text(tool_definition_4)
117-
tool_path_5.write_text(tool_definition_5)
118-
tool_path_6.write_text(tool_definition_6)
119-
120-
tools = strands.tools.loader.scan_directory_for_tools(tmp_path)
121-
122-
tru_tool_names = sorted(tools.keys())
123-
exp_tool_names = ["tool_function_1", "tool_function_2"]
124-
125-
assert tru_tool_names == exp_tool_names
126-
assert all(isinstance(tool, DecoratedFunctionTool) for tool in tools.values())
127-
128-
129-
def test_scan_directory_for_tools_does_not_exist():
130-
tru_tools = strands.tools.loader.scan_directory_for_tools(pathlib.Path("does_not_exist"))
131-
exp_tools = {}
132-
133-
assert tru_tools == exp_tools
134-
135-
13612
@pytest.fixture
13713
def tool_path(request, tmp_path, monkeypatch):
13814
definition = request.param

tests/strands/tools/test_registry.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pytest
88

99
from strands.tools import PythonAgentTool
10+
from strands.tools.decorator import DecoratedFunctionTool, tool
1011
from strands.tools.registry import ToolRegistry
1112

1213

@@ -43,3 +44,34 @@ def test_register_tool_with_similar_name_raises():
4344
str(err.value) == "Tool name 'tool_like_this' already exists as 'tool-like-this'. "
4445
"Cannot add a duplicate tool which differs by a '-' or '_'"
4546
)
47+
48+
49+
def test_scan_module_for_tools():
50+
@tool
51+
def tool_function_1(a):
52+
return a
53+
54+
@tool
55+
def tool_function_2(b):
56+
return b
57+
58+
def tool_function_3(c):
59+
return c
60+
61+
def tool_function_4(d):
62+
return d
63+
64+
tool_function_4.tool_spec = "invalid"
65+
66+
mock_module = MagicMock()
67+
mock_module.tool_function_1 = tool_function_1
68+
mock_module.tool_function_2 = tool_function_2
69+
mock_module.tool_function_3 = tool_function_3
70+
mock_module.tool_function_4 = tool_function_4
71+
72+
tool_registry = ToolRegistry()
73+
74+
tools = tool_registry._scan_module_for_tools(mock_module)
75+
76+
assert len(tools) == 2
77+
assert all(isinstance(tool, DecoratedFunctionTool) for tool in tools)

0 commit comments

Comments
 (0)