resolvelib
Resolvelib is a Python library that provides a generic dependency resolution algorithm. It allows you to resolve abstract dependencies into concrete ones by implementing a custom 'Provider' interface that defines how packages, requirements, and candidates interact. The current version is 1.2.1, and it maintains an active release cadence with several updates per year, indicating ongoing development and support.
Warnings
- breaking Version 0.7.0 (April 2021) introduced breaking changes to the `Provider` interface. Users upgrading from versions prior to 0.7.0 will likely need to update their custom Provider implementations.
- breaking Version 1.0.0 (March 2023) changed the return type of `Resolver.resolve()`. It now returns a `namedtuple` with public attributes instead of an internal `Resolution` object. Code directly accessing internal attributes of the `Resolution` object will break.
- gotcha Complex dependency graphs can sometimes lead to `pip._vendor.resolvelib.resolvers.ResolutionTooDeep: 200000` errors or extremely long resolution times, especially when `resolvelib` is vendored within tools like `pip`. This can be due to intricate backtracking logic and optimization issues within the resolver.
- gotcha `resolvelib` is a low-level building block. It requires users to implement a custom `Provider` interface to define how it interacts with their specific package ecosystem (e.g., how to find candidates, determine dependencies, and evaluate satisfaction). It's not an out-of-the-box solution for a specific package manager.
Install
-
pip install resolvelib
Imports
- Resolver
from resolvelib import Resolver
- BaseReporter
from resolvelib.reporters import BaseReporter
- Provider (interface)
import resolvelib.providers
Quickstart
import resolvelib
from resolvelib.reporters import BaseReporter
import os
# Define simple data structures for our 'packages' and 'requirements'
# In a real scenario, these would be richer objects (e.g., Package(name, version), Requirement(package_name, specifier))
class MyPackage:
def __init__(self, name, version):
self.name = name
self.version = version
def __repr__(self):
return f'{self.name}=={self.version}'
def __hash__(self):
return hash((self.name, self.version))
def __eq__(self, other):
return isinstance(other, MyPackage) and self.name == other.name and self.version == other.version
class MyRequirement:
def __init__(self, name, specifier):
self.name = name
self.specifier = specifier # e.g., '>=1.0', '<2.0'
def __repr__(self):
return f'{self.name}{self.specifier}'
def __hash__(self):
return hash((self.name, self.specifier))
def __eq__(self, other):
return isinstance(other, MyRequirement) and self.name == other.name and self.specifier == other.specifier
# Implement the Provider interface
class MyProvider:
def get_base_requirement(self, identifier):
# For this simple example, we assume identifier is the package name
# and we don't have a 'base' requirement beyond the initial ones.
return None
def identify(self, requirement_or_candidate):
return requirement_or_candidate.name
def get_preference(self, identifier, resolutions, candidates, information):
# Prefer higher versions
return len(candidates) + 1 # Dummy preference, real logic would sort candidates
def get_dependencies(self, candidate):
# Define dependencies for candidates
if candidate.name == 'A' and candidate.version == '1.0':
return [MyRequirement('B', '>=1.0')]
if candidate.name == 'B' and candidate.version == '1.0':
return [MyRequirement('C', '>=1.0')]
return []
def get_candidates(self, requirement):
# Return available candidates for a given requirement
if requirement.name == 'A':
yield MyPackage('A', '1.0')
yield MyPackage('A', '2.0')
elif requirement.name == 'B':
yield MyPackage('B', '1.0')
yield MyPackage('B', '1.1')
elif requirement.name == 'C':
yield MyPackage('C', '1.0')
yield MyPackage('C', '1.2')
else:
return []
def is_satisfied_by(self, requirement, candidate):
# In a real scenario, this would check if candidate.version satisfies requirement.specifier
# For simplicity, we assume any candidate with the correct name satisfies a basic requirement
return requirement.name == candidate.name
# Create an instance of the provider and reporter
provider = MyProvider()
reporter = BaseReporter()
# Create the resolver
resolver = resolvelib.Resolver(provider, reporter)
# Define the initial requirements
requirements = [MyRequirement('A', '>=1.0')]
# Kick off the resolution process
try:
result = resolver.resolve(requirements)
print("Resolution successful:")
for candidate in result.graph.iter_network_linear():
if isinstance(candidate, MyPackage):
print(f" - {candidate}")
except resolvelib.ResolutionImpossible as e:
print(f"Resolution failed: {e}")
# Example of getting an auth key, though not directly used by resolvelib
api_key = os.environ.get('RESOLVELIB_API_KEY', 'your_default_or_mock_key')
if api_key == 'your_default_or_mock_key':
print("\nNote: For real-world use with external registries, an API key might be passed via Provider.")
else:
print(f"\nUsing API Key (first 5 chars): {api_key[:5]}...")