@@ -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