CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pdm

A modern Python package and dependency manager supporting the latest PEP standards

Pending
Overview
Eval results
Files

dependency-resolution.mddocs/

Dependency Resolution

Advanced dependency resolution supporting both ResolveLib and UV backends with conflict resolution, constraint handling, and performance optimization. PDM's resolver system handles complex dependency graphs and provides multiple resolution strategies.

Capabilities

Base Resolver Interface

Abstract base class defining the resolver interface for dependency resolution systems.

from abc import ABC, abstractmethod
from typing import Dict, List, Set

class Resolver(ABC):
    """
    Abstract base class for dependency resolvers.
    
    Defines the interface for resolving package dependencies with
    conflict resolution and constraint satisfaction.
    """
    
    @abstractmethod
    def resolve(
        self,
        requirements: list[Requirement],
        groups: list[str] | None = None,
        prefer_pinned: bool = True
    ) -> dict[str, Candidate]:
        """
        Resolve dependencies to concrete package candidates.
        
        Args:
            requirements: List of requirement specifications to resolve
            groups: Dependency groups to include in resolution
            prefer_pinned: Prefer pinned versions from existing lockfile
            
        Returns:
            Dictionary mapping package names to resolved candidates
            
        Raises:
            ResolutionError: Dependency resolution failed
            ResolutionImpossible: No valid resolution exists
        """
    
    @abstractmethod
    def get_resolution_context(self) -> ResolutionContext:
        """
        Get current resolution context with state and constraints.
        
        Returns:
            Context object containing resolution state
        """
    
    def validate_resolution(
        self, 
        candidates: dict[str, Candidate]
    ) -> bool:
        """
        Validate that resolved candidates form a consistent set.
        
        Args:
            candidates: Resolved candidate mapping
            
        Returns:
            True if resolution is valid and consistent
        """

ResolveLib-based Resolver

Standard resolver implementation using the ResolveLib algorithm for robust dependency resolution.

class RLResolver(Resolver):
    """
    ResolveLib-based dependency resolver (default).
    
    Provides comprehensive dependency resolution using the ResolveLib
    algorithm with backtracking, conflict resolution, and constraint satisfaction.
    """
    
    def __init__(
        self,
        repository: BaseRepository,
        environment: BaseEnvironment,
        allow_prereleases: bool = False,
        strategy: str = "reuse-installed"
    ):
        """
        Initialize ResolveLib resolver.
        
        Args:
            repository: Package repository for candidate discovery
            environment: Target environment for resolution
            allow_prereleases: Include pre-release versions
            strategy: Resolution strategy ("reuse-installed", "eager", "conservative")
        """
    
    def resolve(
        self,
        requirements: list[Requirement],
        groups: list[str] | None = None,
        prefer_pinned: bool = True
    ) -> dict[str, Candidate]:
        """
        Resolve dependencies using ResolveLib algorithm.
        
        Performs comprehensive dependency resolution with backtracking
        and conflict resolution to find a consistent package set.
        """
    
    def get_resolution_context(self) -> ResolutionContext:
        """Get ResolveLib resolution context"""
    
    def set_allow_prereleases(self, allow: bool) -> None:
        """
        Set whether to allow pre-release versions.
        
        Args:
            allow: True to include pre-release versions
        """
    
    def set_resolution_strategy(self, strategy: str) -> None:
        """
        Set resolution strategy.
        
        Args:
            strategy: Strategy name ("reuse-installed", "eager", "conservative")
        """

UV-based Resolver

High-performance resolver implementation using UV for faster dependency resolution.

class UvResolver(Resolver):
    """
    UV-based dependency resolver for high performance.
    
    Provides faster dependency resolution using UV's optimized
    algorithms, particularly beneficial for large dependency sets.
    """
    
    def __init__(
        self,
        repository: BaseRepository,
        environment: BaseEnvironment,
        allow_prereleases: bool = False
    ):
        """
        Initialize UV resolver.
        
        Args:
            repository: Package repository for candidate discovery
            environment: Target environment for resolution
            allow_prereleases: Include pre-release versions
        """
    
    def resolve(
        self,
        requirements: list[Requirement],
        groups: list[str] | None = None,
        prefer_pinned: bool = True
    ) -> dict[str, Candidate]:
        """
        Resolve dependencies using UV algorithm.
        
        Performs fast dependency resolution optimized for performance
        while maintaining compatibility with standard resolution.
        """
    
    def get_resolution_context(self) -> ResolutionContext:
        """Get UV resolution context"""
    
    def is_available(self) -> bool:
        """
        Check if UV resolver is available.
        
        Returns:
            True if UV is installed and functional
        """

Resolution Context and State

Context management for resolution operations and state tracking.

@dataclass
class ResolutionContext:
    """
    Context for dependency resolution operations.
    
    Tracks resolution state, constraints, and intermediate results
    during the resolution process.
    """
    
    requirements: list[Requirement]
    candidates: dict[str, Candidate]
    constraints: dict[str, Requirement]
    overrides: dict[str, str]
    
    @property
    def resolved_count(self) -> int:
        """Number of resolved packages"""
    
    @property
    def constraint_count(self) -> int:
        """Number of active constraints"""
    
    def add_constraint(self, name: str, requirement: Requirement) -> None:
        """
        Add constraint for package resolution.
        
        Args:
            name: Package name
            requirement: Constraint requirement
        """
    
    def get_conflicts(self) -> list[tuple[str, list[Requirement]]]:
        """
        Get current resolution conflicts.
        
        Returns:
            List of conflicts with package names and conflicting requirements
        """

class ResolutionProvider:
    """
    Provider interface for resolution backend integration.
    
    Handles candidate discovery, metadata retrieval, and constraint
    evaluation for the resolution algorithm.
    """
    
    def find_candidates(
        self,
        identifier: str,
        requirement: Requirement
    ) -> list[Candidate]:
        """
        Find candidates matching requirement.
        
        Args:
            identifier: Package identifier
            requirement: Requirement specification
            
        Returns:
            List of matching candidates
        """
    
    def get_dependencies(self, candidate: Candidate) -> list[Requirement]:
        """
        Get dependencies for a candidate.
        
        Args:
            candidate: Package candidate
            
        Returns:
            List of dependency requirements
        """

Resolution Strategies

Different resolution strategies for handling version selection and conflict resolution.

class ResolutionStrategy:
    """Base class for resolution strategies"""
    
    def select_candidate(
        self,
        candidates: list[Candidate],
        requirement: Requirement
    ) -> Candidate:
        """
        Select best candidate from options.
        
        Args:
            candidates: Available candidates
            requirement: Requirement being resolved
            
        Returns:
            Selected candidate
        """

class EagerStrategy(ResolutionStrategy):
    """
    Eager resolution strategy selecting newest compatible versions.
    
    Prefers the latest versions that satisfy requirements,
    potentially leading to more frequent updates.
    """

class ConservativeStrategy(ResolutionStrategy):
    """
    Conservative resolution strategy preferring minimal changes.
    
    Minimizes version changes from existing installations,
    providing stability at the cost of potentially older versions.
    """

class ReuseInstalledStrategy(ResolutionStrategy):
    """
    Strategy that reuses installed packages when possible.
    
    Prefers already installed versions if they satisfy requirements,
    reducing installation time and maintaining stability.
    """

Resolution Graph

Dependency graph representation and analysis for resolution results.

class ResolutionGraph:
    """
    Dependency graph representation for resolved packages.
    
    Provides graph analysis capabilities including dependency
    traversal, conflict detection, and visualization.
    """
    
    def __init__(self, candidates: dict[str, Candidate]):
        """
        Initialize resolution graph.
        
        Args:
            candidates: Resolved candidate mapping
        """
    
    def get_dependencies(self, package: str) -> list[str]:
        """
        Get direct dependencies of a package.
        
        Args:
            package: Package name
            
        Returns:
            List of direct dependency package names
        """
    
    def get_dependents(self, package: str) -> list[str]:
        """
        Get packages that depend on the specified package.
        
        Args:
            package: Package name
            
        Returns:
            List of dependent package names
        """
    
    def get_install_order(self) -> list[str]:
        """
        Get topologically sorted installation order.
        
        Returns:
            List of package names in installation order
        """
    
    def detect_cycles(self) -> list[list[str]]:
        """
        Detect circular dependencies.
        
        Returns:
            List of dependency cycles (empty if no cycles)
        """
    
    def format_tree(self, package: str | None = None) -> str:
        """
        Format dependency tree as text.
        
        Args:
            package: Root package (default: all top-level packages)
            
        Returns:
            Formatted dependency tree string
        """

Usage Examples

Basic Dependency Resolution

from pdm.resolver import RLResolver, UvResolver
from pdm.models.requirements import Requirement
from pdm.models.repositories import PyPIRepository
from pdm.environments import PythonEnvironment

# Setup resolver components
repo = PyPIRepository()
env = PythonEnvironment("/usr/bin/python3.9")

# Create resolver (ResolveLib-based)
resolver = RLResolver(
    repository=repo,
    environment=env,
    allow_prereleases=False,
    strategy="reuse-installed"
)

# Define requirements
requirements = [
    Requirement.from_string("requests>=2.25.0"),
    Requirement.from_string("click>=8.0.0"),
    Requirement.from_string("rich")
]

# Resolve dependencies
try:
    candidates = resolver.resolve(requirements)
    print(f"Resolved {len(candidates)} packages:")
    for name, candidate in candidates.items():
        print(f"  {name} {candidate.version}")
except ResolutionError as e:
    print(f"Resolution failed: {e}")

UV Resolver for Performance

from pdm.resolver import UvResolver

# Try UV resolver for better performance
uv_resolver = UvResolver(
    repository=repo,
    environment=env,
    allow_prereleases=False
)

# Check if UV is available
if uv_resolver.is_available():
    print("Using UV resolver for faster resolution")
    candidates = uv_resolver.resolve(requirements)
else:
    print("UV not available, falling back to ResolveLib")
    candidates = resolver.resolve(requirements)

Resolution with Groups and Constraints

from pdm.resolver import RLResolver
from pdm.models.requirements import Requirement

resolver = RLResolver(repo, env)

# Requirements with dependency groups
requirements = [
    Requirement.from_string("django>=4.0"),
    Requirement.from_string("pytest>=6.0"),  # dev group
    Requirement.from_string("sphinx>=4.0")   # docs group
]

# Resolve specific groups
candidates = resolver.resolve(
    requirements=requirements,
    groups=["default", "dev"],  # exclude docs
    prefer_pinned=True
)

# Get resolution context for analysis
context = resolver.get_resolution_context()
print(f"Resolved {context.resolved_count} packages")
print(f"Active constraints: {context.constraint_count}")

# Check for conflicts
conflicts = context.get_conflicts()
if conflicts:
    print("Resolution conflicts found:")
    for name, conflicting_reqs in conflicts:
        print(f"  {name}: {conflicting_reqs}")

Resolution Graph Analysis

from pdm.resolver.graph import ResolutionGraph

# Create resolution graph from candidates
graph = ResolutionGraph(candidates)

# Analyze dependencies
print("Dependency analysis:")
for package in candidates:
    deps = graph.get_dependencies(package)
    dependents = graph.get_dependents(package)
    print(f"{package}:")
    print(f"  Dependencies: {deps}")
    print(f"  Dependents: {dependents}")

# Get installation order
install_order = graph.get_install_order()
print(f"Installation order: {install_order}")

# Check for circular dependencies
cycles = graph.detect_cycles()
if cycles:
    print("Circular dependencies detected:")
    for cycle in cycles:
        print(f"  {' -> '.join(cycle + [cycle[0]])}")

# Display dependency tree
tree = graph.format_tree()
print("Dependency tree:")
print(tree)

Custom Resolution Strategy

from pdm.resolver import RLResolver, ResolutionStrategy
from pdm.models.candidates import Candidate
from pdm.models.requirements import Requirement

class PreferStableStrategy(ResolutionStrategy):
    """Custom strategy preferring stable releases"""
    
    def select_candidate(
        self,
        candidates: list[Candidate],
        requirement: Requirement
    ) -> Candidate:
        # Filter out pre-releases first
        stable_candidates = [
            c for c in candidates 
            if not c.version.is_prerelease
        ]
        
        if stable_candidates:
            # Return newest stable version
            return max(stable_candidates, key=lambda c: c.version)
        else:
            # Fall back to newest if no stable versions
            return max(candidates, key=lambda c: c.version)

# Use custom strategy
resolver = RLResolver(repo, env)
resolver.strategy = PreferStableStrategy()

candidates = resolver.resolve(requirements)

Parallel Resolution

import asyncio
from pdm.resolver import RLResolver, UvResolver

async def resolve_parallel(
    requirements_list: list[list[Requirement]]
) -> list[dict[str, Candidate]]:
    """Resolve multiple requirement sets in parallel"""
    
    resolvers = [
        RLResolver(repo, env) for _ in requirements_list
    ]
    
    async def resolve_single(resolver, requirements):
        return resolver.resolve(requirements)
    
    # Resolve all requirement sets concurrently
    tasks = [
        resolve_single(resolver, reqs)
        for resolver, reqs in zip(resolvers, requirements_list)
    ]
    
    return await asyncio.gather(*tasks)

# Usage
requirement_sets = [
    [Requirement.from_string("django>=4.0")],
    [Requirement.from_string("flask>=2.0")],
    [Requirement.from_string("fastapi>=0.70")]
]

results = asyncio.run(resolve_parallel(requirement_sets))
for i, candidates in enumerate(results):
    print(f"Set {i+1}: {len(candidates)} packages resolved")

Install with Tessl CLI

npx tessl i tessl/pypi-pdm

docs

cli-framework.md

core-management.md

dependency-resolution.md

environment-management.md

index.md

installation-sync.md

project-management.md

tile.json