summaryrefslogtreecommitdiff
path: root/semantic_version
diff options
context:
space:
mode:
authorRaphaël Barrois <raphael.barrois@polytechnique.org>2015-09-15 23:18:13 +0200
committerRaphaël Barrois <raphael.barrois@polytechnique.org>2015-09-15 23:40:59 +0200
commit2ed3d39c291080c61edd9139370939e1fdc3209a (patch)
tree22a90dfae0e0fc9fd7949ca18e7e47daa8f8aabb /semantic_version
parent4aac5768db2fc158fa87900b54210ecba4dfe6d5 (diff)
downloadsemantic-version-2ed3d39c291080c61edd9139370939e1fdc3209a.tar.gz
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')``
Diffstat (limited to 'semantic_version')
-rw-r--r--semantic_version/base.py73
-rw-r--r--semantic_version/compat.py23
2 files changed, 46 insertions, 50 deletions
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