0
# Version and Generic Constraint Handling
1
2
Poetry Core provides a comprehensive constraint system for handling version requirements and generic constraints. This includes PEP 440-compliant version constraints, Poetry-specific syntax (caret and tilde), and generic constraint operations.
3
4
## Core Imports
5
6
```python
7
# Version constraints
8
from poetry.core.constraints.version import (
9
Version,
10
VersionConstraint,
11
VersionRange,
12
VersionRangeConstraint,
13
VersionUnion,
14
EmptyConstraint,
15
parse_constraint,
16
parse_marker_version_constraint,
17
constraint_regions
18
)
19
20
# Generic constraints
21
from poetry.core.constraints.generic import (
22
BaseConstraint,
23
Constraint,
24
AnyConstraint,
25
EmptyConstraint as GenericEmptyConstraint,
26
MultiConstraint,
27
UnionConstraint,
28
parse_constraint as parse_generic_constraint,
29
parse_extra_constraint
30
)
31
``` { .api }
32
33
## Version Constraints
34
35
Version constraints handle PEP 440 version specifications with additional Poetry syntax support.
36
37
### parse_constraint
38
39
```python
40
def parse_constraint(constraints: str) -> VersionConstraint:
41
"""
42
Parse string version constraints into constraint objects.
43
44
Args:
45
constraints: Version constraint string supporting various formats:
46
- PEP 440: ">=1.2.0,<2.0.0", "==1.5.*", "~=1.4.2"
47
- Poetry caret: "^1.2.0" (compatible release)
48
- Poetry tilde: "~1.2.0" (reasonably close)
49
- Wildcards: "1.2.*"
50
- Multiple: ">=1.0,<2.0,!=1.5"
51
52
Returns:
53
VersionConstraint object (Version, VersionRange, VersionUnion, etc.)
54
55
Raises:
56
ParseConstraintError: If constraint string is malformed
57
58
Examples:
59
>>> constraint = parse_constraint("^1.2.0")
60
>>> print(constraint) # >=1.2.0,<2.0.0
61
62
>>> constraint = parse_constraint("~1.2.3")
63
>>> print(constraint) # >=1.2.3,<1.3.0
64
65
>>> constraint = parse_constraint(">=2.0,<3.0,!=2.5")
66
>>> isinstance(constraint, VersionUnion)
67
True
68
"""
69
``` { .api }
70
71
### parse_marker_version_constraint
72
73
```python
74
def parse_marker_version_constraint(constraint: str) -> VersionConstraint:
75
"""
76
Parse version constraints from PEP 508 environment markers.
77
78
Args:
79
constraint: Version constraint from marker context
80
81
Returns:
82
VersionConstraint suitable for marker evaluation
83
84
Example:
85
>>> # From marker: python_version >= "3.8"
86
>>> constraint = parse_marker_version_constraint("3.8")
87
>>> print(constraint)
88
"""
89
``` { .api }
90
91
### constraint_regions
92
93
```python
94
def constraint_regions(constraints: list[VersionConstraint]) -> list[VersionRange]:
95
"""
96
Analyze constraint regions for optimization and conflict detection.
97
98
Args:
99
constraints: List of version constraints to analyze
100
101
Returns:
102
List of VersionRange objects representing constraint regions
103
104
Note:
105
Utility function for advanced constraint analysis and optimization.
106
"""
107
``` { .api }
108
109
## Version Constraint Classes
110
111
### Version
112
113
```python
114
class Version(PEP440Version, VersionRangeConstraint):
115
"""
116
Concrete version implementation with PEP 440 compliance and constraints.
117
118
Combines PEP 440 version parsing with constraint satisfaction logic.
119
Supports all PEP 440 version formats including pre-releases, dev releases,
120
and local versions.
121
"""
122
123
@classmethod
124
def parse(cls, version: str) -> Version:
125
"""
126
Parse version string into Version object.
127
128
Args:
129
version: PEP 440 compliant version string
130
131
Returns:
132
Version instance
133
134
Example:
135
>>> v = Version.parse("1.2.3")
136
>>> v = Version.parse("2.0.0a1") # alpha
137
>>> v = Version.parse("1.0.0.dev0") # dev
138
"""
139
140
def allows(self, other: Version) -> bool:
141
"""Check if this version allows another version (equality)."""
142
143
def intersect(self, other: VersionConstraint) -> VersionConstraint:
144
"""Create intersection with another constraint."""
145
146
def union(self, other: VersionConstraint) -> VersionConstraint:
147
"""Create union with another constraint."""
148
``` { .api }
149
150
### VersionRange
151
152
```python
153
class VersionRange(VersionRangeConstraint):
154
"""
155
Represents a range of versions with minimum and maximum bounds.
156
157
Supports inclusive and exclusive bounds, unbounded ranges,
158
and complex range operations.
159
"""
160
161
def __init__(
162
self,
163
min_version: Version | None = None,
164
max_version: Version | None = None,
165
include_min: bool = True,
166
include_max: bool = False,
167
) -> None:
168
"""
169
Create version range.
170
171
Args:
172
min_version: Minimum version (None for unbounded)
173
max_version: Maximum version (None for unbounded)
174
include_min: Whether minimum is inclusive
175
include_max: Whether maximum is inclusive
176
177
Example:
178
>>> # >=1.0.0,<2.0.0
179
>>> range1 = VersionRange(
180
... Version.parse("1.0.0"),
181
... Version.parse("2.0.0")
182
... )
183
184
>>> # >1.0.0,<=1.5.0
185
>>> range2 = VersionRange(
186
... Version.parse("1.0.0"),
187
... Version.parse("1.5.0"),
188
... include_min=False,
189
... include_max=True
190
... )
191
"""
192
193
@property
194
def min(self) -> Version | None:
195
"""Minimum version bound."""
196
197
@property
198
def max(self) -> Version | None:
199
"""Maximum version bound."""
200
201
def allows(self, version: Version) -> bool:
202
"""Check if version falls within this range."""
203
204
def intersect(self, other: VersionConstraint) -> VersionConstraint:
205
"""Create intersection with another constraint."""
206
207
def union(self, other: VersionConstraint) -> VersionConstraint:
208
"""Create union with another constraint."""
209
``` { .api }
210
211
### VersionUnion
212
213
```python
214
class VersionUnion(VersionConstraint):
215
"""
216
Union of multiple version constraints (OR operation).
217
218
Represents constraints like ">=1.0,<2.0 || >=3.0,<4.0" where
219
a version can satisfy any of the constituent constraints.
220
"""
221
222
def __init__(self, *constraints: VersionConstraint) -> None:
223
"""
224
Create union of constraints.
225
226
Args:
227
constraints: Version constraints to union
228
229
Example:
230
>>> range1 = VersionRange(Version.parse("1.0.0"), Version.parse("2.0.0"))
231
>>> range2 = VersionRange(Version.parse("3.0.0"), Version.parse("4.0.0"))
232
>>> union = VersionUnion(range1, range2)
233
"""
234
235
@property
236
def constraints(self) -> tuple[VersionConstraint, ...]:
237
"""Constituent constraints in the union."""
238
239
def allows(self, version: Version) -> bool:
240
"""Check if version satisfies any constraint in union."""
241
``` { .api }
242
243
### EmptyConstraint
244
245
```python
246
class EmptyConstraint(VersionConstraint):
247
"""
248
Version constraint representing no valid versions.
249
250
Used when constraints are impossible to satisfy or
251
when explicitly excluding all versions.
252
"""
253
254
def allows(self, version: Version) -> bool:
255
"""Always returns False - no versions allowed."""
256
257
def is_empty(self) -> bool:
258
"""Always returns True."""
259
``` { .api }
260
261
## Generic Constraints
262
263
Generic constraints provide a framework for non-version constraint operations.
264
265
### parse_constraint (Generic)
266
267
```python
268
def parse_constraint(constraints: str) -> BaseConstraint:
269
"""
270
Parse string constraints into generic constraint objects.
271
272
Args:
273
constraints: Constraint string
274
275
Returns:
276
BaseConstraint implementation
277
278
Example:
279
>>> constraint = parse_constraint("foo")
280
>>> constraint = parse_constraint("foo,bar") # Multi-constraint
281
"""
282
``` { .api }
283
284
### parse_extra_constraint
285
286
```python
287
def parse_extra_constraint(constraints: str) -> BaseConstraint:
288
"""
289
Parse extra/optional feature constraints.
290
291
Args:
292
constraints: Extra constraint string (e.g., "dev,test")
293
294
Returns:
295
BaseConstraint for extra matching
296
297
Example:
298
>>> extra_constraint = parse_extra_constraint("dev,test")
299
>>> # Used for conditional dependencies
300
"""
301
``` { .api }
302
303
## Generic Constraint Classes
304
305
### BaseConstraint
306
307
```python
308
class BaseConstraint:
309
"""
310
Abstract base class for all constraint types.
311
312
Defines the interface that all constraints must implement
313
for matching, intersection, and union operations.
314
"""
315
316
def allows(self, other: Any) -> bool:
317
"""Check if constraint allows a value."""
318
319
def intersect(self, other: BaseConstraint) -> BaseConstraint:
320
"""Create intersection with another constraint."""
321
322
def union(self, other: BaseConstraint) -> BaseConstraint:
323
"""Create union with another constraint."""
324
325
def is_any(self) -> bool:
326
"""Check if constraint matches any value."""
327
328
def is_empty(self) -> bool:
329
"""Check if constraint matches no values."""
330
``` { .api }
331
332
### Constraint
333
334
```python
335
class Constraint(BaseConstraint):
336
"""
337
Basic constraint implementation for exact matching.
338
339
Matches values exactly against a stored constraint value.
340
"""
341
342
def __init__(self, constraint: str) -> None:
343
"""
344
Create constraint with exact matching value.
345
346
Args:
347
constraint: Value to match exactly
348
"""
349
350
@property
351
def constraint(self) -> str:
352
"""The constraint value."""
353
``` { .api }
354
355
### AnyConstraint
356
357
```python
358
class AnyConstraint(BaseConstraint):
359
"""
360
Constraint that matches any value.
361
362
Represents the absence of constraints - all values are valid.
363
"""
364
365
def allows(self, other: Any) -> bool:
366
"""Always returns True - allows any value."""
367
368
def is_any(self) -> bool:
369
"""Always returns True."""
370
``` { .api }
371
372
### EmptyConstraint (Generic)
373
374
```python
375
class EmptyConstraint(BaseConstraint):
376
"""
377
Generic constraint matching no values.
378
379
Represents impossible or explicitly empty constraints.
380
"""
381
382
def allows(self, other: Any) -> bool:
383
"""Always returns False - allows no values."""
384
385
def is_empty(self) -> bool:
386
"""Always returns True."""
387
``` { .api }
388
389
### MultiConstraint
390
391
```python
392
class MultiConstraint(BaseConstraint):
393
"""
394
Conjunction of multiple constraints (AND operation).
395
396
All constituent constraints must be satisfied.
397
"""
398
399
def __init__(self, *constraints: BaseConstraint) -> None:
400
"""
401
Create conjunction of constraints.
402
403
Args:
404
constraints: Constraints that must all be satisfied
405
"""
406
407
@property
408
def constraints(self) -> tuple[BaseConstraint, ...]:
409
"""Constituent constraints."""
410
411
def allows(self, other: Any) -> bool:
412
"""Check if all constraints allow the value."""
413
``` { .api }
414
415
### UnionConstraint
416
417
```python
418
class UnionConstraint(BaseConstraint):
419
"""
420
Disjunction of multiple constraints (OR operation).
421
422
Any constituent constraint can be satisfied.
423
"""
424
425
def __init__(self, *constraints: BaseConstraint) -> None:
426
"""
427
Create disjunction of constraints.
428
429
Args:
430
constraints: Constraints where any can be satisfied
431
"""
432
433
@property
434
def constraints(self) -> tuple[BaseConstraint, ...]:
435
"""Constituent constraints."""
436
437
def allows(self, other: Any) -> bool:
438
"""Check if any constraint allows the value."""
439
``` { .api }
440
441
## Usage Examples
442
443
### Basic Version Constraint Parsing
444
445
```python
446
from poetry.core.constraints.version import parse_constraint, Version
447
448
def demonstrate_version_constraints():
449
"""Show various version constraint formats."""
450
451
# Poetry caret constraint (compatible release)
452
caret = parse_constraint("^1.2.0")
453
print(f"Caret ^1.2.0: {caret}") # >=1.2.0,<2.0.0
454
455
# Poetry tilde constraint (reasonably close)
456
tilde = parse_constraint("~1.2.3")
457
print(f"Tilde ~1.2.3: {tilde}") # >=1.2.3,<1.3.0
458
459
# PEP 440 constraints
460
pep440 = parse_constraint(">=1.0,<2.0,!=1.5")
461
print(f"PEP 440: {pep440}")
462
463
# Wildcard constraints
464
wildcard = parse_constraint("1.2.*")
465
print(f"Wildcard: {wildcard}")
466
467
# Test version satisfaction
468
version = Version.parse("1.2.5")
469
print(f"Version {version} satisfies caret: {caret.allows(version)}")
470
print(f"Version {version} satisfies tilde: {tilde.allows(version)}")
471
472
demonstrate_version_constraints()
473
``` { .api }
474
475
### Complex Constraint Operations
476
477
```python
478
from poetry.core.constraints.version import parse_constraint, VersionUnion
479
480
def constraint_operations():
481
"""Demonstrate constraint intersection and union."""
482
483
# Create individual constraints
484
constraint1 = parse_constraint(">=1.0.0")
485
constraint2 = parse_constraint("<3.0.0")
486
constraint3 = parse_constraint("!=2.0.0")
487
488
# Intersection (AND) - must satisfy all
489
intersection = constraint1.intersect(constraint2).intersect(constraint3)
490
print(f"Intersection: {intersection}") # >=1.0.0,<3.0.0,!=2.0.0
491
492
# Union (OR) - can satisfy any
493
union = constraint1.union(constraint3)
494
print(f"Union: {union}")
495
496
# Test complex constraint
497
test_versions = ["0.9.0", "1.5.0", "2.0.0", "2.5.0", "3.5.0"]
498
499
print(f"\nTesting intersection ({intersection}):")
500
for version_str in test_versions:
501
version = Version.parse(version_str)
502
satisfies = intersection.allows(version)
503
print(f" {version}: {'✓' if satisfies else '✗'}")
504
505
constraint_operations()
506
``` { .api }
507
508
### Building Custom Constraints
509
510
```python
511
from poetry.core.constraints.version import Version, VersionRange, VersionUnion
512
513
def build_custom_constraints():
514
"""Build constraints programmatically."""
515
516
# Create version range manually
517
min_version = Version.parse("2.0.0")
518
max_version = Version.parse("3.0.0")
519
520
range1 = VersionRange(
521
min_version=min_version,
522
max_version=max_version,
523
include_min=True, # >=2.0.0
524
include_max=False # <3.0.0
525
)
526
527
# Create another range
528
range2 = VersionRange(
529
min_version=Version.parse("4.0.0"),
530
max_version=Version.parse("5.0.0"),
531
include_min=True,
532
include_max=False
533
)
534
535
# Combine ranges with union
536
combined = VersionUnion(range1, range2)
537
print(f"Combined constraint: {combined}")
538
# Allows: >=2.0.0,<3.0.0 || >=4.0.0,<5.0.0
539
540
# Test versions against combined constraint
541
test_versions = ["1.5.0", "2.5.0", "3.5.0", "4.5.0", "5.5.0"]
542
543
for version_str in test_versions:
544
version = Version.parse(version_str)
545
allowed = combined.allows(version)
546
print(f"{version}: {'allowed' if allowed else 'rejected'}")
547
548
build_custom_constraints()
549
``` { .api }
550
551
### Generic Constraint Usage
552
553
```python
554
from poetry.core.constraints.generic import (
555
parse_constraint,
556
MultiConstraint,
557
UnionConstraint,
558
AnyConstraint
559
)
560
561
def demonstrate_generic_constraints():
562
"""Show generic constraint operations."""
563
564
# Parse simple constraints
565
constraint1 = parse_constraint("development")
566
constraint2 = parse_constraint("testing")
567
568
# Create multi-constraint (AND)
569
both_required = MultiConstraint(constraint1, constraint2)
570
print(f"Must have both: {both_required}")
571
572
# Create union constraint (OR)
573
either_allowed = UnionConstraint(constraint1, constraint2)
574
print(f"Can have either: {either_allowed}")
575
576
# Any constraint (no restrictions)
577
any_constraint = AnyConstraint()
578
print(f"Any allowed: {any_constraint.is_any()}")
579
580
# Test constraint matching
581
print(f"'development' satisfies both_required: {both_required.allows('development')}")
582
print(f"'testing' satisfies either_allowed: {either_allowed.allows('testing')}")
583
584
demonstrate_generic_constraints()
585
``` { .api }
586
587
### Poetry Constraint Syntax
588
589
```python
590
def poetry_syntax_examples():
591
"""Demonstrate Poetry-specific constraint syntax."""
592
593
constraints = {
594
"^1.2.3": "Caret - compatible release (>=1.2.3,<2.0.0)",
595
"~1.2.3": "Tilde - reasonably close (>=1.2.3,<1.3.0)",
596
"^0.2.3": "Caret with 0.x (>=0.2.3,<0.3.0)",
597
"^0.0.3": "Caret with 0.0.x (>=0.0.3,<0.0.4)",
598
"~1.2": "Tilde major.minor (>=1.2.0,<1.3.0)",
599
"1.2.*": "Wildcard (>=1.2.0,<1.3.0)",
600
">=1.2,<2.0": "Range constraint",
601
"!=1.5": "Exclusion constraint"
602
}
603
604
print("Poetry Constraint Syntax:")
605
for syntax, description in constraints.items():
606
try:
607
parsed = parse_constraint(syntax)
608
print(f"{syntax:15} -> {parsed} ({description})")
609
except Exception as e:
610
print(f"{syntax:15} -> ERROR: {e}")
611
612
poetry_syntax_examples()
613
``` { .api }
614
615
## Error Handling
616
617
```python
618
from poetry.core.exceptions import ParseConstraintError
619
from poetry.core.constraints.version import parse_constraint
620
621
def safe_constraint_parsing(constraint_str: str):
622
"""Safely parse constraints with error handling."""
623
624
try:
625
constraint = parse_constraint(constraint_str)
626
return constraint
627
628
except ParseConstraintError as e:
629
print(f"Invalid constraint '{constraint_str}': {e}")
630
return None
631
632
except Exception as e:
633
print(f"Unexpected error parsing '{constraint_str}': {e}")
634
return None
635
636
# Usage
637
constraints_to_test = [
638
"^1.2.0", # Valid
639
">=1.0,<2.0", # Valid
640
"invalid", # Invalid
641
"^", # Invalid
642
"1.2.3.4.5" # Invalid
643
]
644
645
for constraint_str in constraints_to_test:
646
result = safe_constraint_parsing(constraint_str)
647
if result:
648
print(f"✓ {constraint_str} -> {result}")
649
else:
650
print(f"✗ {constraint_str} -> Failed to parse")
651
``` { .api }