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

cli-framework.mddocs/

0

# CLI Framework

1

2

Extensible command-line interface supporting custom commands, argument parsing, plugin integration, and comprehensive action implementations for PDM's command system.

3

4

## Capabilities

5

6

### Base Command System

7

8

Foundation classes for implementing PDM CLI commands with consistent argument parsing and execution patterns.

9

10

```python { .api }

11

from argparse import ArgumentParser, Namespace, _SubParsersAction

12

from typing import Any, Sequence

13

14

class BaseCommand:

15

"""

16

Base class for all CLI commands providing consistent interface

17

and integration with PDM's command system.

18

"""

19

20

# Class attributes

21

name: str | None = None

22

description: str | None = None

23

arguments: Sequence[Option] = (verbose_option, global_option, project_option)

24

25

@classmethod

26

def register_to(cls, subparsers: _SubParsersAction, name: str | None = None, **kwargs: Any) -> None:

27

"""

28

Register command to argument parser subcommands.

29

30

Args:

31

subparsers: Subparser for command registration

32

name: Optional command name override

33

**kwargs: Additional arguments for add_parser

34

"""

35

36

def add_arguments(self, parser: ArgumentParser) -> None:

37

"""

38

Add command-specific arguments to parser.

39

40

Args:

41

parser: Argument parser for this command

42

"""

43

44

def handle(self, project: Project, options: Namespace) -> None:

45

"""

46

Execute command with parsed options.

47

48

Args:

49

project: PDM project instance

50

options: Parsed command line options

51

52

Raises:

53

PdmUsageError: Command usage errors

54

PdmException: Command execution errors

55

"""

56

```

57

58

### Core CLI Commands

59

60

Comprehensive set of built-in commands covering all PDM functionality.

61

62

#### Dependency Management Commands

63

64

```python { .api }

65

class AddCommand(BaseCommand):

66

"""Add dependencies to project"""

67

68

def handle(self, project: Project, options: Namespace) -> None:

69

"""

70

Add dependencies with version constraints and groups.

71

72

Options:

73

- packages: Package specifications to add

74

- group: Dependency group name

75

- dev: Add to development dependencies

76

- editable: Install in editable mode

77

- no-sync: Don't sync after adding

78

"""

79

80

class RemoveCommand(BaseCommand):

81

"""Remove dependencies from project"""

82

83

def handle(self, project: Project, options: Namespace) -> None:

84

"""

85

Remove specified packages from project.

86

87

Options:

88

- packages: Package names to remove

89

- group: Dependency group name

90

- dev: Remove from development dependencies

91

- no-sync: Don't sync after removing

92

"""

93

94

class UpdateCommand(BaseCommand):

95

"""Update project dependencies"""

96

97

def handle(self, project: Project, options: Namespace) -> None:

98

"""

99

Update dependencies to latest compatible versions.

100

101

Options:

102

- packages: Specific packages to update (default: all)

103

- group: Update specific dependency group

104

- top: Only update top-level dependencies

105

- dry-run: Show what would be updated

106

"""

107

```

108

109

#### Project Lifecycle Commands

110

111

```python { .api }

112

class InitCommand(BaseCommand):

113

"""Initialize new PDM project"""

114

115

def handle(self, project: Project, options: Namespace) -> None:

116

"""

117

Initialize project with pyproject.toml and configuration.

118

119

Options:

120

- name: Project name

121

- version: Initial version

122

- author: Author information

123

- license: License specification

124

- python: Python version requirement

125

"""

126

127

class InstallCommand(BaseCommand):

128

"""Install project dependencies"""

129

130

def handle(self, project: Project, options: Namespace) -> None:

131

"""

132

Install all project dependencies from lockfile or requirements.

133

134

Options:

135

- group: Install specific dependency group

136

- production: Skip development dependencies

137

- dry-run: Show what would be installed

138

- no-lock: Don't update lockfile

139

"""

140

141

class SyncCommand(BaseCommand):

142

"""Synchronize environment with lockfile"""

143

144

def handle(self, project: Project, options: Namespace) -> None:

145

"""

146

Sync environment to exactly match lockfile specifications.

147

148

Options:

149

- group: Sync specific dependency group

150

- production: Only production dependencies

151

- clean: Remove packages not in lockfile

152

- dry-run: Show sync operations

153

"""

154

```

155

156

#### Build and Publishing Commands

157

158

```python { .api }

159

class BuildCommand(BaseCommand):

160

"""Build distribution packages"""

161

162

def handle(self, project: Project, options: Namespace) -> None:

163

"""

164

Build wheel and/or source distributions.

165

166

Options:

167

- dest: Output directory for distributions

168

- no-sdist: Skip source distribution

169

- no-wheel: Skip wheel distribution

170

- config-settings: Build configuration overrides

171

"""

172

173

class PublishCommand(BaseCommand):

174

"""Publish packages to repository"""

175

176

def handle(self, project: Project, options: Namespace) -> None:

177

"""

178

Upload distributions to package repository.

179

180

Options:

181

- repository: Repository URL or name

182

- username: Authentication username

183

- password: Authentication password

184

- comment: Upload comment

185

- sign: Sign uploads with GPG

186

"""

187

```

188

189

### CLI Actions

190

191

Core implementation functions that handle the actual command logic.

192

193

```python { .api }

194

def do_add(

195

project: Project,

196

requirements: list[str],

197

group: str = "default",

198

dev: bool = False,

199

editable: bool = False,

200

sync: bool = True,

201

**kwargs

202

) -> dict:

203

"""

204

Add dependencies to project.

205

206

Args:

207

project: Project instance

208

requirements: List of requirement specifications

209

group: Dependency group name

210

dev: Add to development dependencies (deprecated)

211

editable: Install packages in editable mode

212

sync: Synchronize environment after adding

213

214

Returns:

215

Dictionary with operation results and statistics

216

"""

217

218

def do_install(

219

project: Project,

220

groups: list[str] | None = None,

221

production: bool = False,

222

check: bool = False,

223

**kwargs

224

) -> None:

225

"""

226

Install project dependencies.

227

228

Args:

229

project: Project instance

230

groups: Specific dependency groups to install

231

production: Skip development dependencies

232

check: Verify installation without installing

233

"""

234

235

def do_lock(

236

project: Project,

237

groups: list[str] | None = None,

238

refresh: bool = False,

239

**kwargs

240

) -> dict:

241

"""

242

Generate project lockfile.

243

244

Args:

245

project: Project instance

246

groups: Dependency groups to include

247

refresh: Refresh all cached data

248

249

Returns:

250

Dictionary with lock operation results

251

"""

252

253

def do_sync(

254

project: Project,

255

groups: list[str] | None = None,

256

production: bool = False,

257

clean: bool = False,

258

**kwargs

259

) -> None:

260

"""

261

Synchronize environment with lockfile.

262

263

Args:

264

project: Project instance

265

groups: Dependency groups to sync

266

production: Only production dependencies

267

clean: Remove packages not in lockfile

268

"""

269

270

def do_update(

271

project: Project,

272

requirements: list[str] | None = None,

273

groups: list[str] | None = None,

274

top: bool = False,

275

**kwargs

276

) -> dict:

277

"""

278

Update project dependencies.

279

280

Args:

281

project: Project instance

282

requirements: Specific packages to update

283

groups: Dependency groups to update

284

top: Only update top-level dependencies

285

286

Returns:

287

Dictionary with update results

288

"""

289

290

def do_remove(

291

project: Project,

292

requirements: list[str],

293

group: str = "default",

294

dev: bool = False,

295

sync: bool = True,

296

**kwargs

297

) -> dict:

298

"""

299

Remove dependencies from project.

300

301

Args:

302

project: Project instance

303

requirements: Package names to remove

304

group: Dependency group name

305

dev: Remove from development dependencies (deprecated)

306

sync: Synchronize environment after removal

307

308

Returns:

309

Dictionary with removal results

310

"""

311

```

312

313

### Argument Parsing Utilities

314

315

Enhanced argument parsing with PDM-specific extensions and error handling.

316

317

```python { .api }

318

class ArgumentParser:

319

"""

320

Enhanced argument parser with PDM-specific functionality.

321

322

Provides better error messages, command suggestions, and

323

integration with PDM's help system.

324

"""

325

326

def add_subparsers(self, **kwargs):

327

"""Add subparser for commands"""

328

329

def parse_args(self, args: list[str] | None = None) -> Namespace:

330

"""Parse command line arguments with error handling"""

331

332

class ErrorArgumentParser(ArgumentParser):

333

"""

334

Argument parser that raises PdmArgumentError instead of exiting.

335

336

Used for testing and programmatic command invocation.

337

"""

338

339

def error(self, message: str) -> None:

340

"""Raise PdmArgumentError instead of system exit"""

341

342

def format_similar_command(command: str, available: list[str]) -> str:

343

"""

344

Format suggestion for similar commands when command not found.

345

346

Args:

347

command: Command that was not found

348

available: List of available commands

349

350

Returns:

351

Formatted suggestion message

352

"""

353

```

354

355

### Usage Examples

356

357

#### Creating Custom Commands

358

359

```python

360

from pdm.cli.commands.base import BaseCommand

361

from pdm.core import Core

362

from argparse import ArgumentParser, Namespace

363

364

class MyCustomCommand(BaseCommand):

365

"""Custom command example"""

366

367

@property

368

def name(self) -> str:

369

return "mycmd"

370

371

@property

372

def description(self) -> str:

373

return "My custom PDM command"

374

375

def add_arguments(self, parser: ArgumentParser) -> None:

376

parser.add_argument(

377

"--my-option",

378

help="Custom option for my command"

379

)

380

parser.add_argument(

381

"target",

382

help="Target for command operation"

383

)

384

385

def handle(self, project, options: Namespace) -> None:

386

print(f"Executing custom command on {options.target}")

387

if options.my_option:

388

print(f"Option value: {options.my_option}")

389

390

# Register command via plugin

391

def my_plugin(core: Core) -> None:

392

core.register_command(MyCustomCommand(), "mycmd")

393

```

394

395

#### Using CLI Actions Programmatically

396

397

```python

398

from pdm.project import Project

399

from pdm.cli.actions import do_add, do_install, do_lock, do_sync

400

401

# Load project

402

project = Project()

403

404

# Add dependencies programmatically

405

result = do_add(

406

project,

407

requirements=["requests>=2.25.0", "click>=8.0"],

408

group="default",

409

sync=False # Don't sync yet

410

)

411

print(f"Added {len(result['added'])} packages")

412

413

# Add dev dependencies

414

do_add(

415

project,

416

requirements=["pytest", "black", "mypy"],

417

group="dev",

418

sync=False

419

)

420

421

# Generate lockfile

422

lock_result = do_lock(project)

423

print(f"Locked {len(lock_result['candidates'])} packages")

424

425

# Install all dependencies

426

do_install(project, production=False)

427

428

# Or sync to exact lockfile state

429

do_sync(project, clean=True)

430

```

431

432

#### Command Option Handling

433

434

```python

435

from pdm.cli.commands.base import BaseCommand

436

from argparse import ArgumentParser, Namespace

437

438

class ExampleCommand(BaseCommand):

439

"""Example showing comprehensive option handling"""

440

441

def add_arguments(self, parser: ArgumentParser) -> None:

442

# Boolean flags

443

parser.add_argument(

444

"--dry-run",

445

action="store_true",

446

help="Show what would be done without executing"

447

)

448

449

# String options with choices

450

parser.add_argument(

451

"--level",

452

choices=["debug", "info", "warning", "error"],

453

default="info",

454

help="Logging level"

455

)

456

457

# Multiple values

458

parser.add_argument(

459

"--exclude",

460

action="append",

461

help="Packages to exclude (can be repeated)"

462

)

463

464

# Positional arguments

465

parser.add_argument(

466

"packages",

467

nargs="*",

468

help="Package names to process"

469

)

470

471

def handle(self, project, options: Namespace) -> None:

472

if options.dry_run:

473

print("DRY RUN - no changes will be made")

474

475

print(f"Log level: {options.level}")

476

477

if options.exclude:

478

print(f"Excluding: {', '.join(options.exclude)}")

479

480

for package in options.packages:

481

print(f"Processing package: {package}")

482

```