fix: allow more PEP440-compliant characters in python versions (#4964)

Signed-off-by: Keith Zantow <kzantow@gmail.com>
This commit is contained in:
Keith Zantow 2026-07-13 15:10:12 -04:00 committed by GitHub
parent da77ea388a
commit 76ede661db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 142 additions and 2 deletions

View File

@ -29,8 +29,13 @@ const (
// namePattern matches: requests[security]
namePattern = `(?P<name>\w[\w\[\],\s-_\.]+)`
// versionConstraintPattern matches: == 2.8.* (including local version identifiers, e.g. == 1.2.3+gcr.2)
versionConstraintPattern = `(?P<versionConstraint>([^\S\r\n]*[~=>!<]+\s*[0-9a-zA-Z.*+]+[^\S\r\n]*,?)+)?(@[^\S\r\n]*(?P<url>[^;]*))?`
// versionConstraintPattern matches: == 2.8.*
// the version token accepts every character PEP 440 versions can contain: digits and letters,
// '.' (release/segment separator), '!' (epoch), '+' (local version), '*' (wildcard), and the
// alternate segment separators '-' and '_'. See:
// https://packaging.python.org/en/latest/specifications/version-specifiers/#version-scheme
// 1!2.0 1.0rc1 1.0-alpha-1 1.0_beta_2 1.0.post1 1.0.dev1 1.2.3+gcr.2 2.8.*
versionConstraintPattern = `(?P<versionConstraint>([^\S\r\n]*[~=>!<]+\s*[0-9a-zA-Z.*+!_-]+[^\S\r\n]*,?)+)?(@[^\S\r\n]*(?P<url>[^;]*))?`
// markersPattern matches: python_version < "2.7" and sys_platform == "linux"
markersPattern = `(;(?P<markers>.*))?`

View File

@ -386,6 +386,111 @@ func Test_newRequirement(t *testing.T) {
Markers: "sys_platform == 'linux'",
},
},
{
name: "epoch",
raw: "pkg == 1!2.0.0",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1!2.0.0",
},
},
{
name: "pre-release",
raw: "pkg == 1.0rc1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0rc1",
},
},
{
name: "pre-release with dash separators",
raw: "pkg == 1.0-alpha-1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0-alpha-1",
},
},
{
name: "pre-release with underscore separators",
raw: "pkg == 1.0_beta_2",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0_beta_2",
},
},
{
name: "post-release",
raw: "pkg == 1.0.post1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0.post1",
},
},
{
name: "implicit post-release",
raw: "pkg == 1.0-1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0-1",
},
},
{
name: "dev-release",
raw: "pkg == 1.0.dev1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0.dev1",
},
},
{
name: "local version with dash separator",
raw: "pkg == 1.0+ubuntu-1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0+ubuntu-1",
},
},
{
name: "local version with underscore separator",
raw: "pkg == 1.0+ubuntu_1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1.0+ubuntu_1",
},
},
{
name: "all segments combined",
raw: "pkg == 1!1.0a1.post2.dev3+local.1",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1!1.0a1.post2.dev3+local.1",
},
},
{
name: "release wildcard",
raw: "pkg == 2.*",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 2.*",
},
},
{
name: "epoch with markers and hashes",
raw: "pkg == 1!2.0 ; python_version < '3.8' --hash=sha256:abc123",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: "== 1!2.0",
Markers: "python_version < '3.8' --hash=sha256:abc123",
},
},
{
name: "compound constraint with exclusion is unaffected",
raw: "pkg >= 1.0.0, != 1.1.0, < 2.0.0",
want: &unprocessedRequirement{
Name: "pkg",
VersionConstraint: ">= 1.0.0, != 1.1.0, < 2.0.0",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -427,6 +532,36 @@ func Test_parseVersion(t *testing.T) {
version: " === 1.2.3+ubuntu1 ",
want: "1.2.3+ubuntu1",
},
{
name: "pre-release",
version: "== 1.0rc1",
want: "1.0rc1",
},
{
name: "pre-release with dash separators",
version: "== 1.0-alpha-1",
want: "1.0-alpha-1",
},
{
name: "post-release",
version: "== 1.0.post1",
want: "1.0.post1",
},
{
name: "dev-release",
version: "== 1.0.dev1",
want: "1.0.dev1",
},
{
name: "local version with dash separator",
version: "== 1.0+ubuntu-1",
want: "1.0+ubuntu-1",
},
{
name: "all segments combined",
version: "== 1.0a1.post2.dev3+local.1",
want: "1.0a1.post2.dev3+local.1",
},
{
name: "resolve lowest, simple constraint",
version: " >= 1.0.0 ",