feat: add pydantic-compatible import validation and deprecate old utilities

This commit is contained in:
Greyson LaLonde
2025-09-24 11:36:02 -04:00
committed by GitHub
parent a0b757a12c
commit 346faf229f
2 changed files with 220 additions and 8 deletions

View File

@@ -1,9 +1,16 @@
"""Tests for import utilities."""
import pytest
from unittest.mock import patch
import sys
from unittest.mock import MagicMock, patch
from crewai.utilities.import_utils import require, OptionalDependencyError
import pytest
from crewai.utilities.import_utils import (
OptionalDependencyError,
import_and_validate_definition,
require,
validate_import_path,
)
class TestRequire:
@@ -40,3 +47,143 @@ class TestRequire:
def test_optional_dependency_error_is_import_error(self):
"""Test that OptionalDependencyError is a subclass of ImportError."""
assert issubclass(OptionalDependencyError, ImportError)
def test_require_with_attr(self):
"""Test requiring a specific attribute from a module."""
loads = require("json", purpose="testing", attr="loads")
import json
assert loads == json.loads
def test_require_with_nonexistent_attr(self):
"""Test requiring a nonexistent attribute raises AttributeError."""
with pytest.raises(AttributeError) as exc_info:
require("json", purpose="testing", attr="nonexistent_attr")
assert "Module 'json' has no attribute 'nonexistent_attr'" in str(
exc_info.value
)
def test_require_extracts_package_name(self):
"""Test that require correctly extracts package name from module path."""
with pytest.raises(OptionalDependencyError) as exc_info:
require("some.nested.module.path", purpose="testing")
error_msg = str(exc_info.value)
assert "uv add some" in error_msg
class TestValidateImportPath:
"""Test the validate_import_path function."""
def test_validate_import_path_success(self):
"""Test successful import of a class."""
result = validate_import_path("json.JSONDecoder")
import json
assert result == json.JSONDecoder
def test_validate_import_path_malformed_no_module(self):
"""Test validation with no module path."""
with pytest.raises(ValueError) as exc_info:
validate_import_path("ClassName")
assert "import_path 'ClassName' must be of the form 'module.ClassName'" in str(
exc_info.value
)
def test_validate_import_path_empty_string(self):
"""Test validation with empty string."""
with pytest.raises(ValueError) as exc_info:
validate_import_path("")
assert "import_path '' must be of the form 'module.ClassName'" in str(
exc_info.value
)
def test_validate_import_path_module_not_found(self):
"""Test validation with non-existent module."""
with pytest.raises(ValueError) as exc_info:
validate_import_path("nonexistent_module.ClassName")
error_msg = str(exc_info.value)
assert "Package 'nonexistent_module' could not be imported" in error_msg
assert "uv add nonexistent_module" in error_msg
def test_validate_import_path_attribute_not_found(self):
"""Test validation when attribute doesn't exist in module."""
with pytest.raises(ValueError) as exc_info:
validate_import_path("json.NonExistentClass")
assert "Attribute 'NonExistentClass' not found in module 'json'" in str(
exc_info.value
)
def test_validate_import_path_nested_module(self):
"""Test validation with nested module path."""
result = validate_import_path("unittest.mock.MagicMock")
from unittest.mock import MagicMock
assert result == MagicMock
def test_validate_import_path_extracts_package_name(self):
"""Test that package name is correctly extracted for error message."""
with pytest.raises(ValueError) as exc_info:
validate_import_path("some.nested.module.path.ClassName")
error_msg = str(exc_info.value)
assert "Package 'some' could not be imported" in error_msg
assert "uv add some" in error_msg
class TestImportAndValidateDefinition:
"""Test the import_and_validate_definition function."""
def test_import_and_validate_definition_success(self):
"""Test successful import through Pydantic adapter."""
result = import_and_validate_definition("json.JSONEncoder")
import json
assert result == json.JSONEncoder
def test_import_and_validate_definition_with_function(self):
"""Test importing a function instead of a class."""
result = import_and_validate_definition("json.loads")
import json
assert result == json.loads
def test_import_and_validate_definition_invalid(self):
"""Test that invalid paths raise ValueError."""
with pytest.raises(ValueError) as exc_info:
import_and_validate_definition("InvalidPath")
assert "must be of the form 'module.ClassName'" in str(exc_info.value)
def test_import_and_validate_definition_module_error(self):
"""Test error handling for missing modules."""
with pytest.raises(ValueError) as exc_info:
import_and_validate_definition("missing_package.SomeClass")
error_msg = str(exc_info.value)
assert "Package 'missing_package' could not be imported" in error_msg
assert "uv add missing_package" in error_msg
def test_import_and_validate_definition_attribute_error(self):
"""Test error handling for missing attributes."""
with pytest.raises(ValueError) as exc_info:
import_and_validate_definition("json.MissingClass")
assert "Attribute 'MissingClass' not found in module 'json'" in str(
exc_info.value
)
def test_import_and_validate_definition_with_mock(self):
"""Test that mocked modules work correctly."""
mock_module = MagicMock()
mock_class = MagicMock()
mock_module.MockClass = mock_class
with patch.dict(sys.modules, {"mocked_module": mock_module}):
result = import_and_validate_definition("mocked_module.MockClass")
assert result == mock_class