fix: resolve include_extras parameter validation in resolve_dependencies

- Fix extra dependencies being filtered out by Python version checks
- Add proper handling for extra markers in dependency parsing
- Update parameter descriptions and documentation
- Add comprehensive examples and demo script
- Test with requests[socks], django[argon2,bcrypt], setuptools[test]
This commit is contained in:
Ryan Malloy 2025-08-15 11:53:54 -06:00
parent 146952f404
commit 114a7d8d5a
6 changed files with 498 additions and 7 deletions

View file

@ -83,24 +83,145 @@ class TestDependencyResolver:
"""Test dependency resolution with extra dependencies."""
mock_package_data = {
"info": {
"name": "test-package",
"name": "mock-test-package-12345",
"version": "1.0.0",
"requires_python": ">=3.8",
"requires_dist": ["requests>=2.25.0", "pytest>=6.0.0; extra=='test'"],
}
}
# Mock for transitive dependencies
mock_requests_data = {
"info": {
"name": "requests",
"version": "2.25.0",
"requires_python": ">=3.6",
"requires_dist": [],
}
}
mock_pytest_data = {
"info": {
"name": "pytest",
"version": "6.0.0",
"requires_python": ">=3.6",
"requires_dist": [],
}
}
with patch("pypi_query_mcp.core.PyPIClient") as mock_client_class:
mock_client = AsyncMock()
mock_client_class.return_value.__aenter__.return_value = mock_client
mock_client.get_package_info.return_value = mock_package_data
# Setup mock to return different data based on package name
def mock_get_package_info(package_name):
if package_name.lower() == "mock-test-package-12345":
return mock_package_data
elif package_name.lower() == "requests":
return mock_requests_data
elif package_name.lower() == "pytest":
return mock_pytest_data
else:
return {"info": {"name": package_name, "version": "1.0.0", "requires_dist": []}}
mock_client.get_package_info.side_effect = mock_get_package_info
result = await resolver.resolve_dependencies(
"test-package", include_extras=["test"]
"mock-test-package-12345", include_extras=["test"], max_depth=2
)
assert result["include_extras"] == ["test"]
assert "dependency_tree" in result
# Verify that extras are properly resolved and included
assert result["summary"]["total_extra_dependencies"] == 1
main_pkg = result["dependency_tree"]["mock-test-package-12345"]
assert "test" in main_pkg["dependencies"]["extras"]
assert len(main_pkg["dependencies"]["extras"]["test"]) == 1
assert "pytest" in main_pkg["dependencies"]["extras"]["test"][0]
@pytest.mark.asyncio
async def test_resolve_dependencies_with_extras_and_python_version(self, resolver):
"""Test that extras work correctly with Python version filtering."""
mock_package_data = {
"info": {
"name": "test-package",
"version": "1.0.0",
"requires_python": ">=3.8",
"requires_dist": [
"requests>=2.25.0",
"typing-extensions>=4.0.0; python_version<'3.10'",
"pytest>=6.0.0; extra=='test'",
"coverage>=5.0; extra=='test'",
],
}
}
# Mock for transitive dependencies
mock_requests_data = {
"info": {
"name": "requests",
"version": "2.25.0",
"requires_python": ">=3.6",
"requires_dist": [],
}
}
mock_pytest_data = {
"info": {
"name": "pytest",
"version": "6.0.0",
"requires_python": ">=3.6",
"requires_dist": [],
}
}
mock_coverage_data = {
"info": {
"name": "coverage",
"version": "5.0.0",
"requires_python": ">=3.6",
"requires_dist": [],
}
}
with patch("pypi_query_mcp.core.PyPIClient") as mock_client_class:
mock_client = AsyncMock()
mock_client_class.return_value.__aenter__.return_value = mock_client
# Setup mock to return different data based on package name
def mock_get_package_info(package_name):
if package_name.lower() == "test-package":
return mock_package_data
elif package_name.lower() == "requests":
return mock_requests_data
elif package_name.lower() == "pytest":
return mock_pytest_data
elif package_name.lower() == "coverage":
return mock_coverage_data
else:
return {"info": {"name": package_name, "version": "1.0.0", "requires_dist": []}}
mock_client.get_package_info.side_effect = mock_get_package_info
# Test with Python 3.11 - should not include typing-extensions but should include extras
result = await resolver.resolve_dependencies(
"test-package", python_version="3.11", include_extras=["test"], max_depth=2
)
assert result["include_extras"] == ["test"]
assert result["python_version"] == "3.11"
# Verify that extras are properly resolved
assert result["summary"]["total_extra_dependencies"] == 2
main_pkg = result["dependency_tree"]["test-package"]
assert "test" in main_pkg["dependencies"]["extras"]
assert len(main_pkg["dependencies"]["extras"]["test"]) == 2
# Verify Python version filtering worked for runtime deps but not extras
runtime_deps = main_pkg["dependencies"]["runtime"]
assert len(runtime_deps) == 1 # Only requests, not typing-extensions
assert "requests" in runtime_deps[0]
@pytest.mark.asyncio
async def test_resolve_dependencies_max_depth(self, resolver):