From 4871a04875ad5dfb4e97df856ac33726a315e9bb Mon Sep 17 00:00:00 2001 From: nodivbyzero Date: Tue, 11 Aug 2026 16:40:59 -0700 Subject: [PATCH 1/2] feat: add support for Python metadata version 2.5 --- internal/distributions/distribution.go | 3 + internal/distributions/distribution_test.go | 163 ++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 internal/distributions/distribution_test.go diff --git a/internal/distributions/distribution.go b/internal/distributions/distribution.go index 909c947..5e1a4cb 100644 --- a/internal/distributions/distribution.go +++ b/internal/distributions/distribution.go @@ -124,6 +124,8 @@ var HeaderAttrs2_4 = append(HeaderAttrs2_3, []HeaderAttr{ // PEP 639 {"License-File", "license_file", false}, }...) +var HeaderAttrs2_5 = HeaderAttrs2_4 // PEP 639 finalized + var HeaderAttrs = map[string][]HeaderAttr{ "1.0": HeaderAttrs1_0, "1.1": HeaderAttrs1_1, @@ -133,6 +135,7 @@ var HeaderAttrs = map[string][]HeaderAttr{ "2.2": HeaderAttrs2_2, "2.3": HeaderAttrs2_3, "2.4": HeaderAttrs2_4, + "2.5": HeaderAttrs2_5, } type Distribution interface { diff --git a/internal/distributions/distribution_test.go b/internal/distributions/distribution_test.go new file mode 100644 index 0000000..330a069 --- /dev/null +++ b/internal/distributions/distribution_test.go @@ -0,0 +1,163 @@ +package distributions + +import ( + "strings" + "testing" +) + +func TestHeaderAttrsVersions(t *testing.T) { + expectedVersions := []string{"1.0", "1.1", "1.2", "2.0", "2.1", "2.2", "2.3", "2.4", "2.5"} + + for _, version := range expectedVersions { + t.Run("version_"+version, func(t *testing.T) { + attrs, exists := HeaderAttrs[version] + if !exists { + t.Errorf("metadata version %s not found in HeaderAttrs map", version) + return + } + if len(attrs) == 0 { + t.Errorf("metadata version %s has no header attributes", version) + } + }) + } +} + +func TestGetHeaderAttrs(t *testing.T) { + tests := []struct { + version string + expectError bool + }{ + {"1.0", false}, + {"1.1", false}, + {"1.2", false}, + {"2.0", false}, + {"2.1", false}, + {"2.2", false}, + {"2.3", false}, + {"2.4", false}, + {"2.5", false}, + {"9.9", true}, + } + + for _, tt := range tests { + t.Run("version_"+tt.version, func(t *testing.T) { + bd := &BaseDistribution{MetadataVersion: tt.version} + attrs, err := bd.GetHeaderAttrs() + + if tt.expectError { + if err == nil { + t.Errorf("expected error for version %s, got nil", tt.version) + } + } else { + if err != nil { + t.Errorf("unexpected error for version %s: %v", tt.version, err) + } + if len(attrs) == 0 { + t.Errorf("expected non-empty attrs for version %s", tt.version) + } + } + }) + } +} + +func TestHeaderAttrs2_5HasLicenseExpression(t *testing.T) { + attrs := HeaderAttrs2_5 + found := false + for _, attr := range attrs { + if attr.HeaderName == "License-Expression" { + found = true + break + } + } + if !found { + t.Error("HeaderAttrs2_5 should include License-Expression header") + } +} + +func TestParseMetadataVersion2_5(t *testing.T) { + metadata := `Metadata-Version: 2.5 +Name: test-package +Version: 1.0.0 +Summary: A test package +License-Expression: MIT + +` + bd := &BaseDistribution{} + err := bd.Parse([]byte(metadata)) + if err != nil { + t.Fatalf("failed to parse metadata version 2.5: %v", err) + } + + if bd.MetadataVersion != "2.5" { + t.Errorf("expected metadata version 2.5, got %s", bd.MetadataVersion) + } + if bd.Name != "test-package" { + t.Errorf("expected name test-package, got %s", bd.Name) + } + if bd.Version != "1.0.0" { + t.Errorf("expected version 1.0.0, got %s", bd.Version) + } + if bd.LicenseExpression != "MIT" { + t.Errorf("expected license expression MIT, got %s", bd.LicenseExpression) + } +} + +func TestParseMetadataWithDescription(t *testing.T) { + metadata := `Metadata-Version: 2.5 +Name: test-package +Version: 1.0.0 + +This is the description body. +It can have multiple lines.` + + bd := &BaseDistribution{} + err := bd.Parse([]byte(metadata)) + if err != nil { + t.Fatalf("failed to parse metadata: %v", err) + } + + if !strings.Contains(bd.Description, "This is the description body") { + t.Errorf("description not parsed correctly: %s", bd.Description) + } +} + +func TestHeaderAttrsInheritance(t *testing.T) { + hasAttr := func(attrs []HeaderAttr, name string) bool { + for _, a := range attrs { + if a.HeaderName == name { + return true + } + } + return false + } + + t.Run("2.1_has_provides_extra", func(t *testing.T) { + if !hasAttr(HeaderAttrs2_1, "Provides-Extra") { + t.Error("2.1 should have Provides-Extra") + } + }) + + t.Run("2.2_has_dynamic", func(t *testing.T) { + if !hasAttr(HeaderAttrs2_2, "Dynamic") { + t.Error("2.2 should have Dynamic") + } + }) + + t.Run("2.4_has_license_expression", func(t *testing.T) { + if !hasAttr(HeaderAttrs2_4, "License-Expression") { + t.Error("2.4 should have License-Expression") + } + }) + + t.Run("2.5_inherits_from_2.4", func(t *testing.T) { + if !hasAttr(HeaderAttrs2_5, "License-Expression") { + t.Error("2.5 should inherit License-Expression from 2.4") + } + if !hasAttr(HeaderAttrs2_5, "Dynamic") { + t.Error("2.5 should inherit Dynamic from 2.2") + } + if !hasAttr(HeaderAttrs2_5, "Provides-Extra") { + t.Error("2.5 should inherit Provides-Extra from 2.1") + } + }) +} From 47c6b29622d4c681b3c46532a01d12333162cd8a Mon Sep 17 00:00:00 2001 From: nodivbyzero Date: Tue, 11 Aug 2026 16:49:37 -0700 Subject: [PATCH 2/2] update hatch snapshots for metadata version 2.5 --- .snapshots/TestParse-hatch_signed_tarball | 2 +- .snapshots/TestParse-hatch_signed_wheel | 2 +- .snapshots/TestParse-hatch_unsigned_tarball | 2 +- .snapshots/TestParse-hatch_unsigned_wheel | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.snapshots/TestParse-hatch_signed_tarball b/.snapshots/TestParse-hatch_signed_tarball index cde9711..519e4b1 100644 --- a/.snapshots/TestParse-hatch_signed_tarball +++ b/.snapshots/TestParse-hatch_signed_tarball @@ -44,7 +44,7 @@ (string) (len=11) "LICENSE.txt" }, (string) (len=16) "metadata_version": ([]string) (len=1) { - (string) (len=3) "2.4" + (string) (len=3) "2.5" }, (string) (len=4) "name": ([]string) (len=1) { (string) (len=5) "hatch" diff --git a/.snapshots/TestParse-hatch_signed_wheel b/.snapshots/TestParse-hatch_signed_wheel index 4bd5572..1f9e8e6 100644 --- a/.snapshots/TestParse-hatch_signed_wheel +++ b/.snapshots/TestParse-hatch_signed_wheel @@ -44,7 +44,7 @@ (string) (len=11) "LICENSE.txt" }, (string) (len=16) "metadata_version": ([]string) (len=1) { - (string) (len=3) "2.4" + (string) (len=3) "2.5" }, (string) (len=4) "name": ([]string) (len=1) { (string) (len=5) "hatch" diff --git a/.snapshots/TestParse-hatch_unsigned_tarball b/.snapshots/TestParse-hatch_unsigned_tarball index 41e6e6a..f08a6d1 100644 --- a/.snapshots/TestParse-hatch_unsigned_tarball +++ b/.snapshots/TestParse-hatch_unsigned_tarball @@ -44,7 +44,7 @@ (string) (len=11) "LICENSE.txt" }, (string) (len=16) "metadata_version": ([]string) (len=1) { - (string) (len=3) "2.4" + (string) (len=3) "2.5" }, (string) (len=4) "name": ([]string) (len=1) { (string) (len=5) "hatch" diff --git a/.snapshots/TestParse-hatch_unsigned_wheel b/.snapshots/TestParse-hatch_unsigned_wheel index d41d46c..755c6cd 100644 --- a/.snapshots/TestParse-hatch_unsigned_wheel +++ b/.snapshots/TestParse-hatch_unsigned_wheel @@ -44,7 +44,7 @@ (string) (len=11) "LICENSE.txt" }, (string) (len=16) "metadata_version": ([]string) (len=1) { - (string) (len=3) "2.4" + (string) (len=3) "2.5" }, (string) (len=4) "name": ([]string) (len=1) { (string) (len=5) "hatch"