Add PEP 440 version constraint matching for dependency resolution
- Parse version constraints like >=1.9, <2.0 using packaging library - Find the latest version that satisfies the constraint - Support wildcard (*) to get latest version - Fall back to exact version and tag matching
This commit is contained in:
@@ -10,11 +10,20 @@ Handles:
|
||||
- Conflict detection
|
||||
"""
|
||||
|
||||
import re
|
||||
import yaml
|
||||
from typing import List, Dict, Any, Optional, Set, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
|
||||
# Import packaging for PEP 440 version matching
|
||||
try:
|
||||
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||
from packaging.version import Version, InvalidVersion
|
||||
HAS_PACKAGING = True
|
||||
except ImportError:
|
||||
HAS_PACKAGING = False
|
||||
|
||||
from .models import (
|
||||
Project,
|
||||
Package,
|
||||
@@ -304,6 +313,87 @@ def get_reverse_dependencies(
|
||||
)
|
||||
|
||||
|
||||
def _is_version_constraint(version_str: str) -> bool:
|
||||
"""Check if a version string contains constraint operators."""
|
||||
if not version_str:
|
||||
return False
|
||||
# Check for common constraint operators
|
||||
return any(op in version_str for op in ['>=', '<=', '!=', '~=', '>', '<', '==', '*'])
|
||||
|
||||
|
||||
def _resolve_version_constraint(
|
||||
db: Session,
|
||||
package: Package,
|
||||
constraint: str,
|
||||
) -> Optional[Tuple[str, str, int]]:
|
||||
"""
|
||||
Resolve a version constraint (e.g., '>=1.9') to a specific version.
|
||||
|
||||
Uses PEP 440 version matching to find the best matching version.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
package: Package to search versions in
|
||||
constraint: Version constraint string (e.g., '>=1.9', '<2.0,>=1.5')
|
||||
|
||||
Returns:
|
||||
Tuple of (artifact_id, resolved_version, size) or None if not found
|
||||
"""
|
||||
if not HAS_PACKAGING:
|
||||
# Fallback: if packaging not available, can't do constraint matching
|
||||
return None
|
||||
|
||||
# Handle wildcard - return latest version
|
||||
if constraint == '*':
|
||||
# Get the latest version by created_at
|
||||
latest = db.query(PackageVersion).filter(
|
||||
PackageVersion.package_id == package.id,
|
||||
).order_by(PackageVersion.created_at.desc()).first()
|
||||
if latest:
|
||||
artifact = db.query(Artifact).filter(Artifact.id == latest.artifact_id).first()
|
||||
if artifact:
|
||||
return (artifact.id, latest.version, artifact.size)
|
||||
return None
|
||||
|
||||
try:
|
||||
specifier = SpecifierSet(constraint)
|
||||
except InvalidSpecifier:
|
||||
# Invalid constraint, try as exact version
|
||||
return None
|
||||
|
||||
# Get all versions for this package
|
||||
all_versions = db.query(PackageVersion).filter(
|
||||
PackageVersion.package_id == package.id,
|
||||
).all()
|
||||
|
||||
if not all_versions:
|
||||
return None
|
||||
|
||||
# Find matching versions
|
||||
matching = []
|
||||
for pv in all_versions:
|
||||
try:
|
||||
v = Version(pv.version)
|
||||
if v in specifier:
|
||||
matching.append((pv, v))
|
||||
except InvalidVersion:
|
||||
# Skip invalid versions
|
||||
continue
|
||||
|
||||
if not matching:
|
||||
return None
|
||||
|
||||
# Sort by version (descending) and return the latest matching
|
||||
matching.sort(key=lambda x: x[1], reverse=True)
|
||||
best_match = matching[0][0]
|
||||
|
||||
artifact = db.query(Artifact).filter(Artifact.id == best_match.artifact_id).first()
|
||||
if artifact:
|
||||
return (artifact.id, best_match.version, artifact.size)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_dependency_to_artifact(
|
||||
db: Session,
|
||||
project_name: str,
|
||||
@@ -314,11 +404,17 @@ def _resolve_dependency_to_artifact(
|
||||
"""
|
||||
Resolve a dependency constraint to an artifact ID.
|
||||
|
||||
Supports:
|
||||
- Exact version matching (e.g., '1.2.3')
|
||||
- Version constraints (e.g., '>=1.9', '<2.0,>=1.5')
|
||||
- Tag matching
|
||||
- Wildcard ('*' for any version)
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
project_name: Project name
|
||||
package_name: Package name
|
||||
version: Version constraint (exact)
|
||||
version: Version or version constraint
|
||||
tag: Tag constraint
|
||||
|
||||
Returns:
|
||||
@@ -337,7 +433,13 @@ def _resolve_dependency_to_artifact(
|
||||
return None
|
||||
|
||||
if version:
|
||||
# Look up by version
|
||||
# Check if this is a version constraint (>=, <, etc.) or exact version
|
||||
if _is_version_constraint(version):
|
||||
result = _resolve_version_constraint(db, package, version)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
# Look up by exact version
|
||||
pkg_version = db.query(PackageVersion).filter(
|
||||
PackageVersion.package_id == package.id,
|
||||
PackageVersion.version == version,
|
||||
|
||||
Reference in New Issue
Block a user