@@ -763,30 +763,25 @@ def relative_date_parse_for_feature_flag_matching(
763763 return None
764764
765765
766- SEMVER_EXTRACT_RE = re .compile (r"(\d+(?:\.\d+)+)" )
767-
768-
769766def parse_semver (value : str ) -> tuple :
770767 """Parse a semver string into a comparable (major, minor, patch) integer tuple.
771768
772769 Matches the behavior of the sortableSemver HogQL function:
773- - Uses regex to extract version numbers from strings like "v1.2.3-alpha"
774770 - Handles v-prefix, whitespace, pre-release suffixes
775771 - Defaults missing components to 0 (e.g., 1.2 -> 1.2.0)
776772 Raises ValueError if parsing fails.
777773 """
778- text = str (value )
779- if len (text ) > 200 :
780- raise ValueError ("Version string too long" )
781- match = SEMVER_EXTRACT_RE .search (text )
782- if not match :
783- raise ValueError ("Invalid semver format" )
774+ text = str (value ).strip ().lstrip ("vV" )
775+ # Strip pre-release/build metadata suffix
776+ text = text .split ("-" )[0 ].split ("+" )[0 ]
777+ parts = text .split ("." )
784778
785- parts = match .group (1 ).split ("." )
779+ if not parts or not parts [0 ]:
780+ raise ValueError ("Invalid semver format" )
786781
787782 major = int (parts [0 ])
788- minor = int (parts [1 ]) if len (parts ) > 1 else 0
789- patch = int (parts [2 ]) if len (parts ) > 2 else 0
783+ minor = int (parts [1 ]) if len (parts ) > 1 and parts [ 1 ] else 0
784+ patch = int (parts [2 ]) if len (parts ) > 2 and parts [ 2 ] else 0
790785
791786 return (major , minor , patch )
792787
@@ -821,21 +816,13 @@ def _wildcard_bounds(value: str) -> tuple:
821816 1.* means >=1.0.0 <2.0.0
822817 1.2.* means >=1.2.0 <1.3.0
823818 """
824- # Strip wildcards and trailing dots, then extract version digits
825- cleaned = str (value ).replace ("*" , "" ).rstrip ("." )
819+ cleaned = str (value ).strip ().lstrip ("vV" ).replace ("*" , "" ).rstrip ("." )
826820 if not cleaned :
827821 raise ValueError ("Invalid wildcard pattern" )
828822
829- match = SEMVER_EXTRACT_RE .search (cleaned )
830- if match :
831- parts = match .group (1 ).split ("." )
832- else :
833- # Try single number (e.g., "1" from "1.*")
834- cleaned = cleaned .strip ()
835- if cleaned .isdigit ():
836- parts = [cleaned ]
837- else :
838- raise ValueError ("Invalid wildcard pattern" )
823+ parts = [p for p in cleaned .split ("." ) if p ]
824+ if not parts :
825+ raise ValueError ("Invalid wildcard pattern" )
839826
840827 if len (parts ) == 1 :
841828 major = int (parts [0 ])
0 commit comments