Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 23 additions & 21 deletions src/univers/semver.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
#
# Visit https://aboutcode.org and https://github.com/nexB/univers for support and download.

import semantic_version

from univers.utils import remove_spaces
from univers.version_constraint import VersionConstraint
from univers.versions import SemverVersion

"""
node-semver and Rubygems semver-like related utilities.
Expand All @@ -16,14 +15,15 @@

def get_caret_constraints(string):
"""
Return a tuple of two VersionConstraint representing the lower and upper
bound of version constraint ``string`` that contains a caret node-semver-
like range. Raise a ValueError if this is not a caret range.
Return a tuple of two VersionConstraint of ``SemverVersion`` representing
the lower and upper bound of version constraint ``string`` that contains a
caret node-semver- like range. Raise a ValueError if this is not a caret
range.

For example:
>>> lower_bound, upper_bound = get_caret_constraints("^1.0.2")
>>> vlow = semantic_version.Version("1.0.2")
>>> vup = semantic_version.Version("2.0.0")
>>> vlow = SemverVersion("1.0.2")
>>> vup = SemverVersion("2.0.0")
>>> assert lower_bound == VersionConstraint(comparator=">=", version=vlow)
>>> assert upper_bound == VersionConstraint(comparator="<", version=vup)
"""
Expand All @@ -32,8 +32,8 @@ def get_caret_constraints(string):
raise ValueError(f"Invalid caret version range: {string!r}")

version = string.lstrip("^")
lower_bound = semantic_version.Version(version)
upper_bound = lower_bound.next_major()
lower_bound = SemverVersion(version)
upper_bound = SemverVersion(str(lower_bound.value.next_major()))

return (
VersionConstraint(comparator=">=", version=lower_bound),
Expand All @@ -43,14 +43,15 @@ def get_caret_constraints(string):

def get_tilde_constraints(string, operator="~"):
"""
Return a tuple of two VersionConstraint representing the lower and upper
bound of a version range ``string`` that contains a tilde node-semver-like
range. Raise a ValueError if this is not a tilde range.
Return a tuple of two VersionConstraint of ``SemverVersion`` representing
the lower and upper bound of a version range ``string`` that contains a
tilde node-semver-like range.
Raise a ValueError if this is not a tilde range.

For example:
>>> lower_bound, upper_bound = get_tilde_constraints("~1.0.2")
>>> vlow = semantic_version.Version("1.0.2")
>>> vup = semantic_version.Version("1.1.0")
>>> vlow = SemverVersion("1.0.2")
>>> vup = SemverVersion("1.1.0")
>>> assert lower_bound == VersionConstraint(comparator=">=", version=vlow)
>>> assert upper_bound == VersionConstraint(comparator="<", version=vup)
"""
Expand All @@ -59,8 +60,8 @@ def get_tilde_constraints(string, operator="~"):
raise ValueError(f"Invalid version range: {string!r} " f"does not start with {operator!r}")

version = string.lstrip(operator)
lower_bound = semantic_version.Version(version)
upper_bound = lower_bound.next_minor()
lower_bound = SemverVersion(version)
upper_bound = SemverVersion(str(lower_bound.value.next_minor()))

return (
VersionConstraint(comparator=">=", version=lower_bound),
Expand All @@ -71,14 +72,15 @@ def get_tilde_constraints(string, operator="~"):
# FIXME: this is unlikely correct https://github.com/npm/node-semver/issues/112
def get_pessimistic_constraints(string):
"""
Return a tuple of two VersionConstraint representing the lower and upper
bound of version range ``string`` that contains a pessimistic Ruby range.
Raise a ValueError if this is not a pessimistic Rubygems range.
Return a tuple of two VersionConstraint of ``SemverVersion`` representing
the lower and upper bound of version range ``string`` that contains a
pessimistic Ruby range. Raise a ValueError if this is not a pessimistic
Rubygems range.

For example:
>>> lower_bound, upper_bound = get_pessimistic_constraints("~>2.0.8")
>>> vlow = semantic_version.Version("2.0.8")
>>> vup = semantic_version.Version("2.1.0")
>>> vlow = SemverVersion("2.0.8")
>>> vup = SemverVersion("2.1.0")
>>> assert lower_bound == VersionConstraint(comparator=">=", version=vlow)
>>> assert upper_bound == VersionConstraint(comparator="<", version=vup)
"""
Expand Down
33 changes: 23 additions & 10 deletions src/univers/version_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,15 @@ class VersionConstraint:
# one of the COMPARATORS
comparator = attr.ib(type=str, default="=")

# a Version subclass instance or None
# a Version subclass instance
version = attr.ib(type=Version, default=None)

# a function for the comparator
comp_operator = attr.ib(default=None, repr=False)

# a Version subclass
version_class = attr.ib(type=Version, default=None, repr=False)

def __attrs_post_init__(self):
# Notes: setattr is used because this is an immutable frozen instance.
# See https://www.attrs.org/en/stable/init.html?#post-init
Expand All @@ -80,15 +83,26 @@ def __attrs_post_init__(self):
except KeyError as e:
raise ValueError(f"Unknown comparator: {self.comparator}") from e

if self.version and not isinstance(self.version, Version):
raise TypeError(
f"version must be a 'Version' instance and not: {self.version.__class__!r}"
)

if not self.version_class:
if self.version:
object.__setattr__(self, "version_class", self.version.__class__)
else:
raise ValueError("Cannot build a VersionConstraint without a version class")

def __str__(self):
"""
Return a string representing this constraint.
For example::
>>> assert str(VersionConstraint(comparator=">=", version="2.3")) == ">=2.3"
>>> assert str(VersionConstraint(comparator="*")) == "*"
>>> assert str(VersionConstraint(comparator="<", version="2.3")) == "<2.3"
>>> assert str(VersionConstraint(comparator="=", version="2.3.0")) == "2.3.0"
>>> assert str(VersionConstraint(version="2.3.0")) == "2.3.0"
>>> assert str(VersionConstraint(comparator=">=", version=Version("2.3"))) == ">=2.3"
>>> assert str(VersionConstraint(comparator="*", version_class=Version)) == "*"
>>> assert str(VersionConstraint(comparator="<", version=Version("2.3"))) == "<2.3"
>>> assert str(VersionConstraint(comparator="=", version=Version("2.3.0"))) == "2.3.0"
>>> assert str(VersionConstraint(version=Version("2.3.0"))) == "2.3.0"
"""
if self.comparator == "*":
return "*"
Expand Down Expand Up @@ -143,7 +157,7 @@ def from_string(cls, string, version_class):
version = None
else:
version = version_class(version)
return cls(comparator, version)
return cls(comparator=comparator, version=version, version_class=version_class)

@staticmethod
def split(string):
Expand Down Expand Up @@ -210,11 +224,10 @@ def __contains__(self, version):
>>> assert v24 in VersionConstraint(comparator="<=", version=v24)
>>> assert v24 not in VersionConstraint(comparator="<", version=v24)
"""

if not isinstance(version, self.version.__class__):
if not isinstance(version, self.version_class):
raise ValueError(
f"Cannot compare {version.__class__!r} instance "
f"with {self.version.__class__!r} instance."
f"with {self.version_class!r} instance."
)
return self.comp_operator(version, self.version)

Expand Down
8 changes: 6 additions & 2 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ def from_string(cls, vers, simplify=False, validate=False):
if constraints.startswith("*"):
if constraints != "*":
raise ValueError(f"{vers!r} contains an invalid '*' constraint.")
return range_class([VersionConstraint.from_string(string="*", version_class=None)])
return range_class(
[VersionConstraint.from_string(string="*", version_class=version_class)]
)

parsed_constraints = []

Expand Down Expand Up @@ -774,7 +776,9 @@ def from_native(cls, string):
"""
cleaned = remove_spaces(string).lower()
if cleaned == "all":
return cls(constraints=[VersionConstraint(comparator="*")])
return cls(
constraints=[VersionConstraint(comparator="*", version_class=cls.version_class)]
)

constraints = []

Expand Down
5 changes: 5 additions & 0 deletions tests/test_version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,8 @@ def test_GemVersionRange_from_native_range_with_pessimistic_operator(self):
VersionConstraint(comparator=">=", version=RubygemsVersion(string="2.0.8")),
VersionConstraint(comparator="<", version=RubygemsVersion(string="2.1")),
)

def test_VersionRange_contains_works_for_star_range(self):
from univers.versions import SemverVersion

SemverVersion("1.0.0") in VersionRange.from_string("vers:nginx/*")