Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PyPiVersionRange working with * and ~= #126

Closed
wants to merge 1 commit into from
Closed

Fix PyPiVersionRange working with * and ~= #126

wants to merge 1 commit into from

Conversation

GermanMT
Copy link

@GermanMT GermanMT commented Oct 19, 2023

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

Fixes:

Copy link
Member

@pombredanne pombredanne left a comment

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
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
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
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
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
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
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
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 :]

@@ -729,6 +722,35 @@ def from_native(cls, string):

return cls(constraints=constraints)

@classmethod
def sanitize_constraints(cls, string):
Copy link
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
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
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
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
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