From 2ed3d39c291080c61edd9139370939e1fdc3209a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Tue, 15 Sep 2015 23:18:13 +0200 Subject: Forbid build metadata ordering (See #18) SemVer 2.0.0 states that "Build metadata SHOULD be ignored when determining version precedence". This means that, when comparing ``0.1.0+1`` to ``0.1.0+bcd``:: >>> Version('0.1.0+1') == Version('0.1.0+bcd') False >>> Version('0.1.0+1') != Version('0.1.0+bcd') True >>> Version('0.1.0+1') < Version('0.1.0+bcd') False >>> Version('0.1.0+1') > Version('0.1.0+bcd') False >>> Version('0.1.0+1') <= Version('0.1.0+bcd') False >>> Version('0.1.0+1') >= Version('0.1.0+bcd') False >>> compare(Version('0.1.0+1'), Version('0.1.0+bcd')) NotImplemented This change also has the following effects: - When including build metadata in a ``Spec``, the only valid options are ``Spec('==0.1.0+sth')`` and ``Spec('!=0.1.0+sth')`` - The meaning of ``Spec('==0.1.0+')`` is now "Only version 0.1.0 without build metadata" - ``Spec('==0.1.0')`` now matches ``Version('0.1.0+anything')`` --- semantic_version/base.py | 73 +++++++++++++++++++++++----------------------- semantic_version/compat.py | 23 +++++++-------- 2 files changed, 46 insertions(+), 50 deletions(-) (limited to 'semantic_version') diff --git a/semantic_version/base.py b/semantic_version/base.py index 841c5f3..982fcc8 100644 --- a/semantic_version/base.py +++ b/semantic_version/base.py @@ -290,20 +290,14 @@ class Version(object): return 0 def build_cmp(a, b): - """Compare build components. + """Compare build metadata. - Special rule: a version without build component has lower - precedence than one with a build component. + Special rule: there is no ordering on build metadata. """ - if a and b: - return identifier_list_cmp(a, b) - elif a: - # Versions with build field have higher precedence - return 1 - elif b: - return -1 - else: + if a == b: return 0 + else: + return NotImplemented def make_optional(orig_cmp_fun): """Convert a cmp-like function to consider 'None == *'.""" @@ -332,10 +326,7 @@ class Version(object): build_cmp, ] - def __cmp__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - + def __compare(self, other): field_pairs = zip(self, other) comparison_functions = self._comparison_functions(partial=self.partial or other.partial) comparisons = zip(comparison_functions, self, other) @@ -347,44 +338,48 @@ class Version(object): return 0 - def __eq__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self.__cmp__(other) == 0 - def __hash__(self): return hash((self.major, self.minor, self.patch, self.prerelease, self.build)) - def __ne__(self, other): + def __cmp__(self, other): if not isinstance(other, self.__class__): return NotImplemented + return self.__compare(other) - return self.__cmp__(other) != 0 + def __compare_helper(self, other, condition, notimpl_target): + """Helper for comparison. - def __lt__(self, other): + Allows the caller to provide: + - The condition + - The return value if the comparison is meaningless (ie versions with + build metadata). + """ if not isinstance(other, self.__class__): return NotImplemented - return self.__cmp__(other) < 0 + cmp_res = self.__cmp__(other) + if cmp_res is NotImplemented: + return notimpl_target - def __le__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented + return condition(cmp_res) - return self.__cmp__(other) <= 0 + def __eq__(self, other): + return self.__compare_helper(other, lambda x: x == 0, notimpl_target=False) - def __gt__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented + def __ne__(self, other): + return self.__compare_helper(other, lambda x: x != 0, notimpl_target=True) - return self.__cmp__(other) > 0 + def __lt__(self, other): + return self.__compare_helper(other, lambda x: x < 0, notimpl_target=False) - def __ge__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented + def __le__(self, other): + return self.__compare_helper(other, lambda x: x <= 0, notimpl_target=False) + + def __gt__(self, other): + return self.__compare_helper(other, lambda x: x > 0, notimpl_target=False) - return self.__cmp__(other) >= 0 + def __ge__(self, other): + return self.__compare_helper(other, lambda x: x >= 0, notimpl_target=False) class SpecItem(object): @@ -420,6 +415,10 @@ class SpecItem(object): kind, version = match.groups() spec = Version(version, partial=True) + if spec.build is not None and kind not in (cls.KIND_EQUAL, cls.KIND_NEQ): + raise ValueError( + "Invalid requirement specification %r: build numbers have no ordering." + % requirement_string) return (kind, spec) def match(self, version): diff --git a/semantic_version/compat.py b/semantic_version/compat.py index bea6f67..4dd60fe 100644 --- a/semantic_version/compat.py +++ b/semantic_version/compat.py @@ -2,17 +2,14 @@ # Copyright (c) 2012-2014 The python-semanticversion project # This code is distributed under the two-clause BSD License. -import sys -is_python2 = (sys.version_info[0] == 2) - -if is_python2: # pragma: no cover - base_cmp = cmp -else: # pragma: no cover - def base_cmp(x, y): - if x < y: - return -1 - elif x > y: - return 1 - else: - return 0 +def base_cmp(x, y): + if x == y: + return 0 + elif x > y: + return 1 + elif x < y: + return -1 + else: + # Fix Py2's behavior: cmp(x, y) returns -1 for unorderable types + return NotImplemented -- cgit v1.2.1