From 57c78e7307792879dce33734c11e7774383b9d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 26 May 2022 15:28:23 +0200 Subject: 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 --- semantic_version/base.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) (limited to 'semantic_version') 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): -- cgit v1.2.1