or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli-framework.mdcore-management.mddependency-resolution.mdenvironment-management.mdindex.mdinstallation-sync.mdproject-management.md

dependency-resolution.mddocs/

0

# Dependency Resolution

1

2

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.

3

4

## Capabilities

5

6

### Base Resolver Interface

7

8

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

9

10

```python { .api }

11

from abc import ABC, abstractmethod

12

from typing import Dict, List, Set

13

14

class Resolver(ABC):

15

"""

16

Abstract base class for dependency resolvers.

17

18

Defines the interface for resolving package dependencies with

19

conflict resolution and constraint satisfaction.

20

"""

21

22

@abstractmethod

23

def resolve(

24

self,

25

requirements: list[Requirement],

26

groups: list[str] | None = None,

27

prefer_pinned: bool = True

28

) -> dict[str, Candidate]:

29

"""

30

Resolve dependencies to concrete package candidates.

31

32

Args:

33

requirements: List of requirement specifications to resolve

34

groups: Dependency groups to include in resolution

35

prefer_pinned: Prefer pinned versions from existing lockfile

36

37

Returns:

38

Dictionary mapping package names to resolved candidates

39

40

Raises:

41

ResolutionError: Dependency resolution failed

42

ResolutionImpossible: No valid resolution exists

43

"""

44

45

@abstractmethod

46

def get_resolution_context(self) -> ResolutionContext:

47

"""

48

Get current resolution context with state and constraints.

49

50

Returns:

51

Context object containing resolution state

52

"""

53

54

def validate_resolution(

55

self,

56

candidates: dict[str, Candidate]

57

) -> bool:

58

"""

59

Validate that resolved candidates form a consistent set.

60

61

Args:

62

candidates: Resolved candidate mapping

63

64

Returns:

65

True if resolution is valid and consistent

66

"""

67

```

68

69

### ResolveLib-based Resolver

70

71

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

72

73

```python { .api }

74

class RLResolver(Resolver):

75

"""

76

ResolveLib-based dependency resolver (default).

77

78

Provides comprehensive dependency resolution using the ResolveLib

79

algorithm with backtracking, conflict resolution, and constraint satisfaction.

80

"""

81

82

def __init__(

83

self,

84

repository: BaseRepository,

85

environment: BaseEnvironment,

86

allow_prereleases: bool = False,

87

strategy: str = "reuse-installed"

88

):

89

"""

90

Initialize ResolveLib resolver.

91

92

Args:

93

repository: Package repository for candidate discovery

94

environment: Target environment for resolution

95

allow_prereleases: Include pre-release versions

96

strategy: Resolution strategy ("reuse-installed", "eager", "conservative")

97

"""

98

99

def resolve(

100

self,

101

requirements: list[Requirement],

102

groups: list[str] | None = None,

103

prefer_pinned: bool = True

104

) -> dict[str, Candidate]:

105

"""

106

Resolve dependencies using ResolveLib algorithm.

107

108

Performs comprehensive dependency resolution with backtracking

109

and conflict resolution to find a consistent package set.

110

"""

111

112

def get_resolution_context(self) -> ResolutionContext:

113

"""Get ResolveLib resolution context"""

114

115

def set_allow_prereleases(self, allow: bool) -> None:

116

"""

117

Set whether to allow pre-release versions.

118

119

Args:

120

allow: True to include pre-release versions

121

"""

122

123

def set_resolution_strategy(self, strategy: str) -> None:

124

"""

125

Set resolution strategy.

126

127

Args:

128

strategy: Strategy name ("reuse-installed", "eager", "conservative")

129

"""

130

```

131

132

### UV-based Resolver

133

134

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

135

136

```python { .api }

137

class UvResolver(Resolver):

138

"""

139

UV-based dependency resolver for high performance.

140

141

Provides faster dependency resolution using UV's optimized

142

algorithms, particularly beneficial for large dependency sets.

143

"""

144

145

def __init__(

146

self,

147

repository: BaseRepository,

148

environment: BaseEnvironment,

149

allow_prereleases: bool = False

150

):

151

"""

152

Initialize UV resolver.

153

154

Args:

155

repository: Package repository for candidate discovery

156

environment: Target environment for resolution

157

allow_prereleases: Include pre-release versions

158

"""

159

160

def resolve(

161

self,

162

requirements: list[Requirement],

163

groups: list[str] | None = None,

164

prefer_pinned: bool = True

165

) -> dict[str, Candidate]:

166

"""

167

Resolve dependencies using UV algorithm.

168

169

Performs fast dependency resolution optimized for performance

170

while maintaining compatibility with standard resolution.

171

"""

172

173

def get_resolution_context(self) -> ResolutionContext:

174

"""Get UV resolution context"""

175

176

def is_available(self) -> bool:

177

"""

178

Check if UV resolver is available.

179

180

Returns:

181

True if UV is installed and functional

182

"""

183

```

184

185

### Resolution Context and State

186

187

Context management for resolution operations and state tracking.

188

189

```python { .api }

190

@dataclass

191

class ResolutionContext:

192

"""

193

Context for dependency resolution operations.

194

195

Tracks resolution state, constraints, and intermediate results

196

during the resolution process.

197

"""

198

199

requirements: list[Requirement]

200

candidates: dict[str, Candidate]

201

constraints: dict[str, Requirement]

202

overrides: dict[str, str]

203

204

@property

205

def resolved_count(self) -> int:

206

"""Number of resolved packages"""

207

208

@property

209

def constraint_count(self) -> int:

210

"""Number of active constraints"""

211

212

def add_constraint(self, name: str, requirement: Requirement) -> None:

213

"""

214

Add constraint for package resolution.

215

216

Args:

217

name: Package name

218

requirement: Constraint requirement

219

"""

220

221

def get_conflicts(self) -> list[tuple[str, list[Requirement]]]:

222

"""

223

Get current resolution conflicts.

224

225

Returns:

226

List of conflicts with package names and conflicting requirements

227

"""

228

229

class ResolutionProvider:

230

"""

231

Provider interface for resolution backend integration.

232

233

Handles candidate discovery, metadata retrieval, and constraint

234

evaluation for the resolution algorithm.

235

"""

236

237

def find_candidates(

238

self,

239

identifier: str,

240

requirement: Requirement

241

) -> list[Candidate]:

242

"""

243

Find candidates matching requirement.

244

245

Args:

246

identifier: Package identifier

247

requirement: Requirement specification

248

249

Returns:

250

List of matching candidates

251

"""

252

253

def get_dependencies(self, candidate: Candidate) -> list[Requirement]:

254

"""

255

Get dependencies for a candidate.

256

257

Args:

258

candidate: Package candidate

259

260

Returns:

261

List of dependency requirements

262

"""

263

```

264

265

### Resolution Strategies

266

267

Different resolution strategies for handling version selection and conflict resolution.

268

269

```python { .api }

270

class ResolutionStrategy:

271

"""Base class for resolution strategies"""

272

273

def select_candidate(

274

self,

275

candidates: list[Candidate],

276

requirement: Requirement

277

) -> Candidate:

278

"""

279

Select best candidate from options.

280

281

Args:

282

candidates: Available candidates

283

requirement: Requirement being resolved

284

285

Returns:

286

Selected candidate

287

"""

288

289

class EagerStrategy(ResolutionStrategy):

290

"""

291

Eager resolution strategy selecting newest compatible versions.

292

293

Prefers the latest versions that satisfy requirements,

294

potentially leading to more frequent updates.

295

"""

296

297

class ConservativeStrategy(ResolutionStrategy):

298

"""

299

Conservative resolution strategy preferring minimal changes.

300

301

Minimizes version changes from existing installations,

302

providing stability at the cost of potentially older versions.

303

"""

304

305

class ReuseInstalledStrategy(ResolutionStrategy):

306

"""

307

Strategy that reuses installed packages when possible.

308

309

Prefers already installed versions if they satisfy requirements,

310

reducing installation time and maintaining stability.

311

"""

312

```

313

314

### Resolution Graph

315

316

Dependency graph representation and analysis for resolution results.

317

318

```python { .api }

319

class ResolutionGraph:

320

"""

321

Dependency graph representation for resolved packages.

322

323

Provides graph analysis capabilities including dependency

324

traversal, conflict detection, and visualization.

325

"""

326

327

def __init__(self, candidates: dict[str, Candidate]):

328

"""

329

Initialize resolution graph.

330

331

Args:

332

candidates: Resolved candidate mapping

333

"""

334

335

def get_dependencies(self, package: str) -> list[str]:

336

"""

337

Get direct dependencies of a package.

338

339

Args:

340

package: Package name

341

342

Returns:

343

List of direct dependency package names

344

"""

345

346

def get_dependents(self, package: str) -> list[str]:

347

"""

348

Get packages that depend on the specified package.

349

350

Args:

351

package: Package name

352

353

Returns:

354

List of dependent package names

355

"""

356

357

def get_install_order(self) -> list[str]:

358

"""

359

Get topologically sorted installation order.

360

361

Returns:

362

List of package names in installation order

363

"""

364

365

def detect_cycles(self) -> list[list[str]]:

366

"""

367

Detect circular dependencies.

368

369

Returns:

370

List of dependency cycles (empty if no cycles)

371

"""

372

373

def format_tree(self, package: str | None = None) -> str:

374

"""

375

Format dependency tree as text.

376

377

Args:

378

package: Root package (default: all top-level packages)

379

380

Returns:

381

Formatted dependency tree string

382

"""

383

```

384

385

### Usage Examples

386

387

#### Basic Dependency Resolution

388

389

```python

390

from pdm.resolver import RLResolver, UvResolver

391

from pdm.models.requirements import Requirement

392

from pdm.models.repositories import PyPIRepository

393

from pdm.environments import PythonEnvironment

394

395

# Setup resolver components

396

repo = PyPIRepository()

397

env = PythonEnvironment("/usr/bin/python3.9")

398

399

# Create resolver (ResolveLib-based)

400

resolver = RLResolver(

401

repository=repo,

402

environment=env,

403

allow_prereleases=False,

404

strategy="reuse-installed"

405

)

406

407

# Define requirements

408

requirements = [

409

Requirement.from_string("requests>=2.25.0"),

410

Requirement.from_string("click>=8.0.0"),

411

Requirement.from_string("rich")

412

]

413

414

# Resolve dependencies

415

try:

416

candidates = resolver.resolve(requirements)

417

print(f"Resolved {len(candidates)} packages:")

418

for name, candidate in candidates.items():

419

print(f" {name} {candidate.version}")

420

except ResolutionError as e:

421

print(f"Resolution failed: {e}")

422

```

423

424

#### UV Resolver for Performance

425

426

```python

427

from pdm.resolver import UvResolver

428

429

# Try UV resolver for better performance

430

uv_resolver = UvResolver(

431

repository=repo,

432

environment=env,

433

allow_prereleases=False

434

)

435

436

# Check if UV is available

437

if uv_resolver.is_available():

438

print("Using UV resolver for faster resolution")

439

candidates = uv_resolver.resolve(requirements)

440

else:

441

print("UV not available, falling back to ResolveLib")

442

candidates = resolver.resolve(requirements)

443

```

444

445

#### Resolution with Groups and Constraints

446

447

```python

448

from pdm.resolver import RLResolver

449

from pdm.models.requirements import Requirement

450

451

resolver = RLResolver(repo, env)

452

453

# Requirements with dependency groups

454

requirements = [

455

Requirement.from_string("django>=4.0"),

456

Requirement.from_string("pytest>=6.0"), # dev group

457

Requirement.from_string("sphinx>=4.0") # docs group

458

]

459

460

# Resolve specific groups

461

candidates = resolver.resolve(

462

requirements=requirements,

463

groups=["default", "dev"], # exclude docs

464

prefer_pinned=True

465

)

466

467

# Get resolution context for analysis

468

context = resolver.get_resolution_context()

469

print(f"Resolved {context.resolved_count} packages")

470

print(f"Active constraints: {context.constraint_count}")

471

472

# Check for conflicts

473

conflicts = context.get_conflicts()

474

if conflicts:

475

print("Resolution conflicts found:")

476

for name, conflicting_reqs in conflicts:

477

print(f" {name}: {conflicting_reqs}")

478

```

479

480

#### Resolution Graph Analysis

481

482

```python

483

from pdm.resolver.graph import ResolutionGraph

484

485

# Create resolution graph from candidates

486

graph = ResolutionGraph(candidates)

487

488

# Analyze dependencies

489

print("Dependency analysis:")

490

for package in candidates:

491

deps = graph.get_dependencies(package)

492

dependents = graph.get_dependents(package)

493

print(f"{package}:")

494

print(f" Dependencies: {deps}")

495

print(f" Dependents: {dependents}")

496

497

# Get installation order

498

install_order = graph.get_install_order()

499

print(f"Installation order: {install_order}")

500

501

# Check for circular dependencies

502

cycles = graph.detect_cycles()

503

if cycles:

504

print("Circular dependencies detected:")

505

for cycle in cycles:

506

print(f" {' -> '.join(cycle + [cycle[0]])}")

507

508

# Display dependency tree

509

tree = graph.format_tree()

510

print("Dependency tree:")

511

print(tree)

512

```

513

514

#### Custom Resolution Strategy

515

516

```python

517

from pdm.resolver import RLResolver, ResolutionStrategy

518

from pdm.models.candidates import Candidate

519

from pdm.models.requirements import Requirement

520

521

class PreferStableStrategy(ResolutionStrategy):

522

"""Custom strategy preferring stable releases"""

523

524

def select_candidate(

525

self,

526

candidates: list[Candidate],

527

requirement: Requirement

528

) -> Candidate:

529

# Filter out pre-releases first

530

stable_candidates = [

531

c for c in candidates

532

if not c.version.is_prerelease

533

]

534

535

if stable_candidates:

536

# Return newest stable version

537

return max(stable_candidates, key=lambda c: c.version)

538

else:

539

# Fall back to newest if no stable versions

540

return max(candidates, key=lambda c: c.version)

541

542

# Use custom strategy

543

resolver = RLResolver(repo, env)

544

resolver.strategy = PreferStableStrategy()

545

546

candidates = resolver.resolve(requirements)

547

```

548

549

#### Parallel Resolution

550

551

```python

552

import asyncio

553

from pdm.resolver import RLResolver, UvResolver

554

555

async def resolve_parallel(

556

requirements_list: list[list[Requirement]]

557

) -> list[dict[str, Candidate]]:

558

"""Resolve multiple requirement sets in parallel"""

559

560

resolvers = [

561

RLResolver(repo, env) for _ in requirements_list

562

]

563

564

async def resolve_single(resolver, requirements):

565

return resolver.resolve(requirements)

566

567

# Resolve all requirement sets concurrently

568

tasks = [

569

resolve_single(resolver, reqs)

570

for resolver, reqs in zip(resolvers, requirements_list)

571

]

572

573

return await asyncio.gather(*tasks)

574

575

# Usage

576

requirement_sets = [

577

[Requirement.from_string("django>=4.0")],

578

[Requirement.from_string("flask>=2.0")],

579

[Requirement.from_string("fastapi>=0.70")]

580

]

581

582

results = asyncio.run(resolve_parallel(requirement_sets))

583

for i, candidates in enumerate(results):

584

print(f"Set {i+1}: {len(candidates)} packages resolved")

585

```