summaryrefslogtreecommitdiff
path: root/semantic_version
diff options
context:
space:
mode:
authorRaphaël Barrois <raphael.barrois@polytechnique.org>2022-05-26 15:28:23 +0200
committerRaphaël Barrois <raphael.barrois@polytechnique.org>2022-05-26 15:34:45 +0200
commit57c78e7307792879dce33734c11e7774383b9d36 (patch)
treecef730ce8d6f5af904b0a4ad261cc937ea78e69d /semantic_version
parent7dcc42d2a828adbbeb6f8a23cdca40a3c61782bc (diff)
downloadsemantic-version-57c78e7307792879dce33734c11e7774383b9d36.tar.gz
Guarantee a stable ordering with build metadata
Sorting any permutation of Version objects should always yield the same result, even if those hold some build metadata. To that end, the "precedence_key" is now used exclusively for sorting; direct comparisons between Version objects still ignores the "build" metadata, using a different precedence key. For performance improvements, both precedence keys are cached. Closes: #132
Diffstat (limited to 'semantic_version')
-rw-r--r--semantic_version/base.py42
1 files changed, 35 insertions, 7 deletions
diff --git a/semantic_version/base.py b/semantic_version/base.py
index 82a9af0..777c27a 100644
--- a/semantic_version/base.py
+++ b/semantic_version/base.py
@@ -118,6 +118,12 @@ class Version(object):
self.partial = partial
+ # Cached precedence keys
+ # _cmp_precedence_key is used for semver-precedence comparison
+ self._cmp_precedence_key = self._build_precedence_key(with_build=False)
+ # _sort_precedence_key is used for self.precedence_key, esp. for sorted(...)
+ self._sort_precedence_key = self._build_precedence_key(with_build=True)
+
@classmethod
def _coerce(cls, value, allow_none=False):
if value is None and allow_none:
@@ -408,11 +414,15 @@ class Version(object):
# at least a field being `None`.
return hash((self.major, self.minor, self.patch, self.prerelease, self.build))
- @property
- def precedence_key(self):
+ def _build_precedence_key(self, with_build=False):
+ """Build a precedence key.
+
+ The "build" component should only be used when sorting an iterable
+ of versions.
+ """
if self.prerelease:
prerelease_key = tuple(
- NumericIdentifier(part) if re.match(r'^[0-9]+$', part) else AlphaIdentifier(part)
+ NumericIdentifier(part) if part.isdigit() else AlphaIdentifier(part)
for part in self.prerelease
)
else:
@@ -420,13 +430,31 @@ class Version(object):
MaxIdentifier(),
)
+ if not with_build:
+ return (
+ self.major,
+ self.minor,
+ self.patch,
+ prerelease_key,
+ )
+
+ build_key = tuple(
+ NumericIdentifier(part) if part.isdigit() else AlphaIdentifier(part)
+ for part in self.build or ()
+ )
+
return (
self.major,
self.minor,
self.patch,
prerelease_key,
+ build_key,
)
+ @property
+ def precedence_key(self):
+ return self._sort_precedence_key
+
def __cmp__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
@@ -458,22 +486,22 @@ class Version(object):
def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
- return self.precedence_key < other.precedence_key
+ return self._cmp_precedence_key < other._cmp_precedence_key
def __le__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
- return self.precedence_key <= other.precedence_key
+ return self._cmp_precedence_key <= other._cmp_precedence_key
def __gt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
- return self.precedence_key > other.precedence_key
+ return self._cmp_precedence_key > other._cmp_precedence_key
def __ge__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
- return self.precedence_key >= other.precedence_key
+ return self._cmp_precedence_key >= other._cmp_precedence_key
class SpecItem(object):