0
# Specification Matching
1
2
Flexible version requirement specifications for filtering and selecting versions based on ranges. Supports both simple syntax and NPM-style range specifications for comprehensive version matching capabilities.
3
4
## Capabilities
5
6
### Simple Specification Syntax
7
8
The SimpleSpec class provides an intuitive syntax for version requirements using standard comparison operators.
9
10
```python { .api }
11
class SimpleSpec:
12
def __init__(self, expression: str):
13
"""
14
Create a specification from a simple expression.
15
16
Args:
17
expression: Specification string using operators like '>=1.0.0,<2.0.0'
18
19
Supported operators:
20
- '==': Exact match
21
- '!=': Not equal
22
- '<': Less than
23
- '<=': Less than or equal
24
- '>': Greater than
25
- '>=': Greater than or equal
26
- Multiple requirements can be combined with commas
27
"""
28
```
29
30
**Syntax Examples:**
31
32
```python
33
# Single requirements
34
spec1 = SimpleSpec('>=1.0.0') # At least 1.0.0
35
spec2 = SimpleSpec('<2.0.0') # Less than 2.0.0
36
spec3 = SimpleSpec('==1.2.3') # Exactly 1.2.3
37
spec4 = SimpleSpec('!=1.0.0') # Not 1.0.0
38
39
# Combined requirements
40
spec5 = SimpleSpec('>=1.0.0,<2.0.0') # Between 1.0.0 and 2.0.0
41
spec6 = SimpleSpec('>=1.0.0,<2.0.0,!=1.5.0') # Range excluding 1.5.0
42
```
43
44
### NPM-Style Specifications
45
46
The NpmSpec class supports the complete NPM semver range specification syntax including advanced operators and ranges.
47
48
```python { .api }
49
class NpmSpec:
50
def __init__(self, expression: str):
51
"""
52
Create a specification from an NPM-style expression.
53
54
Args:
55
expression: NPM-style specification string
56
57
Supported syntax:
58
- '^1.0.0': Compatible within major version
59
- '~1.0.0': Compatible within minor version
60
- '1.0.0 - 1.9.0': Range from-to
61
- '1.x': Any minor/patch in major 1
62
- '>=1.0.0 <2.0.0': Combined requirements
63
- '>=1.0.0 || >=2.0.0': Alternative requirements (OR)
64
"""
65
```
66
67
**Syntax Examples:**
68
69
```python
70
# Caret ranges (compatible within major)
71
spec1 = NpmSpec('^1.0.0') # >=1.0.0 <2.0.0
72
73
# Tilde ranges (compatible within minor)
74
spec2 = NpmSpec('~1.2.0') # >=1.2.0 <1.3.0
75
76
# X-ranges
77
spec3 = NpmSpec('1.x') # >=1.0.0 <2.0.0
78
spec4 = NpmSpec('1.2.x') # >=1.2.0 <1.3.0
79
80
# Hyphen ranges
81
spec5 = NpmSpec('1.0.0 - 1.9.0') # >=1.0.0 <=1.9.0
82
83
# OR expressions
84
spec6 = NpmSpec('>=1.0.0 <1.5.0 || >=2.0.0') # Multiple ranges
85
```
86
87
### Version Matching
88
89
Test whether individual versions satisfy specification requirements.
90
91
```python { .api }
92
def match(self, version: Version) -> bool:
93
"""
94
Test if a version matches this specification.
95
96
Args:
97
version: Version object to test
98
99
Returns:
100
True if version satisfies specification
101
"""
102
103
def __contains__(self, version: Version) -> bool:
104
"""
105
Support 'version in spec' syntax.
106
107
Args:
108
version: Version object to test
109
110
Returns:
111
True if version satisfies specification
112
"""
113
```
114
115
**Usage Examples:**
116
117
```python
118
spec = SimpleSpec('>=1.0.0,<2.0.0')
119
120
# Using match method
121
print(spec.match(Version('1.5.0'))) # True
122
print(spec.match(Version('2.0.0'))) # False
123
124
# Using 'in' operator (recommended)
125
print(Version('1.5.0') in spec) # True
126
print(Version('0.9.0') in spec) # False
127
128
# NPM-style matching
129
npm_spec = NpmSpec('^1.0.0')
130
print(Version('1.2.3') in npm_spec) # True
131
print(Version('2.0.0') in npm_spec) # False
132
```
133
134
### Version Filtering
135
136
Filter collections of versions to find those matching specification requirements.
137
138
```python { .api }
139
def filter(self, versions: Iterable[Version]) -> Iterator[Version]:
140
"""
141
Filter versions that match this specification.
142
143
Args:
144
versions: Iterable of Version objects
145
146
Returns:
147
Iterator yielding matching versions
148
"""
149
```
150
151
**Usage Examples:**
152
153
```python
154
versions = [
155
Version('0.9.0'),
156
Version('1.0.0'),
157
Version('1.2.3'),
158
Version('1.5.0'),
159
Version('2.0.0'),
160
Version('2.1.0')
161
]
162
163
spec = SimpleSpec('>=1.0.0,<2.0.0')
164
matching = list(spec.filter(versions))
165
print([str(v) for v in matching]) # ['1.0.0', '1.2.3', '1.5.0']
166
167
# NPM-style filtering
168
npm_spec = NpmSpec('^1.0.0')
169
npm_matching = list(npm_spec.filter(versions))
170
print([str(v) for v in npm_matching]) # ['1.0.0', '1.2.3', '1.5.0']
171
```
172
173
### Best Version Selection
174
175
Select the highest version that matches specification requirements.
176
177
```python { .api }
178
def select(self, versions: Iterable[Version]) -> Version | None:
179
"""
180
Select the best (highest) version matching this specification.
181
182
Args:
183
versions: Iterable of Version objects
184
185
Returns:
186
Highest matching version, or None if no matches
187
"""
188
```
189
190
**Usage Examples:**
191
192
```python
193
versions = [
194
Version('1.0.0'),
195
Version('1.2.3'),
196
Version('1.5.0'),
197
Version('2.0.0')
198
]
199
200
spec = SimpleSpec('>=1.0.0,<2.0.0')
201
best = spec.select(versions)
202
print(best) # Version('1.5.0')
203
204
# No matches returns None
205
strict_spec = SimpleSpec('>=3.0.0')
206
no_match = strict_spec.select(versions)
207
print(no_match) # None
208
```
209
210
### Base Specification Class
211
212
The BaseSpec class provides the foundation for all specification types with common functionality.
213
214
```python { .api }
215
class BaseSpec:
216
def __init__(self, expression: str): ...
217
218
@classmethod
219
def parse(cls, expression: str, syntax: str = 'simple'): ...
220
221
def filter(self, versions): ...
222
def match(self, version): ...
223
def select(self, versions): ...
224
def __contains__(self, version): ...
225
226
@classmethod
227
def register_syntax(cls, subclass): ... # For registering new syntax types
228
```
229
230
### Specification Parsing
231
232
Create specifications with explicit syntax control using the BaseSpec.parse method.
233
234
```python { .api }
235
@classmethod
236
def parse(cls, expression: str, syntax: str = 'simple') -> 'BaseSpec':
237
"""
238
Parse specification expression with explicit syntax.
239
240
Args:
241
expression: Specification string
242
syntax: 'simple' or 'npm'
243
244
Returns:
245
Appropriate specification object (SimpleSpec or NpmSpec)
246
"""
247
```
248
249
**Usage Examples:**
250
251
```python
252
# Parse with explicit syntax using BaseSpec
253
from semantic_version import BaseSpec
254
255
simple_spec = BaseSpec.parse('>=1.0.0,<2.0.0', syntax='simple')
256
npm_spec = BaseSpec.parse('^1.0.0', syntax='npm')
257
258
# Default syntax is 'simple'
259
default_spec = BaseSpec.parse('>=1.0.0')
260
261
# Direct class usage (recommended for known syntax)
262
simple_spec = SimpleSpec('>=1.0.0,<2.0.0')
263
npm_spec = NpmSpec('^1.0.0')
264
```
265
266
### Utility Function
267
268
Convenient function for quick version-specification matching.
269
270
```python { .api }
271
def match(spec: str, version: str) -> bool:
272
"""
273
Test if a version string matches a specification string.
274
275
Args:
276
spec: Specification string (uses simple syntax)
277
version: Version string to test
278
279
Returns:
280
True if version matches specification
281
"""
282
```
283
284
**Usage Examples:**
285
286
```python
287
# Quick matching without creating objects
288
print(match('>=1.0.0', '1.2.3')) # True
289
print(match('<2.0.0', '2.0.0')) # False
290
print(match('==1.0.0', '1.0.0+build')) # True (build ignored)
291
```
292
293
## Advanced Usage Patterns
294
295
### Prerelease Handling
296
297
Specifications have specific behavior with prerelease versions:
298
299
```python
300
spec = SimpleSpec('>=1.0.0')
301
302
# Prerelease versions don't satisfy non-prerelease requirements
303
print(Version('1.0.0-alpha') in spec) # False
304
305
# But explicit prerelease specs work
306
prerelease_spec = SimpleSpec('>=1.0.0-alpha')
307
print(Version('1.0.0-alpha') in prerelease_spec) # True
308
print(Version('1.0.0') in prerelease_spec) # True
309
```
310
311
### Build Metadata
312
313
Build metadata is ignored in version matching:
314
315
```python
316
spec = SimpleSpec('==1.0.0')
317
318
print(Version('1.0.0') in spec) # True
319
print(Version('1.0.0+build1') in spec) # True
320
print(Version('1.0.0+build2') in spec) # True
321
```
322
323
### Complex NPM Ranges
324
325
Advanced NPM specification patterns:
326
327
```python
328
# OR expressions for multiple acceptable ranges
329
multi_range = NpmSpec('>=1.0.0 <1.5.0 || >=2.0.0 <3.0.0')
330
print(Version('1.2.0') in multi_range) # True
331
print(Version('1.8.0') in multi_range) # False
332
print(Version('2.5.0') in multi_range) # True
333
334
# Partial version matching
335
partial_spec = NpmSpec('1.2') # Equivalent to >=1.2.0 <1.3.0
336
print(Version('1.2.0') in partial_spec) # True
337
print(Version('1.2.5') in partial_spec) # True
338
print(Version('1.3.0') in partial_spec) # False
339
```
340
341
## Internal Specification Architecture
342
343
Understanding the internal classes can help with advanced usage and debugging of complex version matching scenarios.
344
345
### Range Class
346
347
The Range class represents individual version constraints with specific operators and policies.
348
349
```python { .api }
350
class Range:
351
def __init__(self, operator, target, prerelease_policy='natural', build_policy='implicit'):
352
"""
353
Create a range constraint.
354
355
Args:
356
operator: '==', '!=', '<', '<=', '>', '>='
357
target: Version object to compare against
358
prerelease_policy: 'natural', 'always', 'same-patch'
359
build_policy: 'implicit', 'strict'
360
"""
361
362
def match(self, version): ...
363
```
364
365
### Prerelease and Build Policies
366
367
- **prerelease_policy='natural'**: Prerelease versions don't satisfy non-prerelease constraints
368
- **prerelease_policy='always'**: Prerelease versions can satisfy any constraint
369
- **prerelease_policy='same-patch'**: Prerelease versions only considered if target has same major.minor.patch
370
- **build_policy='implicit'**: Build metadata ignored in comparisons (default)
371
- **build_policy='strict'**: Build metadata must match exactly
372
373
### Clause Combinators
374
375
```python { .api }
376
class Always:
377
def match(self, version): ... # Always returns True
378
379
class Never:
380
def match(self, version): ... # Always returns False
381
382
class AllOf:
383
def __init__(self, *clauses): ... # AND combination
384
def match(self, version): ...
385
386
class AnyOf:
387
def __init__(self, *clauses): ... # OR combination
388
def match(self, version): ...
389
```
390
391
## Error Handling
392
393
Specification classes handle errors consistently:
394
395
```python
396
# Invalid specification syntax
397
try:
398
invalid_spec = SimpleSpec('invalid syntax')
399
except ValueError as e:
400
print(f"Invalid specification: {e}")
401
402
# Invalid NPM syntax
403
try:
404
invalid_npm = NpmSpec('^^1.0.0') # Double caret invalid
405
except ValueError as e:
406
print(f"Invalid NPM specification: {e}")
407
```
408
409
## Performance Considerations
410
411
- Specifications are compiled once and can be reused for multiple matches
412
- Use `filter()` for large collections rather than individual `match()` calls
413
- `select()` is optimized and stops at the first highest version found
414
- Consider caching specification objects for repeated use