fix: parse arbitrary equality python requirements (#4835)

Signed-off-by: cyphercodes <cyphercodes@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
This commit is contained in:
Rayan Salhab 2026-05-05 16:49:03 +03:00 committed by GitHub
parent f878197150
commit ae711963d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 6 deletions

View File

@ -170,8 +170,8 @@ func (rp requirementsParser) parseRequirementsTxt(ctx context.Context, _ file.Re
} }
func parseVersion(version string, guessFromConstraint bool) string { func parseVersion(version string, guessFromConstraint bool) string {
if isPinnedConstraint(version) { if version := parsePinnedVersion(version); version != "" {
return strings.TrimSpace(strings.ReplaceAll(version, "==", "")) return version
} }
if guessFromConstraint { if guessFromConstraint {
@ -181,15 +181,26 @@ func parseVersion(version string, guessFromConstraint bool) string {
return "" return ""
} }
func isPinnedConstraint(version string) bool { func parsePinnedVersion(version string) string {
return strings.Contains(version, "==") && !strings.ContainsAny(version, "*,<>!") version = strings.TrimSpace(version)
if strings.ContainsAny(version, "*,<>!") {
return ""
}
for _, operator := range []string{"===", "=="} {
if strings.HasPrefix(version, operator) && !strings.HasPrefix(version, operator+"=") {
return strings.TrimSpace(strings.TrimPrefix(version, operator))
}
}
return ""
} }
func guessVersion(constraint string) string { func guessVersion(constraint string) string {
// handle "2.8.*" -> "2.8.0" // handle "2.8.*" -> "2.8.0"
constraint = strings.ReplaceAll(constraint, "*", "0") constraint = strings.ReplaceAll(constraint, "*", "0")
if isPinnedConstraint(constraint) { if version := parsePinnedVersion(constraint); version != "" {
return strings.TrimSpace(strings.ReplaceAll(constraint, "==", "")) return version
} }
constraints := strings.Split(constraint, ",") constraints := strings.Split(constraint, ",")

View File

@ -29,6 +29,18 @@ func TestParseRequirementsTxt(t *testing.T) {
VersionConstraint: "== 4.0.0", VersionConstraint: "== 4.0.0",
}, },
}, },
{
Name: "urllib3",
Version: "1.26.20",
PURL: "pkg:pypi/urllib3@1.26.20",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonRequirementsEntry{
Name: "urllib3",
VersionConstraint: "===1.26.20",
},
},
{ {
Name: "foo", Name: "foo",
Version: "1.0.0", Version: "1.0.0",
@ -294,6 +306,14 @@ func Test_newRequirement(t *testing.T) {
VersionConstraint: "==2.8", VersionConstraint: "==2.8",
}, },
}, },
{
name: "arbitrary equality",
raw: "urllib3===1.26.20",
want: &unprocessedRequirement{
Name: "urllib3",
VersionConstraint: "===1.26.20",
},
},
{ {
name: "comment + constraint", name: "comment + constraint",
raw: "Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*", raw: "Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*",
@ -363,6 +383,11 @@ func Test_parseVersion(t *testing.T) {
version: " == 1.0.0 ", version: " == 1.0.0 ",
want: "1.0.0", want: "1.0.0",
}, },
{
name: "arbitrary equality constraint",
version: " === 1.26.20 ",
want: "1.26.20",
},
{ {
name: "resolve lowest, simple constraint", name: "resolve lowest, simple constraint",
version: " >= 1.0.0 ", version: " >= 1.0.0 ",

View File

@ -1,4 +1,5 @@
flask == 4.0.0 flask == 4.0.0
urllib3===1.26.20
# a line that is ignored # a line that is ignored
sqlalchemy >= 1.0.0, <= 2.0.0, != 3.0.0, <= 3.0.0 sqlalchemy >= 1.0.0, <= 2.0.0, != 3.0.0, <= 3.0.0
foo == 1.0.0 # a comment that needs to be ignored foo == 1.0.0 # a comment that needs to be ignored