A library implementing the 'SemVer' scheme.
—
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.
The SimpleSpec class provides an intuitive syntax for version requirements using standard comparison operators.
class SimpleSpec:
def __init__(self, expression: str):
"""
Create a specification from a simple expression.
Args:
expression: Specification string using operators like '>=1.0.0,<2.0.0'
Supported operators:
- '==': Exact match
- '!=': Not equal
- '<': Less than
- '<=': Less than or equal
- '>': Greater than
- '>=': Greater than or equal
- Multiple requirements can be combined with commas
"""Syntax Examples:
# Single requirements
spec1 = SimpleSpec('>=1.0.0') # At least 1.0.0
spec2 = SimpleSpec('<2.0.0') # Less than 2.0.0
spec3 = SimpleSpec('==1.2.3') # Exactly 1.2.3
spec4 = SimpleSpec('!=1.0.0') # Not 1.0.0
# Combined requirements
spec5 = SimpleSpec('>=1.0.0,<2.0.0') # Between 1.0.0 and 2.0.0
spec6 = SimpleSpec('>=1.0.0,<2.0.0,!=1.5.0') # Range excluding 1.5.0The NpmSpec class supports the complete NPM semver range specification syntax including advanced operators and ranges.
class NpmSpec:
def __init__(self, expression: str):
"""
Create a specification from an NPM-style expression.
Args:
expression: NPM-style specification string
Supported syntax:
- '^1.0.0': Compatible within major version
- '~1.0.0': Compatible within minor version
- '1.0.0 - 1.9.0': Range from-to
- '1.x': Any minor/patch in major 1
- '>=1.0.0 <2.0.0': Combined requirements
- '>=1.0.0 || >=2.0.0': Alternative requirements (OR)
"""Syntax Examples:
# Caret ranges (compatible within major)
spec1 = NpmSpec('^1.0.0') # >=1.0.0 <2.0.0
# Tilde ranges (compatible within minor)
spec2 = NpmSpec('~1.2.0') # >=1.2.0 <1.3.0
# X-ranges
spec3 = NpmSpec('1.x') # >=1.0.0 <2.0.0
spec4 = NpmSpec('1.2.x') # >=1.2.0 <1.3.0
# Hyphen ranges
spec5 = NpmSpec('1.0.0 - 1.9.0') # >=1.0.0 <=1.9.0
# OR expressions
spec6 = NpmSpec('>=1.0.0 <1.5.0 || >=2.0.0') # Multiple rangesTest whether individual versions satisfy specification requirements.
def match(self, version: Version) -> bool:
"""
Test if a version matches this specification.
Args:
version: Version object to test
Returns:
True if version satisfies specification
"""
def __contains__(self, version: Version) -> bool:
"""
Support 'version in spec' syntax.
Args:
version: Version object to test
Returns:
True if version satisfies specification
"""Usage Examples:
spec = SimpleSpec('>=1.0.0,<2.0.0')
# Using match method
print(spec.match(Version('1.5.0'))) # True
print(spec.match(Version('2.0.0'))) # False
# Using 'in' operator (recommended)
print(Version('1.5.0') in spec) # True
print(Version('0.9.0') in spec) # False
# NPM-style matching
npm_spec = NpmSpec('^1.0.0')
print(Version('1.2.3') in npm_spec) # True
print(Version('2.0.0') in npm_spec) # FalseFilter collections of versions to find those matching specification requirements.
def filter(self, versions: Iterable[Version]) -> Iterator[Version]:
"""
Filter versions that match this specification.
Args:
versions: Iterable of Version objects
Returns:
Iterator yielding matching versions
"""Usage Examples:
versions = [
Version('0.9.0'),
Version('1.0.0'),
Version('1.2.3'),
Version('1.5.0'),
Version('2.0.0'),
Version('2.1.0')
]
spec = SimpleSpec('>=1.0.0,<2.0.0')
matching = list(spec.filter(versions))
print([str(v) for v in matching]) # ['1.0.0', '1.2.3', '1.5.0']
# NPM-style filtering
npm_spec = NpmSpec('^1.0.0')
npm_matching = list(npm_spec.filter(versions))
print([str(v) for v in npm_matching]) # ['1.0.0', '1.2.3', '1.5.0']Select the highest version that matches specification requirements.
def select(self, versions: Iterable[Version]) -> Version | None:
"""
Select the best (highest) version matching this specification.
Args:
versions: Iterable of Version objects
Returns:
Highest matching version, or None if no matches
"""Usage Examples:
versions = [
Version('1.0.0'),
Version('1.2.3'),
Version('1.5.0'),
Version('2.0.0')
]
spec = SimpleSpec('>=1.0.0,<2.0.0')
best = spec.select(versions)
print(best) # Version('1.5.0')
# No matches returns None
strict_spec = SimpleSpec('>=3.0.0')
no_match = strict_spec.select(versions)
print(no_match) # NoneThe BaseSpec class provides the foundation for all specification types with common functionality.
class BaseSpec:
def __init__(self, expression: str): ...
@classmethod
def parse(cls, expression: str, syntax: str = 'simple'): ...
def filter(self, versions): ...
def match(self, version): ...
def select(self, versions): ...
def __contains__(self, version): ...
@classmethod
def register_syntax(cls, subclass): ... # For registering new syntax typesCreate specifications with explicit syntax control using the BaseSpec.parse method.
@classmethod
def parse(cls, expression: str, syntax: str = 'simple') -> 'BaseSpec':
"""
Parse specification expression with explicit syntax.
Args:
expression: Specification string
syntax: 'simple' or 'npm'
Returns:
Appropriate specification object (SimpleSpec or NpmSpec)
"""Usage Examples:
# Parse with explicit syntax using BaseSpec
from semantic_version import BaseSpec
simple_spec = BaseSpec.parse('>=1.0.0,<2.0.0', syntax='simple')
npm_spec = BaseSpec.parse('^1.0.0', syntax='npm')
# Default syntax is 'simple'
default_spec = BaseSpec.parse('>=1.0.0')
# Direct class usage (recommended for known syntax)
simple_spec = SimpleSpec('>=1.0.0,<2.0.0')
npm_spec = NpmSpec('^1.0.0')Convenient function for quick version-specification matching.
def match(spec: str, version: str) -> bool:
"""
Test if a version string matches a specification string.
Args:
spec: Specification string (uses simple syntax)
version: Version string to test
Returns:
True if version matches specification
"""Usage Examples:
# Quick matching without creating objects
print(match('>=1.0.0', '1.2.3')) # True
print(match('<2.0.0', '2.0.0')) # False
print(match('==1.0.0', '1.0.0+build')) # True (build ignored)Specifications have specific behavior with prerelease versions:
spec = SimpleSpec('>=1.0.0')
# Prerelease versions don't satisfy non-prerelease requirements
print(Version('1.0.0-alpha') in spec) # False
# But explicit prerelease specs work
prerelease_spec = SimpleSpec('>=1.0.0-alpha')
print(Version('1.0.0-alpha') in prerelease_spec) # True
print(Version('1.0.0') in prerelease_spec) # TrueBuild metadata is ignored in version matching:
spec = SimpleSpec('==1.0.0')
print(Version('1.0.0') in spec) # True
print(Version('1.0.0+build1') in spec) # True
print(Version('1.0.0+build2') in spec) # TrueAdvanced NPM specification patterns:
# OR expressions for multiple acceptable ranges
multi_range = NpmSpec('>=1.0.0 <1.5.0 || >=2.0.0 <3.0.0')
print(Version('1.2.0') in multi_range) # True
print(Version('1.8.0') in multi_range) # False
print(Version('2.5.0') in multi_range) # True
# Partial version matching
partial_spec = NpmSpec('1.2') # Equivalent to >=1.2.0 <1.3.0
print(Version('1.2.0') in partial_spec) # True
print(Version('1.2.5') in partial_spec) # True
print(Version('1.3.0') in partial_spec) # FalseUnderstanding the internal classes can help with advanced usage and debugging of complex version matching scenarios.
The Range class represents individual version constraints with specific operators and policies.
class Range:
def __init__(self, operator, target, prerelease_policy='natural', build_policy='implicit'):
"""
Create a range constraint.
Args:
operator: '==', '!=', '<', '<=', '>', '>='
target: Version object to compare against
prerelease_policy: 'natural', 'always', 'same-patch'
build_policy: 'implicit', 'strict'
"""
def match(self, version): ...class Always:
def match(self, version): ... # Always returns True
class Never:
def match(self, version): ... # Always returns False
class AllOf:
def __init__(self, *clauses): ... # AND combination
def match(self, version): ...
class AnyOf:
def __init__(self, *clauses): ... # OR combination
def match(self, version): ...Specification classes handle errors consistently:
# Invalid specification syntax
try:
invalid_spec = SimpleSpec('invalid syntax')
except ValueError as e:
print(f"Invalid specification: {e}")
# Invalid NPM syntax
try:
invalid_npm = NpmSpec('^^1.0.0') # Double caret invalid
except ValueError as e:
print(f"Invalid NPM specification: {e}")filter() for large collections rather than individual match() callsselect() is optimized and stops at the first highest version foundInstall with Tessl CLI
npx tessl i tessl/pypi-semantic-version