Skip to content

Releases: posit-dev/go-python-packaging

v0.3.1 — no method on a zero-value Version panics

Choose a tag to compare

@jonyoder jonyoder released this 07 Aug 18:49
448c491

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:

  1. String and BaseVersion indexed release[0] on a nil slice.
  2. real.Compare(Version{}) dereferenced nil even after (1): a zero comparison key holds nil Part interfaces and Parts.IsAny ranges 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.
  3. Compare padded 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.

⚠️ Worth knowing generally: 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

Choose a tag to compare

@jonyoder jonyoder released this 07 Aug 16:52
dd1f024

See CHANGELOG.md for the full entry.

Breaking

  • A comma is required between adjacent version specifiers. foo (>=1.0<2.0) and foo>=1.0<2.0 no longer parse. Previously they did, and re-rendered with a comma the input never contained, turning urllib3 (>=1.26<2.0) into urllib3>=1.26,<2.0 and 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 Specifiers can reach this; no input string produces one.
  • An all-digit local version segment normalizes as an integer, so 1.0+007 renders as 1.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

Choose a tag to compare

@jonyoder jonyoder released this 03 Aug 16:22
3dfc5dc

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 changes String() output, equality, and ordering. Previously a label was compared as a single alphabetic segment, so 1.0+ubuntu-2 sorted above 1.0+ubuntu-10; it now sorts below, matching upstream.
  • Arbitrary equality (===) accepts exactly one token. === lolwat is valid; ===any string here is 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 \s omits \v where 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 regexp is 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

Choose a tag to compare

@jonyoder jonyoder released this 23 Jul 13:20
e5a9dc4

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.
  • LGPLv3 classifier now maps to LGPL-3.0-only (was LGPL-3.0-or-later); the classifier denotes exactly v3.
  • Versionless GPL/LGPL classifiers 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

Choose a tag to compare

@jonyoder jonyoder released this 23 Jul 11:31
b9bf321

First tagged release of go-python-packaging — Go primitives for Python packaging metadata (RFD 0001).

Packages:

  • version — PEP 440 versions + specifiers
  • requirement / marker / extras — PEP 508 dependency specifiers, environment markers, PEP 685 extras
  • reqtxt — pip requirements.txt parsing
  • distribution / wheelname / tags — METADATA/wheel parsing, PEP 425/600/656 compatibility tags
  • license — 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.