Versioning — version.py¶
Semantic-version parsing, precedence, and constraint matching. Pure module (no I/O), the reference example for the project's testability rule.
- Source: src/hdlpkg/version.py
- Import:
from hdlpkg import Version, VersionConstraint, OpaqueVersion, CalVer, MonotonicVersion, compatibility_group, parse_version
Purpose¶
Every IP core is identified partly by a SemVer 2.0.0 version, and dependencies are expressed as constraints over versions. This module is the single place that knows how to parse versions, order them, and decide whether a version satisfies a constraint.
Version¶
A parsed, immutable, hashable, totally-ordered semantic version.
| Member | Description |
|---|---|
Version.parse(text) -> Version |
Parse MAJOR.MINOR.PATCH[-prerelease][+build]. Raises InvalidVersionError. |
major, minor, patch |
The numeric core (ints). |
prerelease, build |
Dot-split identifier tuples (e.g. ("rc", "1")). |
is_prerelease |
True if a pre-release tag is present. |
core |
The (major, minor, patch) triple. |
str(v) |
Round-trips: Version.parse(str(v)) == v. |
Ordering follows SemVer precedence (§11): the core triple compares first, then
pre-release identifiers (numeric < alphanumeric, fewer fields < more); a normal
version outranks any pre-release of the same core. Build metadata is ignored for
both equality and ordering (§10), so 1.0.0+a == 1.0.0+b. Because ordering is a
genuine total order, sorted(), min(), and max() all work on versions.
VersionConstraint¶
A parsed constraint that a Version may or may not satisfy. Build via
VersionConstraint.parse(text) (raises InvalidConstraintError).
| Grammar | Meaning |
|---|---|
=1.2.3 / ==1.2.3 |
exactly 1.2.3 |
>1.2.3, >=1.2.3, <2.0.0, <=1.9.9 |
inequality bounds |
^1.2.3 |
compatible release: >=1.2.3, <2.0.0 (for 0.x: ^0.2.3 = >=0.2.3, <0.3.0) |
~1.2.3 |
patch-level: >=1.2.3, <1.3.0 |
1.2.3 (bare, no operator) |
caret — same as ^1.2.3 (Cargo/npm convention) |
a, b (comma) |
logical AND of clauses (e.g. >=1.2.0,<2.0.0) |
*, any, or empty |
any stable version |
constraint.matches(version) -> bool tests satisfaction. str(constraint) returns
the original text. constraint.is_exact / exact_version / pinned_token expose a
single = pin (SemVer or opaque) for the resolver.
Pre-release rule (the Cargo rule): a constraint built from a stable operand
never matches a pre-release version, so ^1.0.0 will not pull in 2.0.0-alpha
or even 1.5.0-rc.1. A pre-release candidate is allowed only when some clause's
operand is itself a pre-release of the same MAJOR.MINOR.PATCH (e.g. >=1.4.0-rc.1
admits 1.4.0-rc.2).
Compatibility groups — compatibility_group(version, scheme)¶
The resolver groups a package's versions into compatibility groups: two versions in
the same group are interchangeable (it unifies them), versions in different groups are
incompatible (they may coexist under a conflict policy). For SemVer
this is the Cargo rule — the major for major >= 1, the minor for 0.y, the
patch for 0.0.z. For the opaque scheme every distinct version is its own group.
Version schemes¶
A core declares a version scheme via [package].scheme; each maps
to a version type, and parse_version(text, scheme) is the dispatcher:
scheme |
Type | Ordering | Constraints |
|---|---|---|---|
semver (default) |
Version |
full SemVer 2.0.0 | ^/~/ranges; major-grouped |
calver |
CalVer |
numeric components | ^2024.1 = >=2024.1, <2025 (year-as-major); ~, ranges |
monotonic |
MonotonicVersion |
by revision integer | ^r3 = >=r3 (one shared group); ~r3/=r3 pin |
opaque |
OpaqueVersion |
none (lexical for output) | exact pin only (=D5020100) |
CalVer— a tuple of integercomponents(2024.1->(2024, 1)) plus the raw literal; ordered component-wise. The first component (year) is the compatibility boundary, so caret/tilde andcompatibility_groupare year-based.MonotonicVersion— a single integerrevisionwith an optional letter prefix (r3); ordered by the integer, all in one compatibility group.OpaqueVersion— an uninterpreted token; no precedence, so dependents must pin it exactly.
Bare constraints for non-SemVer schemes mean exact (those schemes lack SemVer's
caret default) — use ^ / ~ / ranges explicitly for flexibility. Because the
dependency's scheme is unknown when a constraint string is parsed, an explicit-operator
non-SemVer constraint is stored as deferred clauses (constraint.ordered) and
interpreted against the candidate's type at match time. AnyVersion = Version |
OpaqueVersion | CalVer | MonotonicVersion is the union a Vlnv may
carry, and every non-SemVer version round-trips through the lockfile
via a scheme marker. (Still open: an ordered scheme with a richer precedence,
e.g. git describe tags.)
Errors¶
InvalidVersionError and InvalidConstraintError (both subclasses of
HdlPackagerError and ValueError). See exceptions.
Example¶
from hdlpkg import Version, VersionConstraint
v = Version.parse("1.4.0-rc.1")
assert v.is_prerelease and v.core == (1, 4, 0)
caret = VersionConstraint.parse("^1.2.0")
assert caret.matches(Version.parse("1.9.0"))
assert not caret.matches(Version.parse("2.0.0"))
assert not caret.matches(Version.parse("2.0.0-alpha")) # pre-release rule
assert sorted([Version.parse(s) for s in ("1.0.0", "1.0.0-rc.1", "0.9.0")]) == [
Version.parse("0.9.0"),
Version.parse("1.0.0-rc.1"),
Version.parse("1.0.0"),
]