A CLI tool to find the latest stable version of an arbitrary project
—
Enhanced version class and utilities for parsing, normalizing, and comparing software versions. Extends PEP 440 with intelligent handling of real-world version inconsistencies found across different software projects and platforms.
Enhanced version class that extends packaging.version.Version with additional normalization and transformation capabilities for handling inconsistent real-world version formats.
class Version(PackagingVersion):
"""
Enhanced version class implementing PEP 440 with additional normalization.
This class extends packaging.version.Version with specialized transformations
to handle common version format inconsistencies found in real-world software
projects across different platforms and release practices.
"""
def __init__(self, version: str):
"""
Initialize Version with intelligent normalization.
Parameters:
- version: Version string to parse and normalize
Raises:
- InvalidVersion: If version cannot be normalized to PEP 440 format
"""
@staticmethod
def special_cases_transformation(version: str) -> str:
"""
Apply specialized transformations for common version format issues.
Handles cases like:
- Release candidate formats (rc1.2 → rc1)
- Post-release patterns (p1 → post1)
- Preview/early access versions
- Beta-RC combinations
- Dashed pre-release formats
Parameters:
- version: Raw version string
Returns:
- Transformed version string compatible with PEP 440
"""The Version class applies various regex-based transformations to normalize version strings:
# Dashed substitution patterns applied during normalization
regex_dashed_substitutions = [
(re.compile(r"-p(\d+)$"), "-post\\1"), # -p1 → -post1
(re.compile(r"-preview-(\d+)"), "-pre\\1"), # -preview-1 → -pre1
(re.compile(r"-early-access-(\d+)"), "-alpha\\1"), # -early-access-1 → -alpha1
(re.compile(r"-pre-(\d+)"), "-pre\\1"), # -pre-1 → -pre1
(re.compile(r"-beta[-.]rc(\d+)"), "-beta\\1"), # -beta-rc1 → -beta1
(re.compile(r"^pre-(.*)"), "\\1-pre0"), # pre-1.0 → 1.0-pre0
]
# Part normalization mapping for common pre-release identifiers
part_to_pypi_dict = {
"devel": "dev0",
"test": "dev0",
"dev": "dev0",
"alpha": "a0",
"beta": "b0",
"rc": "rc0",
"preview": "rc0",
"pre": "rc0",
}Utility functions for extracting and parsing version information from various sources.
def parse_version(tag: str) -> Version:
"""
Parse version from git tag or other version source.
Extracts version information from tag strings that may contain
prefixes, suffixes, or other metadata beyond the core version.
Parameters:
- tag: Tag string or version identifier
Returns:
- Version object with parsed and normalized version
Raises:
- InvalidVersion: If no valid version can be extracted
"""from lastversion.version import Version
# Create version objects
v1 = Version("1.2.3")
v2 = Version("1.2.4")
# Version comparison
print(v1 < v2) # True
print(v1 == v2) # False
print(max(v1, v2)) # Version('1.2.4')
# String representation
print(str(v1)) # "1.2.3"
print(repr(v1)) # "<Version('1.2.3')>"from lastversion.version import Version
# Normalize various real-world version formats
versions = [
"v1.2.3-rc1", # Git tag with prefix
"release-1.2.3", # Release prefix
"1.2.3-p1", # Post-release
"1.2.3-preview-1", # Preview release
"1.2.3-beta-rc2", # Beta release candidate
"pre-1.2.3", # Pre-release prefix
]
normalized = [Version(v) for v in versions]
for orig, norm in zip(versions, normalized):
print(f"{orig} → {norm}")from lastversion.version import Version
from packaging.version import InvalidVersion
def filter_stable_versions(version_strings):
"""Filter out pre-release and invalid versions."""
stable_versions = []
for version_str in version_strings:
try:
version = Version(version_str)
# Check if it's a stable release (no pre-release components)
if not version.is_prerelease:
stable_versions.append(version)
except InvalidVersion:
continue # Skip invalid versions
return sorted(stable_versions)
# Example usage
raw_versions = ["1.0.0", "1.1.0-beta", "1.1.0", "2.0.0-rc1", "2.0.0"]
stable = filter_stable_versions(raw_versions)
print(f"Latest stable: {max(stable)}") # Version('2.0.0')from lastversion import latest
from lastversion.version import Version
# Get version object directly
version_obj = latest("mautic/mautic", output_format="version")
# Version objects support all comparison operations
if version_obj >= Version("4.0.0"):
print("Major version 4 or higher")
# Check version properties
print(f"Is prerelease: {version_obj.is_prerelease}")
print(f"Is devrelease: {version_obj.is_devrelease}")
print(f"Major version: {version_obj.major}")
print(f"Minor version: {version_obj.minor}")
print(f"Micro version: {version_obj.micro}")from lastversion.version import Version
# Example of how special_cases_transformation works
test_versions = [
"1.0.0-p1", # Post release
"2.0.0-preview-1", # Preview
"3.0.0-beta-rc2", # Beta release candidate
"pre-4.0.0", # Pre-prefixed version
]
for version_str in test_versions:
transformed = Version.special_cases_transformation(version_str)
version_obj = Version(transformed)
print(f"{version_str} → {transformed} → {version_obj}")from lastversion.version import Version
from packaging.version import InvalidVersion
def safe_version_parse(version_str):
"""Safely parse version with error handling."""
try:
return Version(version_str)
except InvalidVersion as e:
print(f"Invalid version '{version_str}': {e}")
return None
# Example usage
versions = ["1.2.3", "invalid", "1.2.3-rc1", "not.a.version"]
parsed = [safe_version_parse(v) for v in versions]
valid_versions = [v for v in parsed if v is not None]Install with Tessl CLI
npx tessl i tessl/pypi-lastversion