Skip to content

Fix PyPiVersionRange working with * and ~= - #126

Closed
GermanMT wants to merge 1 commit into
aboutcode-org:mainfrom
GermanMT:main
Closed

Fix PyPiVersionRange working with * and ~=#126
GermanMT wants to merge 1 commit into
aboutcode-org:mainfrom
GermanMT:main

Conversation

@GermanMT

@GermanMT GermanMT commented Oct 19, 2023

Copy link
Copy Markdown

Fix the problem with the ~= operator and the versions ending in *, exposed in the issue.

Fixes:

Signed-off-by: GermanMT <germanoctako@gmail.com>
@TG1999
TG1999 requested a review from pombredanne November 15, 2023 08:54

@pombredanne pombredanne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you ++! See some comments for your consideration.

if "*" in version:
pos = version.find("*")
version = version[: pos - 1]
if "==" in operator:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using a strict equality? otherwise this is true: "==" in "==="

Suggested change
if "==" in operator:
if operator == "==":

The same applies to the tests below

for spec in specifiers:
operator = spec.operator
version = spec.version
if "*" in version:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we limit this to trailing "*" and not at any position?
I am not sure we can handle sanely other cases for now and the case with a star not at the end should likely be an exception.

Suggested change
if "*" in version:
if version.endswith("*"):

version = version[: pos - 1]
if "==" in operator:
constraints.extend(
[f">= {version}", f"< {version[:pos - 2]}{str(int(version[pos - 2]) + 1)}"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why keep a space between the operator and the version? Also pos - 2 is hard to read for me and not obvious... could there be a better way? may be with some intermediate variables to give it some meaning? And multiple append rather than an extend?

constraints.extend(
[f">= {version}", f"< {version[:pos - 2]}{str(int(version[pos - 2]) + 1)}"]
)
elif "!=" in operator:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif "!=" in operator:
elif operator == "!=":

)
elif "!=" in operator:
constraints.extend(
[f"< {version}", f">= {version[:pos - 2]}{str(int(version[pos - 2]) + 1)}"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit as above: there must be a more readable way to express this.

[f"< {version}", f">= {version[:pos - 2]}{str(int(version[pos - 2]) + 1)}"]
)
elif "~=" in operator:
parts = version.split(".")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there is no dot in the version? Will the code below crash?

)
elif "~=" in operator:
parts = version.split(".")
parts[-2] = str(int(parts[-2]) + 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more obvious way to write this? This is really hard for me to understand :]

return cls(constraints=constraints)

@classmethod
def sanitize_constraints(cls, string):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add tests specifically for this function? And some doc string?

(" ~= 0.9", False, "Unsupported PyPI version constraint operator"),
("~= 1.3", False, "Unsupported PyPI version constraint operator"),
(" ~= 0.9", True, "vers:pypi/>=0.9|<1"),
("~= 1.3", True, "vers:pypi/>=1.3|<2"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about these added tests:

"==1*"
"~=1"
"~=1*"
"!=1*"
">=1*"
">1*"
"<=1*"
"<1*"
"~=1.*.*"
"~=*.1"
"~=1.*.2"
"~=*"

constraints.extend([f">= {version}", f"< {'.'.join(parts[:-1])}"])
else:
constraints.append(f"{operator} {version}")
return ", ".join(constraints)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of just sanitizing constraints and returning a string, vs. actually integrating this in the parsing of constraints proper? Here the code ends up parsing the same constraints set twice:

@pombredanne

Copy link
Copy Markdown
Member

BTW, do you have URLs to some concrete examples of where these constraints are used in the wild? Thanks!

@GermanMT GermanMT closed this by deleting the head repository Jul 11, 2024
@pombredanne

Copy link
Copy Markdown
Member

@GermanMT too bad you closed this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants