What changed in packaging from 25 to 26

4 releases numbered after 25.0 up to and including 26.3, stable releases only. 25.0 and 26.3 are the newest stable releases of 25 and 26 we track; this page follows them as new ones ship.

69 changes across 4 releases

Added 25

26.3

  • Add a public VersionRange API and SpecifierSet.to_range(), representing the versions a specifier set accepts as an interval set that supports intersection, union, difference, complement, set relations, membership tests, and filtering
  • Add VersionRange.to_specifier_set() to convert a range back to a SpecifierSet where a PEP 440 form exists
  • Accept Metadata-Version: 2.6 per PEP 808
  • Add a limit argument to parse_tag() for compressed tag sets
  • Add a prefer_sdist_predicate argument to Pylock.select() to prefer source distributions over wheels for selected packages
  • Add pure_python_tags() to generate the pure-Python tags for a Python version without touching the running platform
  • Add SpecifierSet.is_subset(), SpecifierSet.is_superset(), and SpecifierSet.is_disjoint() to compare the versions two specifier sets accept

26.1

  • Add handling for Emscripten wheel tags per PEP 783
  • Add handling for the abi3.abi3t free-threading tag per PEP 803
  • Add packaging.dependency_groups module based on PEP 723
  • Add the packaging.direct_url module
  • Add the packaging.errors module
  • Add SpecifierSet.is_unsatisfiable using ranges
  • Add create_compatible_tags_selector to select compatible tags
  • Add a key argument to SpecifierSet.filter()
  • Support & and | operators for Marker objects
  • Add Version.from_parts and normalize Version.__replace__
  • Add an option to validate compressed tag set sort order in parse_wheel_filename
  • Add pylock select function for PEP 751
  • Add filename property to PackageSdist and PackageWheel

26.0

  • Support PEP 751 for pylock
  • Support PEP 794 import name metadata
  • Support writing metadata
  • Support __replace__ for Version
  • Support positional pattern matching for Version and Specifier
Changed 12

26.3

  • Prefer native linux_* platform tags over manylinux and musllinux tags on Linux

26.1

  • Narrow exclusion of pre-releases for <V.postN to match spec
  • Narrow exclusion of post-releases for >V to match spec
  • Rename format_full_version to _format_full_version to make it visibly private
  • Restrict local version to ASCII
  • Give preference to path over url in pylock

26.0

  • Adjust PEP 440 handling of prereleases for Specifier.contains, SpecifierSet.contains, and SpecifierSet.filter
  • Handle PEP 440 edge case in SpecifierSet.filter
  • Adjust arbitrary equality intersection preservation in SpecifierSet
  • Return False instead of raising for .contains with invalid version
  • Support arbitrary equality on arbitrary strings for Specifier and SpecifierSet's filter and contains methods
  • Only try to parse as Version on certain marker keys, return False on unequal ordered comparisons
Fixed 31

26.3

  • Raise InvalidVersion instead of TypeError when Version is given a non-string
  • Raise InvalidVersion for non-string pre-release letters passed to Version.from_parts
  • Fix an AttributeError when hashing internally trimmed versions
  • Fix SpecifierSet.is_unsatisfiable for post-release boundary intersections
  • Make Requirement.__hash__ consistent with __eq__ for trailing-zero-equivalent specifiers so equal requirements hash equal and deduplicate in sets and dicts
  • Normalize requested extra names before comparing or hashing requirements
  • Preserve a Requirement's specifier prereleases override across a pickle round trip
  • Raise InvalidRequirement instead of InvalidSpecifier when a requirement contains an invalid specifier
  • Clarify the error for post-release prefix wildcards like ==1.0.post1.*
  • Preserve quoting semantics when serializing marker values so round-tripped markers parse back to the same marker
  • Keep the parentheses of a nested group when serializing markers
  • Normalize extra and dependency_groups values in nested markers at parse time

26.2

  • Fix incorrect sysconfig var name for pyemscripten
  • Make Version, Specifier, SpecifierSet, Tag, Marker, and Requirement pickle-safe and backward-compatible with pickles created in 25.0-26.1 including references to the removed packaging._structures module
  • Re-export ExceptionGroup for now

26.1

  • Fix > comparison for versions with dev+local segments
  • Fix incorrect self-comparison for InfinityType and NegativeInfinityType
  • Canonicalize when deduplicating specifiers in SpecifierSet
  • Fix charset error message formatting
  • Handle the key parameter in SpecifierSet.filter when specifiers are empty and prerelease is False
  • Specifier === uses original string, not normalized, when available
  • Propagate int-max-str-digits ValueError

26.0

  • Update _hash when unpickling Tag()
  • Correct comment and simplify implicit prerelease handling in Specifier.prereleases
  • Use explicit _GLibCVersion NamedTuple in _manylinux
  • Detect invalid license expressions containing ()
  • Correct regex for metadata 'name' format
  • Improve the message around expecting a semicolon
  • Support nested parens in license expressions
  • Add space before at symbol in Requirements string
  • Use packaging logger instead of root logger
Removed 1

26.3

  • Drop support for Python 3.8; packaging now requires Python 3.9 or later

Original release notes, newest first

The list above is our reading of these notes; the originals from packaging are here, one fold per release.

26.3
What's Changed
Features
  • Add a public VersionRange API and SpecifierSet.to_range(), representing the versions a specifier set accepts as an interval set that supports intersection, union, difference, complement, set relations, membership tests, and filtering. VersionRange.to_specifier_set() converts a range back to a SpecifierSet where a PEP 440 form exists. (#1267, #1270, #1298)
  • PEP 808: accept Metadata-Version: 2.6. (#1194)
  • Add a limit argument to parse_tag() for compressed tag sets. (#1220)
  • Add a prefer_sdist_predicate argument to Pylock.select() to prefer source distributions over wheels for selected packages. (#1334)
  • Add pure_python_tags() to generate the pure-Python tags for a Python version without touching the running platform. (#1346)
  • Add SpecifierSet.is_subset(), SpecifierSet.is_superset(), and SpecifierSet.is_disjoint(), which compare the versions two specifier sets accept. (#1313)
Behavior adaptations
  • Drop support for Python 3.8; packaging now requires Python 3.9 or later. (#1157)
  • Prefer native linux_* platform tags over manylinux and musllinux tags on Linux. (#160)
Fixes for versions and specifiers
  • Raise InvalidVersion instead of TypeError when Version is given a non-string. (#1319)
  • Raise InvalidVersion for non-string pre-release letters passed to Version.from_parts. (#1241)
  • Fix an AttributeError when hashing internally trimmed versions. (#1242)
  • Fix SpecifierSet.is_unsatisfiable for post-release boundary intersections. (#1257)
Fixes for requirements and markers
  • Make Requirement.__hash__ consistent with __eq__ for trailing-zero-equivalent specifiers (e.g. foo==1.0.0 and foo==1.0.0.0), so equal requirements hash equal and deduplicate in sets and dicts. (#1232)
  • Normalize requested extra names before comparing or hashing requirements. (#644)
  • Preserve a Requirement's specifier prereleases override across a pickle round trip. (#1204)
  • Raise InvalidRequirement instead of InvalidSpecifier when a requirement contains an invalid specifier. (#1332)
  • Clarify the error for post-release prefix wildcards like ==1.0.post1.*. (#1299)
  • Preserve quoting semantics when serializing marker values, so round-tripped markers parse back to the same marker. (#1213)
  • Keep the parentheses of a nested group when serializing markers. (#1316)
  • Normalize extra and dependency_groups values in nested markers at parse time. (#1246, #1310)
  • Raise UndefinedComparison when a set-valued variable like extras is used outside the membership form. (#1265)
  • Raise UndefinedEnvironmentName (a KeyError subclass) for missing environment keys during marker evaluation. (#1276)
  • Wrap malformed string literal errors in InvalidMarker / InvalidRequirement instead of leaking a low-level error. (#1249)
  • Reject requirements and markers with a trailing line break. (#1345)
Fixes for metadata and licenses
  • Collect all from_email validation errors into one ExceptionGroup instead of raising the first. (#1268)
  • Accept the UTF-8 charset case-insensitively in email payloads. (#1330)
  • Reject malformed Description-Content-Type values. (#1329)
  • Don't rewrite user values that contain {field} placeholders in error messages. (#1327)
  • Route multipart email payloads to unparsed instead of asserting. (#1247)
  • Make InvalidMetadata and CyclicDependencyGroup picklable. (#1328)
  • Fold every line boundary str.splitlines recognizes when writing a header with RFC822Message. (#1356)
  • Raise InvalidLicenseExpression for misplaced WITH clauses and empty LicenseRef- names. (#1266)
  • Raise InvalidLicenseExpression instead of KeyError for a LicenseRef- with a + suffix. (#1219)
Fixes for tags and filenames
  • Raise InvalidTag from parse_tag() for tags with the wrong number of components. (#1238)
  • Reject empty tag components in parse_wheel_filename() and parse_tag(). (#1234)
  • Reject an empty project name in the wheel and sdist filename parsers. (#1305)
  • Reject wheel filenames with a trailing newline. (#1341)
  • Reject wheel tags whose interpreter component is not an identifier. (#577)
  • is_normalized_name now rejects names with collapsed double hyphens like a--b. (#1230)
  • Fix duplicate explicit abi3t tags. (#1245)
  • Forward the warn argument to generic_tags() in sys_tags(). (#1264)
  • Raise SystemError for an empty or malformed CPython EXT_SUFFIX. (#1271, #1301)
  • Fix a typo in the macOS fat3 architecture name (was fat32). (#1199)
Fixes for pylock, direct URLs, and dependency groups
  • Percent-decode pylock artifact file names derived from a url so that local versions (e.g. a wheel with 2.12.1+cu130 encoded as 2.12.1%2Bcu130) yield a valid file name. (#1314)
  • Use an explicitly empty tags sequence in Pylock.select() instead of falling back to sys_tags(). (#1349)
  • Fix Pylock.select() on Python builds that report a non-PEP 440 python_full_version (e.g. 3.15.0+). (#1179)
  • Reject TOML booleans where integers are expected in pylock files. (#1244)
  • Add PylockSelectError to packaging.pylock.__all__. (#1202)
  • Fix DirectUrl credential stripping for passwords containing @. (#1218)
  • Parse the URL scheme case-insensitively when checking for file URLs in direct_url. (#1240)
  • Require absolute file URLs for local directories in direct_url. (#1297)
  • Collect InvalidRequirement errors while resolving dependency groups instead of leaking them. (#1302)
  • Don't cache malformed dependency group parses. (#1248)
Performance
  • Implement Specifier and SpecifierSet filtering with the new range engine. (#1120, #1259)
  • Cache the default marker environment. (#1250)
  • Cache the _manylinux module lookup process-wide. (#1254)
  • Add __slots__ to Requirement and the token classes. (#1320, #1258)
  • Keep range caches across canonicalization, precompile the wheel project-name pattern, simplify parse_tag(), and skip platform.mac_ver() when the version and arch are given. (#1253, #1256, #1236, #1255)
Documentation
Internal
  • Add Python 3.15 to the test matrix. (#1190)
  • Add a musl/Alpine test job and make the test suite pass on musl. (#1226, #1227)
  • Expand the downstream test matrix by ten projects. (#1261)
  • Update to mypy 2. (#1191)
  • Use nox's uv integration. (#1057)
New Contributors

Full Changelog: https://github.com/pypa/packaging/compare/26.2...26.3

View originalPermalink

26.2
What's Changed

Fixes:

Documentation:

Internal:

New Contributors

Full Changelog: https://github.com/pypa/packaging/compare/26.1...26.2

View originalPermalink

26.1

Features:

Behavior adaptations:

Pylock (PEP 751) updates:

Fixes:

Performance:

Internal:

Documentation:

Benchmarks

Performance improvements since 26.0, from the new integrated benchmark suite:

uv run asv run 26.0..HEAD
New Contributors

Full Changelog: https://github.com/pypa/packaging/compare/26.0...26.1

View originalPermalink

26.0

Read about the performance improvements here: https://iscinumpy.dev/post/packaging-faster.

What's Changed

Features:

Behavior adaptations:

Fixes:

Performance:

Type annotations:

Internal:

New Contributors
Since last RC

Fixes:

Performance:

Documentaiton:

Full Changelog: https://github.com/pypa/packaging/compare/26.0rc3...26.0

View originalPermalink