Skip to content

Commit 705b9d3

Browse files
authored
distinguish between import and package name in error (#28)
1 parent 31709e3 commit 705b9d3

File tree

3 files changed

+75
-6
lines changed

3 files changed

+75
-6
lines changed

src/cleanlab_codex/codex_tool.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def to_smolagents_tool(self) -> Any:
126126
try:
127127
from cleanlab_codex.utils.smolagents import CodexTool as SmolagentsCodexTool
128128
except ImportError as e:
129-
raise MissingDependencyError("smolagents", "https://github.com/huggingface/smolagents") from e
129+
raise MissingDependencyError(e.name or "smolagents", "https://github.com/huggingface/smolagents") from e
130130

131131
return SmolagentsCodexTool(
132132
query=self.query,
@@ -145,8 +145,9 @@ def to_llamaindex_tool(self) -> Any:
145145

146146
except ImportError as e:
147147
raise MissingDependencyError(
148-
"llama-index-core",
149-
"https://docs.llamaindex.ai/en/stable/getting_started/installation/",
148+
import_name=e.name or "llama_index",
149+
package_name="llama-index-core",
150+
package_url="https://docs.llamaindex.ai/en/stable/getting_started/installation/",
150151
) from e
151152

152153
return FunctionTool.from_defaults(
@@ -165,7 +166,11 @@ def to_langchain_tool(self) -> Any:
165166
from langchain_core.tools.structured import StructuredTool
166167

167168
except ImportError as e:
168-
raise MissingDependencyError("langchain", "https://pypi.org/project/langchain/") from e
169+
raise MissingDependencyError(
170+
import_name=e.name or "langchain",
171+
package_name="langchain",
172+
package_url="https://pypi.org/project/langchain/",
173+
) from e
169174

170175
return StructuredTool.from_function(
171176
func=self.query,

src/cleanlab_codex/utils/errors.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@
44
class MissingDependencyError(Exception):
55
"""Raised when a lazy import is missing."""
66

7-
def __init__(self, import_name: str, package_url: str | None = None) -> None:
7+
def __init__(self, import_name: str, package_name: str | None = None, package_url: str | None = None) -> None:
8+
"""
9+
Args:
10+
import_name: The name of the import that failed.
11+
package_name: The name of the package to install.
12+
package_url: The URL for more information about the package.
13+
"""
814
self.import_name = import_name
15+
self.package_name = package_name
916
self.package_url = package_url
1017

1118
def __str__(self) -> str:
12-
message = f"Failed to import {self.import_name}. Please install the package using `pip install {self.import_name}` and try again."
19+
message = f"Failed to import {self.import_name}. Please install the package using `pip install {self.package_name or self.import_name}` and try again."
1320
if self.package_url:
1421
message += f" For more information, see {self.package_url}."
1522
return message

tests/test_codex_tool.py

+57
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1+
import builtins
2+
import importlib
13
import sys
4+
from typing import Any
25
from unittest.mock import MagicMock, patch
36

47
import pytest
58
from langchain_core.tools.structured import StructuredTool
69
from llama_index.core.tools import FunctionTool
710

811
from cleanlab_codex.codex_tool import CodexTool
12+
from cleanlab_codex.utils.errors import MissingDependencyError
13+
14+
15+
def patch_import_with_import_error(missing_module: str) -> None:
16+
def custom_import(name: str, *args: Any, **kwargs: Any) -> Any:
17+
if name.startswith(missing_module):
18+
raise ImportError("test", name=missing_module)
19+
return importlib.__import__(name, *args, **kwargs)
20+
21+
builtins.__import__ = custom_import
922

1023

1124
def test_to_openai_tool(mock_client_from_access_key: MagicMock) -> None:
@@ -32,6 +45,20 @@ def test_to_llamaindex_tool(mock_client_from_access_key: MagicMock) -> None:
3245
assert llama_index_tool.fn == tool.query
3346

3447

48+
def test_to_llamaindex_tool_import_error(
49+
mock_client_from_access_key: MagicMock,
50+
) -> None:
51+
with patch("cleanlab_codex.codex_tool.Project") as mock_project:
52+
mock_project.from_access_key.return_value = MagicMock(client=mock_client_from_access_key, id="test_project_id")
53+
54+
tool = CodexTool.from_access_key("sk-test-123")
55+
patch_import_with_import_error("llama_index")
56+
with pytest.raises(MissingDependencyError) as exc_info:
57+
tool.to_llamaindex_tool()
58+
59+
assert exc_info.value.import_name == "llama_index"
60+
61+
3562
def test_to_langchain_tool(mock_client_from_access_key: MagicMock) -> None:
3663
with patch("cleanlab_codex.codex_tool.Project") as mock_project:
3764
mock_project.from_access_key.return_value = MagicMock(client=mock_client_from_access_key, id="test_project_id")
@@ -50,6 +77,18 @@ def test_to_langchain_tool(mock_client_from_access_key: MagicMock) -> None:
5077
), f"Expected description '{tool.tool_description}', got '{langchain_tool.description}'."
5178

5279

80+
def test_to_langchain_tool_import_error(mock_client_from_access_key: MagicMock) -> None:
81+
with patch("cleanlab_codex.codex_tool.Project") as mock_project:
82+
mock_project.from_access_key.return_value = MagicMock(client=mock_client_from_access_key, id="test_project_id")
83+
84+
tool = CodexTool.from_access_key("sk-test-123")
85+
patch_import_with_import_error("langchain")
86+
with pytest.raises(MissingDependencyError) as exc_info:
87+
tool.to_langchain_tool()
88+
89+
assert exc_info.value.import_name == "langchain"
90+
91+
5392
def test_to_aws_converse_tool(mock_client_from_access_key: MagicMock) -> None:
5493
with patch("cleanlab_codex.codex_tool.Project") as mock_project:
5594
mock_project.from_access_key.return_value = MagicMock(client=mock_client_from_access_key, id="test_project_id")
@@ -95,3 +134,21 @@ def test_to_smolagents_tool(mock_client_from_access_key: MagicMock) -> None:
95134
assert isinstance(smolagents_tool, Tool)
96135
assert smolagents_tool.name == tool.tool_name
97136
assert smolagents_tool.description == tool.tool_description
137+
138+
139+
def test_to_smolagents_tool_import_error(
140+
mock_client_from_access_key: MagicMock,
141+
) -> None:
142+
with patch("cleanlab_codex.codex_tool.Project") as mock_project:
143+
mock_project.from_access_key.return_value = MagicMock(client=mock_client_from_access_key, id="test_project_id")
144+
145+
tool = CodexTool.from_access_key("sk-test-123")
146+
import_module_name = "smolagents"
147+
if sys.version_info >= (3, 10):
148+
import_module_name = "cleanlab_codex.utils.smolagents"
149+
patch_import_with_import_error(import_module_name)
150+
151+
with pytest.raises(MissingDependencyError) as exc_info:
152+
tool.to_smolagents_tool()
153+
154+
assert exc_info.value.import_name == import_module_name

0 commit comments

Comments
 (0)