Skip to content

Commit

Permalink
Fix PyPiVersionRange working with * and ~=
Browse files Browse the repository at this point in the history
Signed-off-by: GermanMT <[email protected]>
  • Loading branch information
GermanMT committed Oct 19, 2023
1 parent 205d7c4 commit 0fa6ad4
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 15 deletions.
44 changes: 33 additions & 11 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,11 +664,6 @@ class PypiVersionRange(VersionRange):
">=": ">=",
"<": "<",
">": ">",
# per https://www.python.org/dev/peps/pep-0440/#compatible-release
# For a given release identifier V.N, the compatible release clause is
# approximately equivalent to the pair of comparison clauses:
# >= V.N, == V.*
"~=": None,
# 01.01.01 is NOT equal to 1.1.1 using === which is strict string
# equality this is a rare and eventually non-suggested approach
"===": None,
Expand All @@ -681,7 +676,7 @@ def from_native(cls, string):
Raise an a univers.versions.InvalidVersion
"""
# TODO: environment markers are yet supported
# TODO: handle .* version, ~= and === operators
# TODO: handle === operators

if ";" in string:
raise InvalidVersionRange(f"Unsupported PyPI environment marker: {string!r}")
Expand All @@ -694,6 +689,8 @@ def from_native(cls, string):
f"Unsupported character: {unsupported_chars!r} " f"in PyPI version: {string!r}"
)

string = cls.sanitize_constraints(string)

try:
specifiers = SpecifierSet(string)
except InvalidSpecifier as e:
Expand All @@ -707,14 +704,10 @@ def from_native(cls, string):
operator = spec.operator
version = spec.version

if operator == "~=" or operator == "===":
if operator == "===":
msg = f"Unsupported PyPI version constraint operator: {spec!r}"
unsupported_messages.append(msg)

if str(version).endswith(".*"):
msg = f"Unsupported PyPI version: {spec!r}"
unsupported_messages.append(msg)

try:
version = cls.version_class(version)
comparator = cls.vers_by_native_comparators[operator]
Expand All @@ -729,6 +722,35 @@ def from_native(cls, string):

return cls(constraints=constraints)

@classmethod
def sanitize_constraints(cls, string):
constraints = []
try:
specifiers = SpecifierSet(string)
except InvalidSpecifier as e:
raise InvalidVersionRange() from e
for spec in specifiers:
operator = spec.operator
version = spec.version
if "*" in version:
pos = version.find("*")
version = version[: pos - 1]
if "==" in operator:
constraints.extend(
[f">= {version}", f"< {version[:pos - 2]}{str(int(version[pos - 2]) + 1)}"]
)
elif "!=" in operator:
constraints.extend(
[f"< {version}", f">= {version[:pos - 2]}{str(int(version[pos - 2]) + 1)}"]
)
elif "~=" in operator:
parts = version.split(".")
parts[-2] = str(int(parts[-2]) + 1)
constraints.extend([f">= {version}", f"< {'.'.join(parts[:-1])}"])
else:
constraints.append(f"{operator} {version}")
return ", ".join(constraints)


class MavenVersionRange(VersionRange):
"""
Expand Down
10 changes: 6 additions & 4 deletions tests/test_version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,14 @@ def test_from_native_and_from_string_round_trip(scheme, native_ranges):
@pytest.mark.parametrize(
"range, will_pass, expected",
[
(" ~= 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"),
(" >= 1.0", True, "vers:pypi/>=1.0"),
(" != 1.3.4.*", False, "Unsupported PyPI version"),
(" != 1.3.4.*", True, "vers:pypi/<1.3.4|>=1.3.5"),
("< 2.0", True, "vers:pypi/<2.0"),
("~= 1.3.4.*", False, ""),
("==1. *", False, "Unsupported PyPI version"),
("==1. *", True, "vers:pypi/>=1|<2"),
("!=1.2.*", True, "vers:pypi/<1.2|>=1.3"),
("==1.3.4 ) (", False, "Unsupported character"),
("===1.0", False, "Unsupported PyPI version"),
],
Expand All @@ -375,6 +376,7 @@ def test_PypiVersionRange_raises_ivr_for_unsupported_and_invalid_ranges(range, w
raise Exception("Exception not raised")
except InvalidVersionRange as ivre:
assert expected in str(ivre)

else:
assert expected == str(PypiVersionRange.from_native(range))

Expand Down

0 comments on commit 0fa6ad4

Please sign in to comment.