From 14cd842c9f637f2f9f4e38ded1f4cfade6a092e2 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Tue, 21 Apr 2020 12:33:43 +0200 Subject: [PATCH 1/3] Fix #236: add missing deprecated functions Deprecate: * `semver.compare` -> `semver.VersionInfo.compare` * `semver.match` -> `semver.VersionInfo.match` Change: * Implementation of `semver.max_ver` and `semver.min_ver` --- semver.py | 145 ++++++++++++++++++++++++++++++++++++------------- test_semver.py | 2 + 2 files changed, 109 insertions(+), 38 deletions(-) diff --git a/semver.py b/semver.py index de72f061..0cb9d648 100644 --- a/semver.py +++ b/semver.py @@ -371,6 +371,50 @@ def bump_build(self, token="build"): build = cls._increment_string(self._build or (token or "build") + ".0") return cls(self._major, self._minor, self._patch, self._prerelease, build) + def compare(self, other): + """ + Compare self with other. + + :param other: second version (can be string or a VersionInfo instance) + :return: The return value is negative if ver1 < ver2, + zero if ver1 == ver2 and strictly positive if ver1 > ver2 + :rtype: int + + >>> semver.VersionInfo.parse("1.0.0").compare("2.0.0") + -1 + >>> semver.VersionInfo.parse("2.0.0").compare("1.0.0") + 1 + >>> semver.VersionInfo.parse("2.0.0").compare("2.0.0") + 0 + """ + cls = type(self) + if isinstance(other, str): + other = cls.parse(other) + elif not isinstance(other, cls): + raise TypeError( + "Expected str or {} instance, but got {}".format( + cls.__name__, type(other) + ) + ) + + v1 = self.to_tuple()[:3] + v2 = other.to_tuple()[:3] + x = cmp(v1, v2) + if x: + return x + + rc1, rc2 = self.prerelease, other.prerelease + rccmp = _nat_cmp(rc1, rc2) + + if not rccmp: + return 0 + if not rc1: + return 1 + elif not rc2: + return -1 + + return rccmp + @comparator def __eq__(self, other): return _compare_by_keys(self.to_dict(), _to_dict(other)) == 0 @@ -424,6 +468,53 @@ def finalize_version(self): cls = type(self) return cls(self.major, self.minor, self.patch) + def match(self, match_expr): + """ + Compare self to match a match expression. + + :param str match_expr: operator and version; valid operators are + < smaller than + > greater than + >= greator or equal than + <= smaller or equal than + == equal + != not equal + :return: True if the expression matches the version, otherwise False + :rtype: bool + + >>> semver.VersionInfo.parse("2.0.0").match(">=1.0.0") + True + >>> semver.VersionInfo.parse("1.0.0").match(">1.0.0") + False + """ + prefix = match_expr[:2] + if prefix in (">=", "<=", "==", "!="): + match_version = match_expr[2:] + elif prefix and prefix[0] in (">", "<"): + prefix = prefix[0] + match_version = match_expr[1:] + else: + raise ValueError( + "match_expr parameter should be in format , " + "where is one of " + "['<', '>', '==', '<=', '>=', '!=']. " + "You provided: %r" % match_expr + ) + + possibilities_dict = { + ">": (1,), + "<": (-1,), + "==": (0,), + "!=": (-1, 1), + ">=": (0, 1), + "<=": (-1, 0), + } + + possibilities = possibilities_dict[prefix] + cmp_res = self.compare(match_version) + + return cmp_res in possibilities + @staticmethod def parse(version): """ @@ -579,6 +670,7 @@ def _compare_by_keys(d1, d2): return rccmp +@deprecated(version="2.10.0") def compare(ver1, ver2): """ Compare two versions strings. @@ -596,13 +688,13 @@ def compare(ver1, ver2): >>> semver.compare("2.0.0", "2.0.0") 0 """ - - v1 = VersionInfo.parse(ver1).to_dict() - v2 = VersionInfo.parse(ver2).to_dict() - - return _compare_by_keys(v1, v2) + v1 = VersionInfo.parse(ver1) + # v2 = VersionInfo.parse(ver2) + return v1.compare(ver2) + # return _compare_by_keys(v1, v2) +@deprecated(version="2.10.0") def match(version, match_expr): """ Compare two versions strings through a comparison. @@ -623,33 +715,8 @@ def match(version, match_expr): >>> semver.match("1.0.0", ">1.0.0") False """ - prefix = match_expr[:2] - if prefix in (">=", "<=", "==", "!="): - match_version = match_expr[2:] - elif prefix and prefix[0] in (">", "<"): - prefix = prefix[0] - match_version = match_expr[1:] - else: - raise ValueError( - "match_expr parameter should be in format , " - "where is one of " - "['<', '>', '==', '<=', '>=', '!=']. " - "You provided: %r" % match_expr - ) - - possibilities_dict = { - ">": (1,), - "<": (-1,), - "==": (0,), - "!=": (-1, 1), - ">=": (0, 1), - "<=": (-1, 0), - } - - possibilities = possibilities_dict[prefix] - cmp_res = compare(version, match_version) - - return cmp_res in possibilities + ver = VersionInfo.parse(version) + return ver.match(match_expr) def max_ver(ver1, ver2): @@ -664,9 +731,10 @@ def max_ver(ver1, ver2): >>> semver.max_ver("1.0.0", "2.0.0") '2.0.0' """ - cmp_res = compare(ver1, ver2) - if cmp_res == 0 or cmp_res == 1: - return ver1 + ver1 = VersionInfo.parse(ver1) + cmp_res = ver1.compare(ver2) + if cmp_res >= 0: + return str(ver1) else: return ver2 @@ -683,9 +751,10 @@ def min_ver(ver1, ver2): >>> semver.min_ver("1.0.0", "2.0.0") '1.0.0' """ - cmp_res = compare(ver1, ver2) - if cmp_res == 0 or cmp_res == -1: - return ver1 + ver1 = VersionInfo.parse(ver1) + cmp_res = ver1.compare(ver2) + if cmp_res <= 0: + return str(ver1) else: return ver2 diff --git a/test_semver.py b/test_semver.py index 4611d771..5daf3f1a 100644 --- a/test_semver.py +++ b/test_semver.py @@ -855,8 +855,10 @@ def test_should_versioninfo_isvalid(): (bump_minor, ("1.2.3",), {}), (bump_patch, ("1.2.3",), {}), (bump_prerelease, ("1.2.3",), {}), + (compare, ("1.2.1", "1.2.2"), {}), (format_version, (3, 4, 5), {}), (finalize_version, ("1.2.3-rc.5",), {}), + (match, ("1.0.0", ">=1.0.0"), {}), (parse, ("1.2.3",), {}), (parse_version_info, ("1.2.3",), {}), (replace, ("1.2.3",), dict(major=2, patch=10)), From 4482f8cf0baa2be02790265054cc3784cdf6506a Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Tue, 21 Apr 2020 18:27:33 +0200 Subject: [PATCH 2/3] Get rid of _compare_by_keys, adapt comparison operators * Call self.compare(other) in all comparison operators. * Make sure, "other" is a compatible type (VersionInfo, dict, list, tuple, or string) --- semver.py | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/semver.py b/semver.py index 0cb9d648..bdc5fc36 100644 --- a/semver.py +++ b/semver.py @@ -375,7 +375,8 @@ def compare(self, other): """ Compare self with other. - :param other: second version (can be string or a VersionInfo instance) + :param other: the second version (can be string, a dict, tuple/list, or + a VersionInfo instance) :return: The return value is negative if ver1 < ver2, zero if ver1 == ver2 and strictly positive if ver1 > ver2 :rtype: int @@ -386,10 +387,16 @@ def compare(self, other): 1 >>> semver.VersionInfo.parse("2.0.0").compare("2.0.0") 0 + >>> semver.VersionInfo.parse("2.0.0").compare(dict(major=2, minor=0, patch=0)) + 0 """ cls = type(self) if isinstance(other, str): other = cls.parse(other) + elif isinstance(other, dict): + other = cls(**other) + elif isinstance(other, (tuple, list)): + other = cls(*other) elif not isinstance(other, cls): raise TypeError( "Expected str or {} instance, but got {}".format( @@ -417,27 +424,27 @@ def compare(self, other): @comparator def __eq__(self, other): - return _compare_by_keys(self.to_dict(), _to_dict(other)) == 0 + return self.compare(other) == 0 @comparator def __ne__(self, other): - return _compare_by_keys(self.to_dict(), _to_dict(other)) != 0 + return self.compare(other) != 0 @comparator def __lt__(self, other): - return _compare_by_keys(self.to_dict(), _to_dict(other)) < 0 + return self.compare(other) < 0 @comparator def __le__(self, other): - return _compare_by_keys(self.to_dict(), _to_dict(other)) <= 0 + return self.compare(other) <= 0 @comparator def __gt__(self, other): - return _compare_by_keys(self.to_dict(), _to_dict(other)) > 0 + return self.compare(other) > 0 @comparator def __ge__(self, other): - return _compare_by_keys(self.to_dict(), _to_dict(other)) >= 0 + return self.compare(other) >= 0 def __repr__(self): s = ", ".join("%s=%r" % (key, val) for key, val in self.to_dict().items()) @@ -651,25 +658,6 @@ def cmp_prerelease_tag(a, b): return cmp(len(a), len(b)) -def _compare_by_keys(d1, d2): - for key in ["major", "minor", "patch"]: - v = cmp(d1.get(key), d2.get(key)) - if v: - return v - - rc1, rc2 = d1.get("prerelease"), d2.get("prerelease") - rccmp = _nat_cmp(rc1, rc2) - - if not rccmp: - return 0 - if not rc1: - return 1 - elif not rc2: - return -1 - - return rccmp - - @deprecated(version="2.10.0") def compare(ver1, ver2): """ @@ -689,9 +677,7 @@ def compare(ver1, ver2): 0 """ v1 = VersionInfo.parse(ver1) - # v2 = VersionInfo.parse(ver2) return v1.compare(ver2) - # return _compare_by_keys(v1, v2) @deprecated(version="2.10.0") From 1c8fc743aabcf321dd42b3b083a3d8c380080f4f Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Tue, 21 Apr 2020 18:58:42 +0200 Subject: [PATCH 3/3] Remove _to_dict() function --- semver.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/semver.py b/semver.py index bdc5fc36..a5739b16 100644 --- a/semver.py +++ b/semver.py @@ -593,14 +593,6 @@ def isvalid(cls, version): return False -def _to_dict(obj): - if isinstance(obj, VersionInfo): - return obj.to_dict() - elif isinstance(obj, tuple): - return VersionInfo(*obj).to_dict() - return obj - - @deprecated(replace="semver.VersionInfo.parse", version="2.10.0") def parse_version_info(version): """