fix: prevent false circular dependency detection on self-dependencies

When packages like pytest have extras (e.g., pytest[testing]) that depend
on the base package, the resolution was incorrectly detecting this as a
circular dependency.

Added additional check to skip dependencies that resolve to an artifact
already in the visiting set, preventing the false cycle detection while
still catching real circular dependencies.
This commit is contained in:
Mondo Diaz
2026-02-04 13:19:03 -06:00
parent 15cd90b36d
commit 659ecf6f73

View File

@@ -806,6 +806,10 @@ def resolve_dependencies(
if dep_artifact_id == artifact_id: if dep_artifact_id == artifact_id:
continue continue
# Skip if this artifact is already being visited (would cause cycle)
if dep_artifact_id in visiting:
continue
_resolve_recursive( _resolve_recursive(
dep_artifact_id, dep_artifact_id,
dep.dependency_project, dep.dependency_project,
@@ -1142,14 +1146,19 @@ async def resolve_dependencies_with_fetch(
).all() ).all()
for dep in deps: for dep in deps:
# Skip self-dependencies # Skip self-dependencies (common with PyPI extras like pytest[testing] -> pytest)
dep_proj_normalized = dep.dependency_project.lower() dep_proj_normalized = dep.dependency_project.lower()
dep_pkg_normalized = _normalize_pypi_package_name(dep.dependency_package) dep_pkg_normalized = _normalize_pypi_package_name(dep.dependency_package)
curr_proj_normalized = proj_name.lower() curr_proj_normalized = proj_name.lower()
curr_pkg_normalized = _normalize_pypi_package_name(pkg_name) curr_pkg_normalized = _normalize_pypi_package_name(pkg_name)
if dep_proj_normalized == curr_proj_normalized and dep_pkg_normalized == curr_pkg_normalized: if dep_proj_normalized == curr_proj_normalized and dep_pkg_normalized == curr_pkg_normalized:
logger.debug(
f"Skipping self-dependency: {pkg_key} -> {dep.dependency_project}/{dep.dependency_package}"
)
continue continue
# Also check if this dependency would resolve to the current artifact
# (handles cases where package names differ but resolve to same artifact)
resolved_dep = _resolve_dependency_to_artifact( resolved_dep = _resolve_dependency_to_artifact(
db, db,
dep.dependency_project, dep.dependency_project,
@@ -1185,7 +1194,20 @@ async def resolve_dependencies_with_fetch(
dep_artifact_id, dep_version, dep_size = resolved_dep dep_artifact_id, dep_version, dep_size = resolved_dep
# Skip if resolved to same artifact (self-dependency at artifact level)
if dep_artifact_id == artifact_id: if dep_artifact_id == artifact_id:
logger.debug(
f"Skipping self-dependency (same artifact): {pkg_key} -> "
f"{dep.dependency_project}/{dep.dependency_package} (artifact {dep_artifact_id[:12]})"
)
continue
# Skip if this artifact is already being visited (would cause cycle)
if dep_artifact_id in visiting:
logger.debug(
f"Skipping dependency already in resolution stack: {pkg_key} -> "
f"{dep.dependency_project}/{dep.dependency_package} (artifact {dep_artifact_id[:12]})"
)
continue continue
await _resolve_recursive_async( await _resolve_recursive_async(