0
# Installation & Synchronization
1
2
Package installation management with support for wheels, source distributions, editable installs, and environment synchronization. This system handles the actual package installation process and keeps environments in sync with project specifications.
3
4
## Capabilities
5
6
### Installation Manager
7
8
Central coordinator for package installation operations with support for multiple installation backends and strategies.
9
10
```python { .api }
11
class InstallManager:
12
"""
13
Main installation coordinator managing package installation,
14
uninstallation, and environment synchronization.
15
"""
16
17
def __init__(self, environment: BaseEnvironment, use_install_cache: bool = True):
18
"""
19
Initialize installation manager.
20
21
Args:
22
environment: Target environment for installation
23
use_install_cache: Enable installation caching
24
"""
25
26
def install(self, candidates: list[Candidate]) -> None:
27
"""
28
Install packages from candidate list.
29
30
Args:
31
candidates: List of resolved package candidates to install
32
33
Raises:
34
InstallationError: Installation failures
35
"""
36
37
def uninstall(self, packages: list[str]) -> None:
38
"""
39
Uninstall packages by name.
40
41
Args:
42
packages: List of package names to uninstall
43
44
Raises:
45
UninstallError: Uninstallation failures
46
"""
47
48
def reinstall(self, packages: list[str]) -> None:
49
"""
50
Reinstall packages (uninstall then install).
51
52
Args:
53
packages: List of package names to reinstall
54
"""
55
56
def get_installation_plan(
57
self,
58
candidates: list[Candidate]
59
) -> InstallationPlan:
60
"""
61
Generate installation plan without executing.
62
63
Args:
64
candidates: Package candidates to analyze
65
66
Returns:
67
Installation plan with operations and dependencies
68
"""
69
```
70
71
### Synchronization Systems
72
73
Environment synchronization ensuring installed packages exactly match project specifications.
74
75
```python { .api }
76
class BaseSynchronizer(ABC):
77
"""
78
Abstract base class for environment synchronizers.
79
80
Synchronizers ensure the environment matches the lockfile
81
by installing missing packages and removing extras.
82
"""
83
84
@abstractmethod
85
def synchronize(
86
self,
87
candidates: dict[str, Candidate],
88
tracked_names: set[str] = None,
89
retry_times: int = 1
90
) -> None:
91
"""
92
Synchronize environment with candidate specifications.
93
94
Args:
95
candidates: Target package candidates
96
tracked_names: Names of packages to track
97
retry_times: Number of retry attempts
98
"""
99
100
class Synchronizer(BaseSynchronizer):
101
"""
102
Standard synchronizer using pip for package operations.
103
104
Provides reliable installation with comprehensive error handling
105
and support for all standard Python package formats.
106
"""
107
108
def __init__(
109
self,
110
candidate: Candidate,
111
environment: BaseEnvironment,
112
clean: bool = False,
113
dry_run: bool = False
114
):
115
"""
116
Initialize synchronizer.
117
118
Args:
119
candidate: Package candidate for synchronization
120
environment: Target environment
121
clean: Remove packages not in candidate list
122
dry_run: Show operations without executing
123
"""
124
125
def synchronize(
126
self,
127
candidates: dict[str, Candidate],
128
tracked_names: set[str] = None,
129
retry_times: int = 1
130
) -> None:
131
"""Synchronize using pip-based installation"""
132
133
class UvSynchronizer(BaseSynchronizer):
134
"""
135
High-performance synchronizer using UV for package operations.
136
137
Provides faster installation and resolution with improved
138
performance for large dependency sets.
139
"""
140
141
def synchronize(
142
self,
143
candidates: dict[str, Candidate],
144
tracked_names: set[str] = None,
145
retry_times: int = 1
146
) -> None:
147
"""Synchronize using UV-based installation"""
148
```
149
150
### Installation Plans
151
152
Planning and preview functionality for installation operations.
153
154
```python { .api }
155
@dataclass
156
class InstallationPlan:
157
"""
158
Installation plan containing all operations to be performed.
159
160
Provides preview of installation operations including downloads,
161
builds, installations, and uninstallations.
162
"""
163
164
to_install: list[Candidate]
165
to_uninstall: list[str]
166
to_update: list[tuple[str, Candidate]]
167
168
@property
169
def total_size(self) -> int:
170
"""Total download size in bytes"""
171
172
@property
173
def operation_count(self) -> int:
174
"""Total number of operations"""
175
176
def format_summary(self) -> str:
177
"""
178
Format human-readable installation summary.
179
180
Returns:
181
Formatted summary of planned operations
182
"""
183
```
184
185
### Package Candidate Management
186
187
Package candidate representation and metadata handling for installation operations.
188
189
```python { .api }
190
class Candidate:
191
"""
192
Represents a specific package version candidate for installation.
193
194
Contains all metadata and information needed for package installation
195
including source locations, dependencies, and build requirements.
196
"""
197
198
@property
199
def name(self) -> str:
200
"""Package name"""
201
202
@property
203
def version(self) -> str:
204
"""Package version"""
205
206
@property
207
def source_type(self) -> str:
208
"""Source type (wheel, sdist, vcs, etc.)"""
209
210
@property
211
def dependencies(self) -> list[Requirement]:
212
"""Package dependencies"""
213
214
@property
215
def requires_python(self) -> str | None:
216
"""Python version requirement"""
217
218
def get_metadata(self) -> dict:
219
"""
220
Get package metadata.
221
222
Returns:
223
Dictionary containing package metadata
224
"""
225
226
def prepare(self, environment: BaseEnvironment) -> PreparedCandidate:
227
"""
228
Prepare candidate for installation.
229
230
Args:
231
environment: Target environment
232
233
Returns:
234
Prepared candidate ready for installation
235
"""
236
237
class PreparedCandidate:
238
"""
239
Candidate prepared for installation with resolved metadata and build artifacts.
240
"""
241
242
@property
243
def wheel_path(self) -> Path | None:
244
"""Path to wheel file if available"""
245
246
@property
247
def metadata_dir(self) -> Path:
248
"""Path to extracted metadata directory"""
249
250
def install(self, installer: InstallManager) -> None:
251
"""Install this prepared candidate"""
252
```
253
254
### Installation Strategies
255
256
Different installation strategies for various package types and requirements.
257
258
```python { .api }
259
class InstallationStrategy(ABC):
260
"""Base class for installation strategies"""
261
262
@abstractmethod
263
def install_candidate(
264
self,
265
candidate: PreparedCandidate,
266
environment: BaseEnvironment
267
) -> None:
268
"""Install prepared candidate"""
269
270
class WheelInstaller(InstallationStrategy):
271
"""Strategy for installing wheel distributions"""
272
273
def install_candidate(
274
self,
275
candidate: PreparedCandidate,
276
environment: BaseEnvironment
277
) -> None:
278
"""Install wheel using installer library"""
279
280
class EditableInstaller(InstallationStrategy):
281
"""Strategy for editable/development installations"""
282
283
def install_candidate(
284
self,
285
candidate: PreparedCandidate,
286
environment: BaseEnvironment
287
) -> None:
288
"""Install package in editable mode"""
289
```
290
291
### Usage Examples
292
293
#### Basic Installation Operations
294
295
```python
296
from pdm.installers import InstallManager
297
from pdm.environments import PythonEnvironment
298
from pdm.models.candidates import Candidate
299
300
# Setup environment and installer
301
env = PythonEnvironment(python_executable="python3.9")
302
installer = InstallManager(env, use_install_cache=True)
303
304
# Install packages
305
candidates = [
306
Candidate.from_requirement("requests>=2.25.0"),
307
Candidate.from_requirement("click>=8.0.0")
308
]
309
310
installer.install(candidates)
311
312
# Uninstall packages
313
installer.uninstall(["requests", "click"])
314
```
315
316
#### Environment Synchronization
317
318
```python
319
from pdm.installers import Synchronizer, UvSynchronizer
320
from pdm.environments import PythonEnvironment
321
from pdm.project import Project
322
323
project = Project()
324
env = PythonEnvironment(project.python.executable)
325
326
# Get lockfile candidates
327
lockfile = project.lockfile.read()
328
candidates = {
329
name: Candidate.from_lockfile_entry(entry)
330
for name, entry in lockfile["package"].items()
331
}
332
333
# Synchronize with standard synchronizer
334
sync = Synchronizer(env, clean=True, dry_run=False)
335
sync.synchronize(candidates)
336
337
# Or use UV synchronizer for better performance
338
uv_sync = UvSynchronizer(env)
339
uv_sync.synchronize(candidates)
340
```
341
342
#### Installation Planning
343
344
```python
345
from pdm.installers import InstallManager
346
from pdm.environments import PythonEnvironment
347
348
env = PythonEnvironment()
349
installer = InstallManager(env)
350
351
# Generate installation plan
352
candidates = [
353
Candidate.from_requirement("django>=4.0"),
354
Candidate.from_requirement("psycopg2-binary")
355
]
356
357
plan = installer.get_installation_plan(candidates)
358
359
# Review plan before installation
360
print(plan.format_summary())
361
print(f"Total operations: {plan.operation_count}")
362
print(f"Download size: {plan.total_size / (1024*1024):.1f} MB")
363
364
# Execute if acceptable
365
if input("Proceed? (y/N): ").lower() == 'y':
366
installer.install(candidates)
367
```
368
369
#### Custom Installation Strategy
370
371
```python
372
from pdm.installers import InstallationStrategy, InstallManager
373
from pdm.environments import BaseEnvironment
374
from pdm.models.candidates import PreparedCandidate
375
376
class CustomInstaller(InstallationStrategy):
377
"""Custom installation strategy with logging"""
378
379
def install_candidate(
380
self,
381
candidate: PreparedCandidate,
382
environment: BaseEnvironment
383
) -> None:
384
print(f"Installing {candidate.name} {candidate.version}")
385
386
# Custom pre-installation steps
387
self._pre_install_hook(candidate)
388
389
# Standard wheel installation
390
if candidate.wheel_path:
391
environment.install_wheel(candidate.wheel_path)
392
else:
393
environment.install_sdist(candidate.source_dir)
394
395
# Custom post-installation steps
396
self._post_install_hook(candidate)
397
398
def _pre_install_hook(self, candidate: PreparedCandidate) -> None:
399
"""Custom pre-installation logic"""
400
pass
401
402
def _post_install_hook(self, candidate: PreparedCandidate) -> None:
403
"""Custom post-installation logic"""
404
pass
405
406
# Use custom installer
407
env = PythonEnvironment()
408
installer = InstallManager(env)
409
installer.strategy = CustomInstaller()
410
```
411
412
#### Batch Installation Operations
413
414
```python
415
from pdm.installers import InstallManager
416
from pdm.environments import PythonEnvironment
417
import asyncio
418
419
async def install_packages_batch(
420
packages: list[str],
421
environment: BaseEnvironment
422
) -> None:
423
"""Install multiple packages in batches"""
424
425
installer = InstallManager(environment)
426
batch_size = 5
427
428
for i in range(0, len(packages), batch_size):
429
batch = packages[i:i + batch_size]
430
candidates = [
431
Candidate.from_requirement(pkg) for pkg in batch
432
]
433
434
print(f"Installing batch {i//batch_size + 1}: {batch}")
435
installer.install(candidates)
436
437
# Brief pause between batches
438
await asyncio.sleep(1)
439
440
# Usage
441
packages = [
442
"requests", "click", "rich", "typer", "httpx",
443
"pydantic", "fastapi", "pytest", "black", "mypy"
444
]
445
446
env = PythonEnvironment()
447
asyncio.run(install_packages_batch(packages, env))
448
```