or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

build-backend.mdbuilders.mdconfiguration.mdconstraints.mdfactory-core.mdindex.mdjson-validation.mdpackages.mdutilities.mdvcs-support.mdversion-system.md

version-system.mddocs/

0

# PEP 440 Version and Environment Markers

1

2

Poetry Core provides comprehensive support for PEP 440 version specifications and PEP 508 environment markers. This enables precise version handling and conditional dependency specification based on the installation environment.

3

4

## Core Imports

5

6

```python

7

# PEP 440 version components

8

from poetry.core.version.pep440 import (

9

PEP440Version,

10

Release,

11

ReleaseTag,

12

LocalSegmentType

13

)

14

15

# Environment markers

16

from poetry.core.version.markers import (

17

BaseMarker,

18

AnyMarker,

19

EmptyMarker,

20

SingleMarker,

21

MultiMarker,

22

MarkerUnion,

23

parse_marker,

24

intersection,

25

union

26

)

27

28

# Marker exceptions

29

from poetry.core.version.markers import (

30

InvalidMarkerError,

31

UndefinedComparisonError,

32

UndefinedEnvironmentNameError

33

)

34

``` { .api }

35

36

## PEP 440 Version Implementation

37

38

### PEP440Version

39

40

```python

41

class PEP440Version:

42

"""

43

Core PEP 440 version implementation.

44

45

Supports all PEP 440 version formats including:

46

- Release versions: 1.2.3, 2.0.0

47

- Pre-releases: 1.0.0a1, 2.0.0b2, 1.5.0rc1

48

- Post-releases: 1.0.0.post1

49

- Development releases: 1.0.0.dev0

50

- Local versions: 1.0.0+local.1

51

"""

52

53

@classmethod

54

def parse(cls, version: str) -> PEP440Version:

55

"""

56

Parse version string into PEP440Version object.

57

58

Args:

59

version: PEP 440 compliant version string

60

61

Returns:

62

PEP440Version instance

63

64

Raises:

65

InvalidVersionError: If version string is not PEP 440 compliant

66

67

Examples:

68

>>> v1 = PEP440Version.parse("1.2.3")

69

>>> v2 = PEP440Version.parse("2.0.0a1") # Alpha pre-release

70

>>> v3 = PEP440Version.parse("1.0.0.post1") # Post-release

71

>>> v4 = PEP440Version.parse("1.0.0.dev0") # Development

72

>>> v5 = PEP440Version.parse("1.0.0+local") # Local version

73

"""

74

75

@property

76

def epoch(self) -> int:

77

"""Version epoch (defaults to 0)."""

78

79

@property

80

def release(self) -> Release:

81

"""Release version tuple (e.g., (1, 2, 3))."""

82

83

@property

84

def pre(self) -> ReleaseTag | None:

85

"""Pre-release information (alpha, beta, rc)."""

86

87

@property

88

def post(self) -> int | None:

89

"""Post-release number."""

90

91

@property

92

def dev(self) -> int | None:

93

"""Development release number."""

94

95

@property

96

def local(self) -> str | None:

97

"""Local version identifier."""

98

99

@property

100

def is_prerelease(self) -> bool:

101

"""Whether version is a pre-release."""

102

103

@property

104

def is_postrelease(self) -> bool:

105

"""Whether version is a post-release."""

106

107

@property

108

def is_devrelease(self) -> bool:

109

"""Whether version is a development release."""

110

111

def __str__(self) -> str:

112

"""String representation of version."""

113

114

def __eq__(self, other: object) -> bool:

115

"""Version equality comparison."""

116

117

def __lt__(self, other: PEP440Version) -> bool:

118

"""Version less-than comparison."""

119

120

def __le__(self, other: PEP440Version) -> bool:

121

"""Version less-than-or-equal comparison."""

122

123

def __gt__(self, other: PEP440Version) -> bool:

124

"""Version greater-than comparison."""

125

126

def __ge__(self, other: PEP440Version) -> bool:

127

"""Version greater-than-or-equal comparison."""

128

``` { .api }

129

130

### Release

131

132

```python

133

class Release:

134

"""

135

Version release component representing the main version tuple.

136

137

Handles version tuples like (1, 2, 3) with proper comparison

138

and normalization according to PEP 440.

139

"""

140

141

def __init__(self, *parts: int) -> None:

142

"""

143

Create release version.

144

145

Args:

146

parts: Version parts (e.g., 1, 2, 3 for "1.2.3")

147

148

Example:

149

>>> release = Release(1, 2, 3)

150

>>> print(release) # 1.2.3

151

"""

152

153

@property

154

def parts(self) -> tuple[int, ...]:

155

"""Version parts tuple."""

156

157

def __str__(self) -> str:

158

"""String representation (e.g., "1.2.3")."""

159

160

def __eq__(self, other: object) -> bool:

161

"""Equality comparison with normalization."""

162

163

def __lt__(self, other: Release) -> bool:

164

"""Less-than comparison."""

165

``` { .api }

166

167

### ReleaseTag

168

169

```python

170

class ReleaseTag:

171

"""

172

Pre-release tag information (alpha, beta, release candidate).

173

174

Represents pre-release versions like 1.0.0a1, 2.0.0b2, 1.5.0rc1

175

with proper ordering and normalization.

176

"""

177

178

def __init__(self, tag: str, number: int) -> None:

179

"""

180

Create release tag.

181

182

Args:

183

tag: Tag type ("a", "b", "rc" or full forms)

184

number: Tag number

185

186

Example:

187

>>> alpha = ReleaseTag("a", 1) # alpha 1

188

>>> beta = ReleaseTag("b", 2) # beta 2

189

>>> rc = ReleaseTag("rc", 1) # release candidate 1

190

"""

191

192

@property

193

def tag(self) -> str:

194

"""Normalized tag type ("a", "b", "rc")."""

195

196

@property

197

def number(self) -> int:

198

"""Tag number."""

199

200

def __str__(self) -> str:

201

"""String representation (e.g., "a1", "b2", "rc1")."""

202

203

def __eq__(self, other: object) -> bool:

204

"""Equality comparison."""

205

206

def __lt__(self, other: ReleaseTag) -> bool:

207

"""Less-than comparison (a < b < rc)."""

208

``` { .api }

209

210

### LocalSegmentType

211

212

```python

213

LocalSegmentType = Union[int, str]

214

"""

215

Type for local version segments.

216

217

Local versions can contain integers and strings separated by dots or hyphens,

218

like "1.0.0+local.1" or "1.0.0+abc.123".

219

"""

220

``` { .api }

221

222

## Environment Markers

223

224

Environment markers provide conditional dependency installation based on the target environment.

225

226

### parse_marker

227

228

```python

229

def parse_marker(marker: str) -> BaseMarker:

230

"""

231

Parse PEP 508 environment marker string.

232

233

Args:

234

marker: PEP 508 marker expression

235

236

Returns:

237

BaseMarker implementation representing the parsed marker

238

239

Raises:

240

InvalidMarkerError: If marker syntax is invalid

241

242

Examples:

243

>>> marker = parse_marker("python_version >= '3.8'")

244

>>> marker = parse_marker("sys_platform == 'win32'")

245

>>> marker = parse_marker("python_version >= '3.8' and sys_platform != 'win32'")

246

>>> marker = parse_marker("extra == 'dev'")

247

"""

248

``` { .api }

249

250

### intersection

251

252

```python

253

def intersection(*markers: BaseMarker) -> BaseMarker:

254

"""

255

Create intersection (AND) of multiple markers.

256

257

Args:

258

markers: Markers to intersect

259

260

Returns:

261

BaseMarker representing the intersection

262

263

Example:

264

>>> marker1 = parse_marker("python_version >= '3.8'")

265

>>> marker2 = parse_marker("sys_platform != 'win32'")

266

>>> combined = intersection(marker1, marker2)

267

>>> # Equivalent to: python_version >= '3.8' and sys_platform != 'win32'

268

"""

269

``` { .api }

270

271

### union

272

273

```python

274

def union(*markers: BaseMarker) -> BaseMarker:

275

"""

276

Create union (OR) of multiple markers.

277

278

Args:

279

markers: Markers to union

280

281

Returns:

282

BaseMarker representing the union

283

284

Example:

285

>>> marker1 = parse_marker("sys_platform == 'win32'")

286

>>> marker2 = parse_marker("sys_platform == 'darwin'")

287

>>> combined = union(marker1, marker2)

288

>>> # Equivalent to: sys_platform == 'win32' or sys_platform == 'darwin'

289

"""

290

``` { .api }

291

292

## Marker Classes

293

294

### BaseMarker

295

296

```python

297

class BaseMarker:

298

"""

299

Abstract base class for all environment markers.

300

301

Provides the interface for marker evaluation, intersection,

302

and union operations.

303

"""

304

305

@abstractmethod

306

def intersect(self, other: BaseMarker) -> BaseMarker:

307

"""Create intersection with another marker."""

308

309

@abstractmethod

310

def union(self, other: BaseMarker) -> BaseMarker:

311

"""Create union with another marker."""

312

313

def is_any(self) -> bool:

314

"""Check if marker matches any environment."""

315

316

def is_empty(self) -> bool:

317

"""Check if marker matches no environment."""

318

319

@property

320

def complexity(self) -> tuple[int, int]:

321

"""

322

Marker complexity metrics for optimization.

323

324

Returns:

325

Tuple of (detailed_count, simplified_count)

326

"""

327

328

def evaluate(self, environment: dict[str, Any] | None = None) -> bool:

329

"""

330

Evaluate marker against environment.

331

332

Args:

333

environment: Environment variables dict (uses current if None)

334

335

Returns:

336

True if marker matches the environment

337

"""

338

``` { .api }

339

340

### AnyMarker

341

342

```python

343

class AnyMarker(BaseMarker):

344

"""

345

Marker that matches any environment.

346

347

Represents the absence of restrictions - all environments are valid.

348

"""

349

350

def is_any(self) -> bool:

351

"""Always returns True."""

352

353

def evaluate(self, environment: dict[str, Any] | None = None) -> bool:

354

"""Always returns True."""

355

356

def intersect(self, other: BaseMarker) -> BaseMarker:

357

"""Returns the other marker (intersection with 'any')."""

358

359

def union(self, other: BaseMarker) -> BaseMarker:

360

"""Returns self (union with 'any' is 'any')."""

361

``` { .api }

362

363

### EmptyMarker

364

365

```python

366

class EmptyMarker(BaseMarker):

367

"""

368

Marker that matches no environment.

369

370

Represents impossible conditions or explicit exclusion.

371

"""

372

373

def is_empty(self) -> bool:

374

"""Always returns True."""

375

376

def evaluate(self, environment: dict[str, Any] | None = None) -> bool:

377

"""Always returns False."""

378

379

def intersect(self, other: BaseMarker) -> BaseMarker:

380

"""Returns self (intersection with 'empty' is 'empty')."""

381

382

def union(self, other: BaseMarker) -> BaseMarker:

383

"""Returns the other marker (union with 'empty')."""

384

``` { .api }

385

386

### SingleMarker

387

388

```python

389

class SingleMarker(BaseMarker):

390

"""

391

Single condition marker (e.g., "python_version >= '3.8'").

392

393

Represents a single comparison operation between an environment

394

variable and a value.

395

"""

396

397

def __init__(self, name: str, constraint: BaseConstraint) -> None:

398

"""

399

Create single marker.

400

401

Args:

402

name: Environment variable name

403

constraint: Constraint to apply to the variable

404

405

Example:

406

>>> from poetry.core.constraints.generic import parse_constraint

407

>>> marker = SingleMarker("python_version", parse_constraint(">= 3.8"))

408

"""

409

410

@property

411

def name(self) -> str:

412

"""Environment variable name."""

413

414

@property

415

def constraint(self) -> BaseConstraint:

416

"""Constraint applied to the variable."""

417

418

def evaluate(self, environment: dict[str, Any] | None = None) -> bool:

419

"""Evaluate constraint against environment variable."""

420

``` { .api }

421

422

### MultiMarker

423

424

```python

425

class MultiMarker(BaseMarker):

426

"""

427

Conjunction of multiple markers (AND operation).

428

429

All constituent markers must be satisfied.

430

"""

431

432

def __init__(self, *markers: BaseMarker) -> None:

433

"""

434

Create conjunction of markers.

435

436

Args:

437

markers: Markers that must all be satisfied

438

439

Example:

440

>>> marker1 = parse_marker("python_version >= '3.8'")

441

>>> marker2 = parse_marker("sys_platform != 'win32'")

442

>>> multi = MultiMarker(marker1, marker2)

443

"""

444

445

@property

446

def markers(self) -> tuple[BaseMarker, ...]:

447

"""Constituent markers."""

448

449

def evaluate(self, environment: dict[str, Any] | None = None) -> bool:

450

"""Check if all markers are satisfied."""

451

``` { .api }

452

453

### MarkerUnion

454

455

```python

456

class MarkerUnion(BaseMarker):

457

"""

458

Disjunction of multiple markers (OR operation).

459

460

Any constituent marker can be satisfied.

461

"""

462

463

def __init__(self, *markers: BaseMarker) -> None:

464

"""

465

Create disjunction of markers.

466

467

Args:

468

markers: Markers where any can be satisfied

469

470

Example:

471

>>> marker1 = parse_marker("sys_platform == 'win32'")

472

>>> marker2 = parse_marker("sys_platform == 'darwin'")

473

>>> union = MarkerUnion(marker1, marker2)

474

"""

475

476

@property

477

def markers(self) -> tuple[BaseMarker, ...]:

478

"""Constituent markers."""

479

480

def evaluate(self, environment: dict[str, Any] | None = None) -> bool:

481

"""Check if any marker is satisfied."""

482

``` { .api }

483

484

## Environment Variables

485

486

### Supported Environment Variables

487

488

```python

489

# Standard PEP 508 environment variables

490

ENVIRONMENT_VARIABLES = {

491

"implementation_name", # Python implementation (cpython, pypy, etc.)

492

"implementation_version", # Implementation version

493

"os_name", # Operating system name (posix, nt, java)

494

"platform_machine", # Machine type (x86_64, i386, etc.)

495

"platform_python_implementation", # Python implementation

496

"platform_release", # Platform release

497

"platform_system", # Platform system (Linux, Windows, Darwin)

498

"platform_version", # Platform version

499

"python_full_version", # Full Python version (3.9.1)

500

"python_version", # Python version (3.9)

501

"sys_platform", # Platform string (linux, win32, darwin)

502

"extra", # Extra/feature being installed

503

}

504

505

# Variable aliases for compatibility

506

ALIASES = {

507

"os.name": "os_name",

508

"sys.platform": "sys_platform",

509

"platform.version": "platform_version",

510

"platform.machine": "platform_machine",

511

"platform.python_implementation": "platform_python_implementation",

512

"python_implementation": "platform_python_implementation",

513

}

514

``` { .api }

515

516

## Usage Examples

517

518

### Version Parsing and Comparison

519

520

```python

521

from poetry.core.version.pep440 import PEP440Version

522

523

def version_examples():

524

"""Demonstrate PEP 440 version parsing and comparison."""

525

526

# Parse various version formats

527

versions = [

528

"1.0.0", # Release

529

"2.0.0a1", # Alpha pre-release

530

"1.5.0b2", # Beta pre-release

531

"2.1.0rc1", # Release candidate

532

"1.0.0.post1", # Post-release

533

"2.0.0.dev0", # Development release

534

"1.0.0+local.1", # Local version

535

"1!2.0.0", # Epoch version

536

]

537

538

parsed_versions = []

539

for version_str in versions:

540

try:

541

version = PEP440Version.parse(version_str)

542

parsed_versions.append(version)

543

544

print(f"Version: {version}")

545

print(f" Release: {version.release}")

546

print(f" Pre-release: {version.pre}")

547

print(f" Post-release: {version.post}")

548

print(f" Dev release: {version.dev}")

549

print(f" Local: {version.local}")

550

print(f" Is pre-release: {version.is_prerelease}")

551

print()

552

553

except Exception as e:

554

print(f"Failed to parse '{version_str}': {e}")

555

556

# Version comparison

557

print("Version Comparison:")

558

sorted_versions = sorted(parsed_versions)

559

for version in sorted_versions:

560

print(f" {version}")

561

562

version_examples()

563

``` { .api }

564

565

### Environment Marker Parsing

566

567

```python

568

from poetry.core.version.markers import parse_marker

569

570

def marker_examples():

571

"""Demonstrate environment marker parsing and evaluation."""

572

573

markers = [

574

"python_version >= '3.8'",

575

"sys_platform == 'win32'",

576

"python_version >= '3.8' and sys_platform != 'win32'",

577

"python_version < '3.9' or python_version >= '3.10'",

578

"implementation_name == 'cpython'",

579

"platform_machine == 'x86_64'",

580

"extra == 'dev'",

581

"os_name == 'posix' and platform_system == 'Linux'",

582

]

583

584

print("Parsing Environment Markers:")

585

for marker_str in markers:

586

try:

587

marker = parse_marker(marker_str)

588

print(f"✓ {marker_str}")

589

print(f" Type: {type(marker).__name__}")

590

print(f" Is any: {marker.is_any()}")

591

print(f" Is empty: {marker.is_empty()}")

592

593

except Exception as e:

594

print(f"✗ {marker_str}: {e}")

595

print()

596

597

marker_examples()

598

``` { .api }

599

600

### Marker Evaluation

601

602

```python

603

import platform

604

import sys

605

from poetry.core.version.markers import parse_marker

606

607

def evaluate_markers():

608

"""Evaluate markers against current environment."""

609

610

# Get current environment

611

current_env = {

612

"implementation_name": sys.implementation.name,

613

"implementation_version": ".".join(map(str, sys.implementation.version[:2])),

614

"os_name": os.name,

615

"platform_machine": platform.machine(),

616

"platform_python_implementation": platform.python_implementation(),

617

"platform_release": platform.release(),

618

"platform_system": platform.system(),

619

"platform_version": platform.version(),

620

"python_full_version": ".".join(map(str, sys.version_info)),

621

"python_version": ".".join(map(str, sys.version_info[:2])),

622

"sys_platform": sys.platform,

623

}

624

625

print(f"Current Environment:")

626

for key, value in current_env.items():

627

print(f" {key}: {value}")

628

print()

629

630

# Test markers

631

test_markers = [

632

"python_version >= '3.8'",

633

"python_version >= '3.12'",

634

"sys_platform == 'win32'",

635

"sys_platform == 'linux'",

636

"implementation_name == 'cpython'",

637

"implementation_name == 'pypy'",

638

]

639

640

print("Marker Evaluation:")

641

for marker_str in test_markers:

642

try:

643

marker = parse_marker(marker_str)

644

result = marker.evaluate(current_env)

645

status = "✓ MATCH" if result else "✗ NO MATCH"

646

print(f"{status}: {marker_str}")

647

648

except Exception as e:

649

print(f"ERROR: {marker_str} -> {e}")

650

651

evaluate_markers()

652

``` { .api }

653

654

### Complex Marker Operations

655

656

```python

657

from poetry.core.version.markers import parse_marker, intersection, union

658

659

def complex_marker_operations():

660

"""Demonstrate complex marker operations."""

661

662

# Create individual markers

663

py38_plus = parse_marker("python_version >= '3.8'")

664

not_windows = parse_marker("sys_platform != 'win32'")

665

linux_only = parse_marker("sys_platform == 'linux'")

666

macos_only = parse_marker("sys_platform == 'darwin'")

667

668

# Intersection (AND)

669

py38_not_win = intersection(py38_plus, not_windows)

670

print(f"Python 3.8+ AND not Windows: {py38_not_win}")

671

672

# Union (OR)

673

linux_or_macos = union(linux_only, macos_only)

674

print(f"Linux OR macOS: {linux_or_macos}")

675

676

# Complex combination

677

complex_marker = intersection(py38_plus, linux_or_macos)

678

print(f"Python 3.8+ AND (Linux OR macOS): {complex_marker}")

679

680

# Test evaluation

681

test_environments = [

682

{"python_version": "3.9", "sys_platform": "linux"},

683

{"python_version": "3.7", "sys_platform": "linux"},

684

{"python_version": "3.9", "sys_platform": "win32"},

685

{"python_version": "3.9", "sys_platform": "darwin"},

686

]

687

688

print("\nEvaluation Results:")

689

for env in test_environments:

690

result = complex_marker.evaluate(env)

691

py_ver = env["python_version"]

692

platform = env["sys_platform"]

693

status = "✓" if result else "✗"

694

print(f" {status} Python {py_ver} on {platform}")

695

696

complex_marker_operations()

697

``` { .api }

698

699

### Practical Dependency Scenarios

700

701

```python

702

from poetry.core.version.markers import parse_marker

703

704

def dependency_scenarios():

705

"""Show practical dependency marker scenarios."""

706

707

scenarios = {

708

"Windows-specific dependency": "sys_platform == 'win32'",

709

"Unix-only dependency": "os_name == 'posix'",

710

"Modern Python requirement": "python_version >= '3.8'",

711

"Legacy Python support": "python_version < '3.8'",

712

"CPython optimization": "implementation_name == 'cpython'",

713

"PyPy compatibility": "implementation_name == 'pypy'",

714

"Development extra": "extra == 'dev'",

715

"Testing extra": "extra == 'test'",

716

"macOS ARM64": "sys_platform == 'darwin' and platform_machine == 'arm64'",

717

"Linux x86_64": "sys_platform == 'linux' and platform_machine == 'x86_64'",

718

"Not Windows": "sys_platform != 'win32'",

719

"Python 3.8-3.11": "python_version >= '3.8' and python_version < '3.12'",

720

}

721

722

print("Common Dependency Marker Scenarios:")

723

print("=" * 50)

724

725

for scenario, marker_str in scenarios.items():

726

try:

727

marker = parse_marker(marker_str)

728

print(f"{scenario}:")

729

print(f" Marker: {marker_str}")

730

print(f" Parsed: {marker}")

731

print(f" Type: {type(marker).__name__}")

732

print()

733

734

except Exception as e:

735

print(f"❌ {scenario}: {e}")

736

print()

737

738

dependency_scenarios()

739

``` { .api }

740

741

## Error Handling

742

743

### Marker Exceptions

744

745

```python

746

from poetry.core.version.markers import (

747

InvalidMarkerError,

748

UndefinedComparisonError,

749

UndefinedEnvironmentNameError,

750

parse_marker

751

)

752

753

def safe_marker_operations():

754

"""Demonstrate safe marker parsing and evaluation."""

755

756

def safe_parse_marker(marker_str: str):

757

"""Safely parse marker with error handling."""

758

try:

759

return parse_marker(marker_str)

760

except InvalidMarkerError as e:

761

print(f"Invalid marker syntax '{marker_str}': {e}")

762

return None

763

except Exception as e:

764

print(f"Unexpected error parsing '{marker_str}': {e}")

765

return None

766

767

def safe_evaluate_marker(marker, environment):

768

"""Safely evaluate marker with error handling."""

769

try:

770

return marker.evaluate(environment)

771

except UndefinedEnvironmentNameError as e:

772

print(f"Undefined environment variable: {e}")

773

return False

774

except UndefinedComparisonError as e:

775

print(f"Invalid comparison: {e}")

776

return False

777

except Exception as e:

778

print(f"Evaluation error: {e}")

779

return False

780

781

# Test various marker strings

782

test_markers = [

783

"python_version >= '3.8'", # Valid

784

"python_version >= 3.8", # Invalid (no quotes)

785

"invalid_var == 'test'", # Valid syntax, undefined var

786

"python_version >> '3.8'", # Invalid operator

787

"python_version", # Incomplete

788

"", # Empty

789

]

790

791

print("Safe Marker Operations:")

792

for marker_str in test_markers:

793

print(f"\nTesting: '{marker_str}'")

794

marker = safe_parse_marker(marker_str)

795

796

if marker:

797

env = {"python_version": "3.9"}

798

result = safe_evaluate_marker(marker, env)

799

print(f" ✓ Parsed successfully")

800

print(f" ✓ Evaluation result: {result}")

801

else:

802

print(f" ✗ Failed to parse")

803

804

safe_marker_operations()

805

``` { .api }