From 36aeb27c07a2c00ee3f5c7f755690876b7cc8c12 Mon Sep 17 00:00:00 2001 From: Angela Date: Thu, 6 Feb 2025 16:31:19 -0800 Subject: [PATCH 1/3] add informative error messages for lazy imports --- pyproject.toml | 2 +- src/cleanlab_codex/codex_tool.py | 42 +++++++++++++++++++++++++----- src/cleanlab_codex/utils/errors.py | 15 +++++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/cleanlab_codex/utils/errors.py diff --git a/pyproject.toml b/pyproject.toml index cb2cba3..b0da1bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,4 +101,4 @@ html = "coverage html" xml = "coverage xml" [tool.ruff.lint] -ignore = ["FA100", "UP007", "UP006"] +ignore = ["FA100", "UP007", "UP006", "EM101"] diff --git a/src/cleanlab_codex/codex_tool.py b/src/cleanlab_codex/codex_tool.py index 8c78c1b..b2946d3 100644 --- a/src/cleanlab_codex/codex_tool.py +++ b/src/cleanlab_codex/codex_tool.py @@ -7,7 +7,11 @@ from typing_extensions import Annotated from cleanlab_codex.project import Project -from cleanlab_codex.utils.function import pydantic_model_from_function, required_properties_from_model +from cleanlab_codex.utils.function import ( + pydantic_model_from_function, + required_properties_from_model, +) +from cleanlab_codex.utils.errors import MissingDependencyError class CodexTool: @@ -25,9 +29,15 @@ def __init__( ): self._project = project self._fallback_answer = fallback_answer - self._tool_function_schema = pydantic_model_from_function(self._tool_name, self.query) - self._tool_properties = self._tool_function_schema.model_json_schema()["properties"] - self._tool_requirements = required_properties_from_model(self._tool_function_schema) + self._tool_function_schema = pydantic_model_from_function( + self._tool_name, self.query + ) + self._tool_properties = self._tool_function_schema.model_json_schema()[ + "properties" + ] + self._tool_requirements = required_properties_from_model( + self._tool_function_schema + ) @classmethod def from_access_key( @@ -119,7 +129,12 @@ def to_smolagents_tool(self) -> Any: Note: You must have the [`smolagents` library installed](https://github.com/huggingface/smolagents) to use this method. """ - from cleanlab_codex.utils.smolagents import CodexTool as SmolagentsCodexTool + try: + from cleanlab_codex.utils.smolagents import CodexTool as SmolagentsCodexTool + except ImportError as e: + raise MissingDependencyError( + "smolagents", "https://github.com/huggingface/smolagents" + ) from e return SmolagentsCodexTool( query=self.query, @@ -133,7 +148,14 @@ def to_llamaindex_tool(self) -> Any: Note: You must have the [`llama-index` library installed](https://docs.llamaindex.ai/en/stable/getting_started/installation/) to use this method. """ - from llama_index.core.tools import FunctionTool + try: + from llama_index.core.tools import FunctionTool + + except ImportError as e: + raise MissingDependencyError( + "llama-index-core", + "https://docs.llamaindex.ai/en/stable/getting_started/installation/", + ) from e return FunctionTool.from_defaults( fn=self.query, @@ -147,7 +169,13 @@ def to_langchain_tool(self) -> Any: Note: You must have the [`langchain` library installed](https://python.langchain.com/docs/concepts/architecture/) to use this method. """ - from langchain_core.tools.structured import StructuredTool + try: + from langchain_core.tools.structured import StructuredTool + + except ImportError as e: + raise MissingDependencyError( + "langchain", "https://pypi.org/project/langchain/" + ) from e return StructuredTool.from_function( func=self.query, diff --git a/src/cleanlab_codex/utils/errors.py b/src/cleanlab_codex/utils/errors.py new file mode 100644 index 0000000..ee6a45d --- /dev/null +++ b/src/cleanlab_codex/utils/errors.py @@ -0,0 +1,15 @@ +from __future__ import annotations + + +class MissingDependencyError(Exception): + """Raised when a lazy import is missing.""" + + def __init__(self, import_name: str, package_url: str | None = None) -> None: + self.import_name = import_name + self.package_url = package_url + + def __str__(self) -> str: + message = f"Failed to import {self.import_name}. Please install the package using `pip install {self.import_name}` and try again." + if self.package_url: + message += f" For more information, see {self.package_url}." + return message From 5fe65412d71776b3d7e5299cf1ddf2a7716198bf Mon Sep 17 00:00:00 2001 From: Angela Date: Thu, 6 Feb 2025 16:40:02 -0800 Subject: [PATCH 2/3] add info on lazy imports to dev guide --- DEVELOPMENT.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e26968a..298566f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -95,3 +95,16 @@ Automated releases are handled by the [release workflow][release-workflow] which Testing, type checking, and formatting/linting is [checked in CI][ci]. [ci]: .github/workflows/ci.yml + +## Style guide + +### Adding integrations with external libraries + +When adding integrations with external libraries, always use a lazy import. The external dependency should not be required to use the `cleanlab-codex` library. Wrap the lazy import in a `try`/`except` block to catch the `ImportError` and raise a `MissingDependencyError` with a helpful message. See [codex_tool.py](src/cleanlab_codex/codex_tool.py) file for examples one of which is shown below: + +```python +try: + from cleanlab_codex.utils.smolagents import CodexTool as SmolagentsCodexTool +except ImportError as e: + raise MissingDependencyError("smolagents", "https://github.com/huggingface/smolagents") from e +``` From 9d416606ca917ac26710264f802b8c0528b866e0 Mon Sep 17 00:00:00 2001 From: Angela Date: Thu, 6 Feb 2025 16:43:29 -0800 Subject: [PATCH 3/3] fmt --- src/cleanlab_codex/codex_tool.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/cleanlab_codex/codex_tool.py b/src/cleanlab_codex/codex_tool.py index b2946d3..e03051b 100644 --- a/src/cleanlab_codex/codex_tool.py +++ b/src/cleanlab_codex/codex_tool.py @@ -7,11 +7,11 @@ from typing_extensions import Annotated from cleanlab_codex.project import Project +from cleanlab_codex.utils.errors import MissingDependencyError from cleanlab_codex.utils.function import ( pydantic_model_from_function, required_properties_from_model, ) -from cleanlab_codex.utils.errors import MissingDependencyError class CodexTool: @@ -29,15 +29,9 @@ def __init__( ): self._project = project self._fallback_answer = fallback_answer - self._tool_function_schema = pydantic_model_from_function( - self._tool_name, self.query - ) - self._tool_properties = self._tool_function_schema.model_json_schema()[ - "properties" - ] - self._tool_requirements = required_properties_from_model( - self._tool_function_schema - ) + self._tool_function_schema = pydantic_model_from_function(self._tool_name, self.query) + self._tool_properties = self._tool_function_schema.model_json_schema()["properties"] + self._tool_requirements = required_properties_from_model(self._tool_function_schema) @classmethod def from_access_key( @@ -132,9 +126,7 @@ def to_smolagents_tool(self) -> Any: try: from cleanlab_codex.utils.smolagents import CodexTool as SmolagentsCodexTool except ImportError as e: - raise MissingDependencyError( - "smolagents", "https://github.com/huggingface/smolagents" - ) from e + raise MissingDependencyError("smolagents", "https://github.com/huggingface/smolagents") from e return SmolagentsCodexTool( query=self.query, @@ -173,9 +165,7 @@ def to_langchain_tool(self) -> Any: from langchain_core.tools.structured import StructuredTool except ImportError as e: - raise MissingDependencyError( - "langchain", "https://pypi.org/project/langchain/" - ) from e + raise MissingDependencyError("langchain", "https://pypi.org/project/langchain/") from e return StructuredTool.from_function( func=self.query,