diff --git a/lib/crewai-tools/src/crewai_tools/__init__.py b/lib/crewai-tools/src/crewai_tools/__init__.py index 38ef6f92c..4fffbca64 100644 --- a/lib/crewai-tools/src/crewai_tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/__init__.py @@ -288,3 +288,28 @@ __all__ = [ ] __version__ = "1.1.0" + + +def __getattr__(name: str): + """ + Catch common typos and provide helpful error messages. + + This function is called when an attribute is not found in the module. + It helps users who make common typos when importing tools. + """ + if name == "PGSearchTool": + raise NotImplementedError( + f"'{name}' is currently under development and not yet available. " + f"Please check the CrewAI documentation for updates on when this tool will be released." + ) + + if name.endswith("tool") and not name.endswith("Tool"): + correct_name = name[:-4] + "Tool" + if correct_name in __all__ or correct_name == "PGSearchTool": + raise ImportError( + f"Cannot import name '{name}' from 'crewai_tools'. " + f"Did you mean '{correct_name}'? " + f"Note: Tool names use capital 'T' in 'Tool'." + ) + + raise AttributeError(f"module 'crewai_tools' has no attribute '{name}'") diff --git a/lib/crewai-tools/tests/test_import_errors.py b/lib/crewai-tools/tests/test_import_errors.py new file mode 100644 index 000000000..b166a9de2 --- /dev/null +++ b/lib/crewai-tools/tests/test_import_errors.py @@ -0,0 +1,54 @@ +import pytest +import crewai_tools + + +def test_typo_in_tool_name_lowercase_t(): + """Test that accessing a tool with lowercase 't' in 'tool' provides a helpful error message.""" + with pytest.raises(ImportError) as exc_info: + getattr(crewai_tools, "CSVSearchtool") + + assert "Cannot import name 'CSVSearchtool' from 'crewai_tools'" in str(exc_info.value) + assert "Did you mean 'CSVSearchTool'?" in str(exc_info.value) + assert "Tool names use capital 'T' in 'Tool'" in str(exc_info.value) + + +def test_typo_pgsearchtool_lowercase_t(): + """Test that accessing PGSearchtool (with lowercase 't') provides a helpful error message.""" + with pytest.raises(ImportError) as exc_info: + getattr(crewai_tools, "PGSearchtool") + + assert "Cannot import name 'PGSearchtool' from 'crewai_tools'" in str(exc_info.value) + assert "Did you mean 'PGSearchTool'?" in str(exc_info.value) + + +def test_pgsearchtool_not_implemented(): + """Test that accessing PGSearchTool (correct spelling) shows it's not yet implemented.""" + with pytest.raises(NotImplementedError) as exc_info: + getattr(crewai_tools, "PGSearchTool") + + assert "'PGSearchTool' is currently under development" in str(exc_info.value) + assert "not yet available" in str(exc_info.value) + + +def test_nonexistent_tool(): + """Test that accessing a completely nonexistent tool gives a standard AttributeError.""" + with pytest.raises(AttributeError) as exc_info: + getattr(crewai_tools, "NonExistentTool") + + assert "module 'crewai_tools' has no attribute 'NonExistentTool'" in str(exc_info.value) + + +def test_multiple_typos(): + """Test multiple common typos to ensure they all get helpful messages.""" + typos = [ + ("FileReadtool", "FileReadTool"), + ("PDFSearchtool", "PDFSearchTool"), + ("MySQLSearchtool", "MySQLSearchTool"), + ] + + for typo, correct in typos: + with pytest.raises(ImportError) as exc_info: + getattr(crewai_tools, typo) + + assert f"Cannot import name '{typo}' from 'crewai_tools'" in str(exc_info.value) + assert f"Did you mean '{correct}'?" in str(exc_info.value)