CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-semantic-version

A library implementing the 'SemVer' scheme.

Pending
Overview
Eval results
Files

specification-matching.mddocs/

Specification Matching

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.

Capabilities

Simple Specification Syntax

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.0

NPM-Style Specifications

The 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 ranges

Version Matching

Test 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)  # False

Version Filtering

Filter 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']

Best Version Selection

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)  # None

Base Specification Class

The 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 types

Specification Parsing

Create 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')

Utility Function

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)

Advanced Usage Patterns

Prerelease Handling

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)        # True

Build Metadata

Build 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) # True

Complex NPM Ranges

Advanced 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)  # False

Internal Specification Architecture

Understanding the internal classes can help with advanced usage and debugging of complex version matching scenarios.

Range Class

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): ...

Prerelease and Build Policies

  • prerelease_policy='natural': Prerelease versions don't satisfy non-prerelease constraints
  • prerelease_policy='always': Prerelease versions can satisfy any constraint
  • prerelease_policy='same-patch': Prerelease versions only considered if target has same major.minor.patch
  • build_policy='implicit': Build metadata ignored in comparisons (default)
  • build_policy='strict': Build metadata must match exactly

Clause Combinators

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): ...

Error Handling

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}")

Performance Considerations

  • Specifications are compiled once and can be reused for multiple matches
  • Use filter() for large collections rather than individual match() calls
  • select() is optimized and stops at the first highest version found
  • Consider caching specification objects for repeated use

Install with Tessl CLI

npx tessl i tessl/pypi-semantic-version

docs

django-integration.md

index.md

specification-matching.md

version-operations.md

tile.json