A high-performance Python library (written in Rust via PyO3) for parsing and comparing software version strings across all major package ecosystems.
Handles: SemVer, PEP 440, npm, Go modules, Debian/dpkg, RPM, Ruby Gems, Maven, NuGet, Composer, Crates.io, Hex, Swift PM, CalVer, Alpine, Docker, Conda, Arch/pacman, Gentoo/Portage, R/CRAN, Spack, Dart pub, Julia Pkg, Terraform/OpenTofu, Helm, CPAN, Nix, Homebrew, FreeBSD pkg — with a single generic parser, ecosystem-specific modes, range helpers, and security-interoperability utilities.
pip install anyverBuild from source (requires Rust toolchain):
pip install maturin
maturin develop --releasefrom anyver import Version
import anyver
# Parse and compare versions
Version("1.2.3") < Version("2.0.0") # True
Version("1.0.0-alpha") < Version("1.0.0") # True
Version("1.0") == Version("1.0.0") # True (trailing zero equivalence)
Version("v1.0") == Version("1.0") # True (v-prefix stripped)
Version("1.0+build") == Version("1.0+other") # True (build metadata ignored)
# Module-level compare
anyver.compare("1.0", "2.0") # -1
anyver.compare("2.0", "1.0") # 1
anyver.compare("1.0", "1.0.0") # 0v = Version("1.2.3-rc.1+build.42")
# Properties
v.raw # "1.2.3-rc.1+build.42"
v.ecosystem # "generic" (detected ecosystem name)
v.epoch # 0
v.build # "build.42"
v.major # 1
v.minor # 2
v.patch # 3
v.is_prerelease # True
v.is_postrelease # False
v.is_stable # False (opposite of is_prerelease)
# Segments
v.segments() # (1, 2, 3, "rc", 1)
v.release() # (1, 2, 3)
len(v) # 5
v[0] # 1
v[3] # "rc"
v[-1] # 1
# String representations
str(v) # "1.2.3-rc.1+build.42"
repr(v) # "Version('1.2.3-rc.1+build.42')"
# Compare using the Version's own detected ecosystem
v.compare("1.2.3") # 0
v.compare("2.0.0") # -1
v.compare("0.1.0") # 1anyver.sort_versions(["2.0", "1.0-alpha", "1.0", "0.1"])
# ["0.1", "1.0-alpha", "1.0", "2.0"]
anyver.batch_compare([("1.0", "2.0"), ("2.0", "1.0"), ("1.0", "1.0")])
# [-1, 1, 0]
anyver.max_version(["1.0", "3.0", "2.0"]) # "3.0"
anyver.min_version(["1.0", "3.0", "2.0"]) # "1.0"anyver.gt("2.0", "1.0") # True
anyver.ge("1.0", "1.0.0") # True
anyver.lt("1.0", "2.0") # True
anyver.le("1.0", "1.0.0") # True
anyver.eq("1.0", "1.0.0") # True
anyver.ne("1.0", "2.0") # TrueCheck whether a version satisfies a constraint expression. Supports >=, <=, >, <, ==, != operators, combined with commas for AND logic:
anyver.satisfies("1.5.0", ">=1.0.0,<2.0.0") # True
anyver.satisfies("2.0.0", ">=1.0.0,<2.0.0") # False
anyver.satisfies("1.0.0", ">=1.0.0") # True
anyver.satisfies("1.0.0", "!=1.0.0") # False
anyver.satisfies("1.0.0-alpha", ">1.0.0") # False
# Works with any ecosystem
anyver.satisfies("5.14.0-503.19.1.el9_5", ">=5.14.0-427.0.0.el9_4", ecosystem="rpm")Range helpers also support OR with ||, npm/Cargo-style caret and tilde ranges, x-ranges, Maven/NuGet interval notation, and Terraform-style ~>:
anyver.range_contains("1.5.0", ">=1.0.0,<2.0.0") # True
anyver.range_contains("1.2.7", "1.2.x") # True
anyver.range_contains("1.5.0", "[1.0.0,2.0.0)") # True
anyver.ranges_intersect(">=1.0,<2.0", ">=1.5,<3.0") # TrueFilter out pre-release versions or find the latest stable:
anyver.stable_versions(["2.0.0-rc1", "1.0.0", "2.0.0", "1.5.0-beta"])
# ["1.0.0", "2.0.0"]
anyver.latest_stable(["2.0.0-rc1", "1.0.0", "2.0.0", "1.5.0-beta"])
# "2.0.0"anyver.bump_major("1.2.3") # "2.0.0"
anyver.bump_minor("1.2.3") # "1.3.0"
anyver.bump_patch("1.2.3") # "1.2.4"
# Pre-release tags are stripped
anyver.bump_major("1.2.3-alpha") # "2.0.0"
anyver.bump_patch("1.0.0-rc1") # "1.0.1"s = {Version("1.0"), Version("1.0.0"), Version("2.0")}
len(s) # 2 — "1.0" and "1.0.0" are equal, so deduplicated
d = {Version("1.0"): "stable"}
d[Version("1.0.0")] # "stable"By default, Version() and anyver.version() use ecosystem="auto" — the library inspects the version string and picks the best ecosystem automatically:
# Auto-detected as PEP 440 (contains "!")
Version("1!2.0.0") # ecosystem=pep440
# Auto-detected as Debian (contains "~")
Version("1.0~rc1") # ecosystem=debian
# Auto-detected as RPM (contains ".fc" / ".el")
Version("5.14.0-362.24.1.el9_4") # ecosystem=rpm
# Auto-detected as Go (ends with "+incompatible")
Version("v2.0.0+incompatible") # ecosystem=go
# Auto-detected as Alpine (contains "_alpha", "_rc", "-rN")
Version("3.1.4-r5") # ecosystem=alpine
# Auto-detected as Maven (ends with "-SNAPSHOT")
Version("1.0-SNAPSHOT") # ecosystem=maven
# Auto-detected as CalVer (starts with year-like number)
Version("2024.1.15") # ecosystem=calver
# Falls back to generic when ambiguous
Version("1.2.3") # ecosystem=genericDetection rules (in priority order):
!in string → PEP 440~→ Debian,^→ RPM.post,.dev→ PEP 440+incompatible→ Go,-SNAPSHOT→ Maven_alpha,_beta,_rc,_p,-rN→ Alpine+deb,+ubuntu→ Debian;.el,.fc,.amzn→ RPM- Digit-letter patterns like
1a1,1b2,1rc1→ PEP 440 - Dot-separated
.pre,.rc,.beta,.alpha(no hyphens) → Ruby - Year-like first segment (1990-2100) → CalVer
- Otherwise → Generic
You can also set the ecosystem explicitly:
# Strict SemVer (numeric < alpha in pre-release)
anyver.compare_semver_strict("1.0.0-alpha", "1.0.0") # -1
# Debian/dpkg (tilde sorts before everything)
anyver.compare("1.0~rc1", "1.0", ecosystem="debian") # -1
# RPM (caret for post-release snapshots)
anyver.compare("1.0", "1.0^git1", ecosystem="rpm") # -1
# PEP 440 (epoch with !)
anyver.compare("1!0.1", "2.0", ecosystem="pep440") # 1
# All supported ecosystems:
# auto (default for Version), generic (default for compare),
# semver, npm, pep440, debian, rpm, go, ruby/gem/rubygems,
# maven/mvn, nuget/dotnet, composer/php/packagist,
# crates/cargo, hex/elixir/erlang, swift/swiftpm,
# calver, alpine/apk, docker/oci,
# conda/mamba, arch/pacman, gentoo/portage, cran/r,
# spack, dart/pub/flutter, julia, terraform/opentofu, helm,
# cpan/perl, nix/nixpkgs, homebrew/brew, freebsd/freebsd-pkgThe mature exact-native comparators are Debian/dpkg, RPM, Maven
(ComparableVersion), strict SemVer-family modes, and PEP 440 local-version
handling. Newer ecosystem modes start with validation plus generic or
SemVer-compatible ordering where their native algorithm is not yet fully
specialized.
Version("1.2.3-rc.1+build.42").to_dict()
# {
# "raw": "1.2.3-rc.1+build.42",
# "epoch": 0,
# "major": 1,
# "minor": 2,
# "patch": 3,
# "build": "build.42",
# "is_prerelease": True,
# "is_postrelease": False
# }Version("1.2.3-rc.1").sort_key()
# Tuple of tuples that preserves comparison order when sorted lexically.
# Isomorphic with comparing two versions of the same ecosystem, including
# PEP 440 local labels (e.g. `1.0+abc` sorts above `1.0`).Scope: the key reproduces the generic/SemVer and PEP 440 orderings exactly. Debian, RPM and Maven compare with their native
dpkg/rpm/ComparableVersionalgorithms, which the key approximates via the generic ordering — the same documented approximation the parser makes for those ecosystems. For Debian/RPM/Maven, sort withanyver.sort_versions(..., ecosystem=...)rather than bysort_key()when exact native ordering matters.
hash(Version(...))follows the same encoding, with Maven versions hashed through their canonical form so that equal Maven versions always hash equal. Hash equality is guaranteed within one ecosystem; mixing ecosystems in a set or dict is best-effort, since cross-ecosystem==falls back to generic comparison.
Helpers are provided for package-url (PURL), VERS-style version ranges, and OSV-shaped affected records:
anyver.parse_purl("pkg:npm/%40scope/[email protected]")
# {"type": "npm", "namespace": "%40scope", "name": "name",
# "version": "1.2.3", "ecosystem": "npm", ...}
anyver.build_purl("npm", "name", "1.2.3", "%40scope")
# "pkg:npm/%40scope/[email protected]"
vers = anyver.build_vers("npm", ">=1.0.0,<2.0.0")
anyver.vers_contains(vers, "1.5.0") # True
affected = {
"package": {"ecosystem": "npm", "name": "demo"},
"ranges": [{"events": [{"introduced": "1.0.0"}, {"fixed": "1.2.0"}]}],
}
anyver.osv_affected("1.1.0", affected) # True# PEP 440 (Python)
Version("1.0.dev1") < Version("1.0a1") < Version("1.0b1") < Version("1.0rc1") < Version("1.0") < Version("1.0.post1")
# Debian
Version("1.0~alpha") < Version("1.0~beta") < Version("1.0")
# RPM
Version("1.0~rc1") < Version("1.0") < Version("1.0^git1")
# Maven — ecosystem="maven" implements Apache Maven's ComparableVersion
Version("1.0-alpha-1") < Version("1.0-SNAPSHOT") < Version("1.0") < Version("1.0-sp-1")
anyver.compare("1.0-jre", "1.0", ecosystem="maven") # 1 — unknown qualifiers rank above the release
anyver.compare("1.0-1", "1.0.1", ecosystem="maven") # -1 — `-` nests, `.` does not
# Go modules
Version("v2.0.0+incompatible") == Version("v2.0.0")
# Ruby Gems — numeric, not lexical
Version("3.2") < Version("3.10")
# Epoch overrides everything
Version("1:0.1") > Version("999.0")Built in Rust for speed. Typical benchmarks (Apple M-series):
| Operation | Time |
|---|---|
Version("1.2.3") construction |
~330 ns |
anyver.compare("1.2.3", "1.2.4") |
~300 ns |
Version < Version (pre-parsed) |
~66 ns |
sort_versions(1000) |
~460 us |
vs Python packaging.version |
~14x faster |
vs Python semver |
~23x faster |
anyver follows Semantic Versioning. The public API
surface covered by SemVer guarantees:
Stable (breaking changes require a major version bump):
- The
Versionclass constructor, operators, properties, and methods (raw,ecosystem,epoch,build,major/minor/patch,is_prerelease,is_postrelease,is_stable,count,segments(),release(),compare(),to_dict(),from_dict(),sort_key(),parse(),try_parse()). - Module-level functions:
version,compare,compare_semver_strict,sort_versions,batch_compare,gt/ge/gte/lt/le/lte/eq/ne,max_version,min_version,stable_versions,latest_stable,satisfies,bump_major/bump_minor/bump_patch/bump_prerelease,next_stable. - The set of accepted ecosystem names (aliases may grow, but existing names keep their meaning).
- The comparison result (
-1/0/1) for any pair of versions within a given ecosystem, within a major version. - Type stubs (
.pyi) and thepy.typedmarker. - Pickle format and
__reduce__output (stable across patch versions).
Experimental (may change within minor versions until documented stable):
Version.sort_key()tuple shape (the encoding scheme can evolve if new ecosystems require additional distinguishing fields).- The
ecosystem="auto"detection heuristic. The rules may be refined to reduce false positives; the existing correct detections will not flip. - Error messages and exception attribute text.
Not covered by SemVer:
- Rust internals (
src/), benchmarks, CI configuration. - Performance numbers (we aim to improve them monotonically, but there are no guarantees tied to the public version).
See CHANGELOG.md for release history.
Aleksandr Pavlov [email protected]
MIT — see LICENSE for details.