Releases: posit-dev/go-python-packaging
Release list
v0.3.1 — no method on a zero-value Version panics
Eight of the thirteen exported methods on Version panicked on a zero value — including all six comparison methods, since Compare uses String as its equality fast path. A zero Version now renders as "", sorts below every parsed version, and two zero values compare equal.
Three separate defects, each verified load-bearing by reverting it alone:
StringandBaseVersionindexedrelease[0]on a nil slice.real.Compare(Version{})dereferenced nil even after (1): a zero comparison key holds nilPartinterfaces andParts.IsAnyranges over them without a nil check. Asymmetric —Version{}.Compare(real)returned an answer while the reverse crashed, which is what showed this was a second, distinct defect.Comparepadded the second release segment to its own length rather than the longer of the two. Latent only: parsed-version behavior was already correct, verified across nine pairs with differing segment counts in both directions.
Fixed, not Breaking. Every changed path previously panicked, so no caller can depend on it. Parsed-version behavior is unchanged.
fmt recovers a panic raised inside a String method and substitutes %!s(PANIC=...). This class of bug is therefore invisible wherever the value is formatted into a message, and fatal only where String() is called directly. That is why rstudio/package-manager#19466's F14 identified the wrong function.
v0.3.0 — conformance completion and attribution correction
See CHANGELOG.md for the full entry.
Breaking
- A comma is required between adjacent version specifiers.
foo (>=1.0<2.0)andfoo>=1.0<2.0no longer parse. Previously they did, and re-rendered with a comma the input never contained, turningurllib3 (>=1.26<2.0)intourllib3>=1.26,<2.0and fabricating a constraint boundary from malformed input. 756 of 2,804,135 distinct requirement strings in a production PyPI snapshot were affected. Arbitrary equality (===) is unchanged. - An empty specifier set admits every version, where it previously matched none. PEP 508 makes a version specifier optional and an omitted one accepts any version. Only a deliberately constructed or zero-valued
Specifierscan reach this; no input string produces one. - An all-digit local version segment normalizes as an integer, so
1.0+007renders as1.0+7. Ordering is unaffected. What changes is the rendered string, and therefore===matching.
Notes
NOTICE now credits the Python pkginfo package and pypa/twine, whose material distribution/ ports and which were uncredited through v0.2.0; records that pypa/packaging is dual-licensed Apache-2.0 or two-clause BSD; and deletes a stale claim that no source from the cited projects was incorporated. Redistributors of v0.2.0 or earlier should take the NOTICE from this release. No code changed.
A note on v0.2.0
The three breaking changes above were documented under 0.2.0 until 2026-08-07, on the mistaken belief that its tag had not yet been cut. It had, on 2026-08-03. v0.2.0 does not contain them. The changelog is now accurate for both releases.
v0.2.0 — conformance release
Conformance release. requirement/, version/, and the PEP 508 grammar are now tested against tables ported from pypa/packaging's tests/test_requirements.py (upstream SHA 4eb0753), plus property-based tests adapted from its Hypothesis suite. The divergences those tests revealed are fixed.
This release changes behavior. It is a minor rather than a patch bump because a consumer pinning ^0.1 would otherwise pick up the narrowing and the ordering change silently. See CHANGELOG.md for the full list.
Breaking
Requirement.String()renders the marker separator as"; "when the requirement has no URL — it previously always rendered" ; ". The" ; "form is kept when a URL is present, since a bare;after a URL is ambiguous. Consumers that compare, hash, cache, or persist rendered requirement strings will see different bytes for the same input.- Local version labels normalize
-and_to., as PEP 440 requires. This changesString()output, equality, and ordering. Previously a label was compared as a single alphabetic segment, so1.0+ubuntu-2sorted above1.0+ubuntu-10; it now sorts below, matching upstream. - Arbitrary equality (
===) accepts exactly one token.=== lolwatis valid;===any string hereis now rejected. - A
.*prefix match is rejected on a post-release (e.g.==1.2.3.post4.*). - A trailing line break (
\n,\r,\r\n) is rejected. A trailing space or tab is still accepted. - Malformed quoted strings in environment markers are rejected: a trailing unpaired backslash and a truncated
\x. Valid Python escapes still parse.
Added
name()— an empty parenthesized specifier group — is valid, meaning "no version constraint".- A vertical tab is accepted as surrounding whitespace, matching upstream (Go's
\somits\vwhere Python's includes it). - Conformance tables and property tests ported from
pypa/packaging. The property-test dependency (pgregory.net/rapid, MPL-2.0) is test-only and does not enter this module's non-test import graph.
Fixed
- The PEP 440 operator alternation is built in a deterministic, longest-first order. It was previously assembled by ranging a Go map, so the compiled pattern varied per process — and because Go's
regexpis leftmost-first, an operator that is a prefix of another could win.
Note on comparing against pip install packaging
The trailing-line-break rule matches upstream's current source, not the released packaging 26.2. Upstream changed its end-of-input rule from $ to \Z after 26.2, and Python's $ matches before a trailing newline — so 26.2 still accepts "name>=1\n". A difference seen there is upstream's version skew, not a divergence here.
v0.1.1
License-derivation correctness fixes (#14):
- Rune-aware SPDX expression tokenizer (
utf8.DecodeRuneInString) — fixes misparsing of non-ASCII whitespace separators (e.g. NBSP). ASCII behavior unchanged. LGPLv3classifier now maps toLGPL-3.0-only(wasLGPL-3.0-or-later); the classifier denotes exactly v3.- Versionless
GPL/LGPLclassifiers no longer fabricate a version — they map to Unknown (SPDXNotIncluded).
These change derived license output for the affected classifiers (feeds license-compliance decisions in consumers).
v0.1.0
First tagged release of go-python-packaging — Go primitives for Python packaging metadata (RFD 0001).
Packages:
version— PEP 440 versions + specifiersrequirement/marker/extras— PEP 508 dependency specifiers, environment markers, PEP 685 extrasreqtxt— piprequirements.txtparsingdistribution/wheelname/tags— METADATA/wheel parsing, PEP 425/600/656 compatibility tagslicense— PyPI license derivation (Names/Types+ SPDX/classifier helpers), consumed by the PyPI RSF producer (rstudio/package-manager#19135)
Dual-licensed Apache-2.0 OR MIT.