From 6b9863f9c3e0710ca646e6ea76d14b6638521ea7 Mon Sep 17 00:00:00 2001 From: Mondo Diaz Date: Wed, 4 Feb 2026 12:18:44 -0600 Subject: [PATCH] fix: fetch root artifact from upstream when missing in auto_fetch mode When auto_fetch=true and the root artifact doesn't exist locally in a system project (_pypi), now attempts to fetch it from upstream before starting dependency resolution. Also fixed a bug where fetched_artifacts was being redeclared, which would lose the root artifact from the list. --- backend/app/dependencies.py | 82 ++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/backend/app/dependencies.py b/backend/app/dependencies.py index 2dad1bf..30463b9 100644 --- a/backend/app/dependencies.py +++ b/backend/app/dependencies.py @@ -886,6 +886,9 @@ async def resolve_dependencies_with_fetch( when a missing dependency is from a system project (e.g., _pypi), it attempts to fetch the package from the corresponding upstream registry. + If the root artifact itself doesn't exist in a system project, it will also + be fetched from upstream before resolution begins. + Args: db: Database session project_name: Project name @@ -900,43 +903,94 @@ async def resolve_dependencies_with_fetch( DependencyResolutionResponse with all resolved artifacts and fetch status Raises: - DependencyNotFoundError: If the root artifact cannot be found + DependencyNotFoundError: If the root artifact cannot be found (even after fetch attempt) CircularDependencyError: If circular dependencies are detected DependencyConflictError: If conflicting versions are required """ - # Resolve the initial artifact (same as sync version) + # Track fetched artifacts for response + fetched_artifacts: List[ResolvedArtifact] = [] + + # Check if project exists project = db.query(Project).filter(Project.name == project_name).first() + + # If project doesn't exist and it's a system project pattern, we can't auto-create it if not project: raise DependencyNotFoundError(project_name, package_name, ref) + # Check if package exists package = db.query(Package).filter( Package.project_id == project.id, Package.name == package_name, ).first() - if not package: - raise DependencyNotFoundError(project_name, package_name, ref) + + # Try to resolve the root artifact + root_artifact_id = None + root_version = None + root_size = None # Handle artifact: prefix for direct artifact ID references if ref.startswith("artifact:"): artifact_id = ref[9:] artifact = db.query(Artifact).filter(Artifact.id == artifact_id).first() - if not artifact: - raise DependencyNotFoundError(project_name, package_name, ref) - root_artifact_id = artifact.id - root_version = artifact_id[:12] - root_size = artifact.size - else: + if artifact: + root_artifact_id = artifact.id + root_version = artifact_id[:12] + root_size = artifact.size + elif package: + # Try to resolve by version/constraint resolved = _resolve_dependency_to_artifact( db, project_name, package_name, ref ) - if not resolved: - raise DependencyNotFoundError(project_name, package_name, ref) - root_artifact_id, root_version, root_size = resolved + if resolved: + root_artifact_id, root_version, root_size = resolved + + # If root artifact not found and this is a system project, try to fetch it + if root_artifact_id is None and project_name in SYSTEM_PROJECT_REGISTRY_MAP: + logger.info( + f"Root artifact {project_name}/{package_name}@{ref} not found, " + "attempting to fetch from upstream" + ) + + client = registry_clients.get(project_name) + if client: + try: + # Resolve the version constraint from upstream + version_info = await client.resolve_constraint(package_name, ref) + if version_info: + # Fetch and cache the package + fetch_result = await client.fetch_package( + package_name, version_info, db, storage + ) + if fetch_result: + logger.info( + f"Successfully fetched root artifact {package_name}==" + f"{fetch_result.version} (artifact {fetch_result.artifact_id[:12]})" + ) + root_artifact_id = fetch_result.artifact_id + root_version = fetch_result.version + root_size = fetch_result.size + + # Add to fetched list + fetched_artifacts.append(ResolvedArtifact( + artifact_id=fetch_result.artifact_id, + project=project_name, + package=package_name, + version=fetch_result.version, + size=fetch_result.size, + download_url=f"{base_url}/api/v1/project/{project_name}/{package_name}/+/{fetch_result.version}", + )) + except Exception as e: + logger.warning(f"Failed to fetch root artifact {package_name}: {e}") + + # If still no root artifact, raise error + if root_artifact_id is None: + raise DependencyNotFoundError(project_name, package_name, ref) # Track state resolved_artifacts: Dict[str, ResolvedArtifact] = {} missing_dependencies: List[MissingDependency] = [] - fetched_artifacts: List[ResolvedArtifact] = [] # Newly fetched + # Note: fetched_artifacts was already initialized above (line 911) + # and may already contain the root artifact if it was fetched from upstream version_requirements: Dict[str, List[Dict[str, Any]]] = {} visiting: Set[str] = set() visited: Set[str] = set()