_version.py (3895B)
1 # File: _version.py 2 # Created Date: Tuesday December 20th 2022 3 # Author: Steven Atkinson (steven@atkinson.mn) 4 5 """ 6 Version utility 7 """ 8 9 from typing import Optional as _Optional 10 11 from .._version import __version__ 12 13 14 class IncomparableVersionError(ValueError): 15 """ 16 Error raised when two versions can't be compared. 17 """ 18 19 pass 20 21 22 class Version: 23 def __init__(self, major: int, minor: int, patch: int, dev: _Optional[str] = None): 24 self.major = major 25 self.minor = minor 26 self.patch = patch 27 self.dev = dev 28 self.dev_int = self._parse_dev_int(dev) 29 30 @classmethod 31 def from_string(cls, s: str): 32 def special_case(s: str) -> _Optional[dict]: 33 """ 34 Regretful hacks 35 """ 36 # It seems like the git repo isn't accessible to setuptools_scm's version 37 # guesser, so it comes up with this during install: 38 if s == "0.1.dev1": 39 # This will be fine. 40 return { 41 "major": 0, 42 "minor": 1, 43 "patch": 0, 44 "dev": "dev1" 45 } 46 return None 47 48 if special_case(s) is not None: 49 return cls(**special_case(s)) 50 51 # Typical 52 parts = s.split(".") 53 if len(parts) == 3: # e.g. "0.7.1" 54 dev = None 55 elif len(parts) == 4: # e.g. "0.7.1.dev7" 56 dev = parts[3] 57 else: 58 raise ValueError(f"Invalid version string {s}") 59 try: 60 major, minor, patch = [int(x) for x in parts[:3]] 61 except ValueError as e: 62 raise ValueError(f"Failed to parse version from string '{s}':\n{e}") 63 return cls(major=major, minor=minor, patch=patch, dev=dev) 64 65 def __eq__(self, other) -> bool: 66 return ( 67 self.major == other.major 68 and self.minor == other.minor 69 and self.patch == other.patch 70 and self.dev == other.dev 71 ) 72 73 def __lt__(self, other) -> bool: 74 if self == other: 75 return False 76 if self.major != other.major: 77 return self.major < other.major 78 if self.minor != other.minor: 79 return self.minor < other.minor 80 if self.patch != other.patch: 81 return self.patch < other.patch 82 if self.dev != other.dev: 83 # None is defined as least 84 if self.dev is None and other.dev is not None: 85 return True 86 elif self.dev is not None and other.dev is None: 87 return False 88 assert self.dev is not None 89 assert other.dev is not None 90 if self.dev_int is None: 91 raise IncomparableVersionError( 92 f"Version {str(self)} has incomparable dev version {self.dev}" 93 ) 94 if other.dev_int is None: 95 raise IncomparableVersionError( 96 f"Version {str(other)} has incomparable dev version {other.dev}" 97 ) 98 return self.dev_int < other.dev_int 99 raise RuntimeError( 100 f"Unhandled comparison between versions {str(self)} and {str(other)}" 101 ) 102 103 def __str__(self) -> str: 104 return f"{self.major}.{self.minor}.{self.patch}" 105 106 def _parse_dev_int(self, dev: _Optional[str]) -> _Optional[int]: 107 """ 108 Turn the string into an int that can be compared if possible. 109 """ 110 if dev is None: 111 return None 112 if not isinstance(dev, str): 113 raise TypeError(f"Invalid dev string type {type(dev)}") 114 if not dev.startswith("dev") or len(dev) <= 3: # "misc", "dev", etc 115 return None 116 return int(dev.removeprefix("dev")) 117 118 119 PROTEUS_VERSION = Version(4, 0, 0) 120 121 122 def get_current_version() -> Version: 123 return Version.from_string(__version__)