@@ -526,7 +526,6 @@ def _pad_zeros(release, n):
526526 release = list (release ) + [0 ] * padding
527527 return tuple (release )
528528
529- # TODO @aignas 2025-05-04: add tests for the comparison
530529def _version_eq (left , right ):
531530 if left .is_prefix and right .is_prefix :
532531 fail ("Invalid comparison: both versions cannot be prefix matching" )
@@ -568,7 +567,14 @@ def _version_lt(left, right):
568567 elif left_release < right_release :
569568 return True
570569
571- return left .key () < right .key ()
570+ # From PEP440, this is not a simple ordering check and we need to check the version
571+ # semantically:
572+ # * The exclusive ordered comparison <V MUST NOT allow a pre-release of the specified version
573+ # unless the specified version is itself a pre-release.
574+ if left .pre and right .pre :
575+ return left .pre < right .pre
576+ else :
577+ return False
572578
573579def _version_gt (left , right ):
574580 if left .epoch > right .epoch :
@@ -585,7 +591,30 @@ def _version_gt(left, right):
585591 elif left_release < right_release :
586592 return False
587593
588- return left .key () > right .key ()
594+ # From PEP440, this is not a simple ordering check and we need to check the version
595+ # semantically:
596+ # * The exclusive ordered comparison >V MUST NOT allow a post-release of the given version
597+ # unless V itself is a post release.
598+ #
599+ # * The exclusive ordered comparison >V MUST NOT match a local version of the specified
600+ # version.
601+
602+ if left .post and right .post :
603+ return left .post > right .post
604+ else :
605+ # ignore the left.post if right is not a post if right is a post, then this evaluates to
606+ # False anyway.
607+ return False
608+
609+ def _version_ge (left , right ):
610+ # PEP440: simple order check
611+ # https://peps.python.org/pep-0440/#inclusive-ordered-comparison
612+ return left .key (local = False ) >= right .key (local = False )
613+
614+ def _version_le (left , right ):
615+ # PEP440: simple order check
616+ # https://peps.python.org/pep-0440/#inclusive-ordered-comparison
617+ return left .key (local = False ) <= right .key (local = False )
589618
590619def _first_non_none (* args ):
591620 for arg in args :
@@ -594,11 +623,14 @@ def _first_non_none(*args):
594623
595624 return None
596625
597- def _key (self , release_key = ("z" ,)):
626+ def _key (self , * , local , release_key = ("z" ,)):
598627 """This function returns a tuple that can be used in 'sorted' calls.
599628
600629 This implements the PEP440 version sorting.
601630 """
631+ local = self .local if local else []
632+ local = local or []
633+
602634 return (
603635 self .epoch ,
604636 self .release ,
@@ -609,7 +641,7 @@ def _key(self, release_key = ("z",)):
609641 # then stable
610642 _first_non_none (self .pre , self .post , self .dev , release_key ),
611643 # PEP440 local versions go before post versions
612- tuple ([(type (item ) == "int" , item ) for item in self . local or [] ]),
644+ tuple ([(type (item ) == "int" , item ) for item in local ]),
613645 # PEP440 - pre-release ordering: .devN, <no suffix>, .postN
614646 _first_non_none (
615647 self .post ,
@@ -672,23 +704,22 @@ def _new_version(*, epoch = 0, release, pre = "", post = "", dev = "", local = "
672704 local = local ,
673705 is_prefix = is_prefix ,
674706 norm = norm ,
675- # TODO @aignas 2025-05-04: add tests for the comparison
676707 eq = lambda x : _version_eq (self , x ), # buildifier: disable=uninitialized
677708 ne = lambda x : not _version_eq (self , x ), # buildifier: disable=uninitialized
678709 lt = lambda x : _version_lt (self , x ), # buildifier: disable=uninitialized
679710 gt = lambda x : _version_gt (self , x ), # buildifier: disable=uninitialized
680- le = lambda x : not _version_gt (self , x ), # buildifier: disable=uninitialized
681- ge = lambda x : not _version_lt (self , x ), # buildifier: disable=uninitialized
711+ le = lambda x : _version_le (self , x ), # buildifier: disable=uninitialized
712+ ge = lambda x : _version_ge (self , x ), # buildifier: disable=uninitialized
682713 str = lambda : norm ,
683- key = lambda : _key (self ), # buildifier: disable=uninitialized
714+ key = lambda * , local = True : _key (self , local = local ), # buildifier: disable=uninitialized
684715 )
685716
686717 return self
687718
688719def parse_version (version , strict = False ):
689720 """Parse a PEP4408 compliant version
690721
691- TODO: finish
722+ TODO @aignas 2025-05-06: where should this go?
692723
693724 See https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
694725 and https://peps.python.org/pep-0440/
@@ -702,6 +733,9 @@ def parse_version(version, strict = False):
702733 """
703734
704735 parser = _new (version .strip (" " if strict else " .*" )) # PEP 440: Leading and Trailing Whitespace and .*
736+
737+ # TODO @aignas 2025-05-06: Remove this usage of a second parser just to get the normalized
738+ # version.
705739 parser_2 = _new (version .strip (" " if strict else " .*" )) # PEP 440: Leading and Trailing Whitespace and .*
706740 accept (parser , _is ("v" ), "" ) # PEP 440: Preceding v character
707741 accept (parser_2 , _is ("v" ), "" ) # PEP 440: Preceding v character
0 commit comments