opsml.registry.semver
1# Copyright (c) Shipt, Inc. 2# This source code is licensed under the MIT license found in the 3# LICENSE file in the root directory of this source tree. 4import re 5from enum import Enum 6from typing import Any, List, Optional 7 8import semver 9from pydantic import BaseModel, model_validator 10 11from opsml.helpers.exceptions import VersionError 12from opsml.helpers.logging import ArtifactLogger 13 14logger = ArtifactLogger.get_logger() 15 16 17class VersionType(str, Enum): 18 MAJOR = "major" 19 MINOR = "minor" 20 PATCH = "patch" 21 PRE = "pre" 22 BUILD = "build" 23 PRE_BUILD = "pre_build" 24 25 @staticmethod 26 def from_str(name: str) -> "VersionType": 27 l_name = name.strip().lower() 28 if l_name == "major": 29 return VersionType.MAJOR 30 if l_name == "minor": 31 return VersionType.MINOR 32 if l_name == "patch": 33 return VersionType.PATCH 34 if l_name == "pre": 35 return VersionType.PRE 36 if l_name == "build": 37 return VersionType.BUILD 38 if l_name == "pre_build": 39 return VersionType.PRE_BUILD 40 raise NotImplementedError() 41 42 43class CardVersion(BaseModel): 44 version: str 45 version_splits: List[str] = [] 46 is_full_semver: bool = False 47 48 @model_validator(mode="before") 49 @classmethod 50 def validate_inputs(cls, values: Any) -> Any: 51 """Validates a user-supplied version""" 52 version = values.get("version") 53 splits = version.split(".") 54 values["version_splits"] = splits 55 56 if cls.check_full_semver(splits): 57 values["is_full_semver"] = True 58 cls._validate_full_semver(version) 59 else: 60 values["is_full_semver"] = False 61 cls._validate_partial_semver(splits) 62 63 return values 64 65 @classmethod 66 def check_full_semver(cls, version_splits: List[str]) -> bool: 67 """Checks if a version is a full semver""" 68 return len(version_splits) >= 3 69 70 @classmethod 71 def _validate_full_semver(cls, version: str) -> None: 72 """Validates a full semver""" 73 if not semver.VersionInfo.isvalid(version): 74 raise ValueError("Version is not a valid Semver") 75 76 @classmethod 77 def _validate_partial_semver(cls, version_splits: List[str]) -> None: 78 """Validates a partial semver""" 79 try: 80 assert all((i.isdigit() for i in version_splits)) 81 except AssertionError as exc: 82 version = ".".join(version_splits) 83 raise AssertionError(f"Version {version} is not a valid semver or partial semver") from exc 84 85 def _get_version_split(self, split: int) -> str: 86 """Splits a version into its major, minor, and patch components""" 87 88 try: 89 return self.version_splits[split] 90 except IndexError as exc: 91 raise IndexError(f"Version split {split} not found: {self.version}") from exc 92 93 @property 94 def has_major_minor(self) -> bool: 95 """Checks if a version has a major and minor component""" 96 return len(self.version_splits) >= 2 97 98 @property 99 def major(self) -> str: 100 return self._get_version_split(0) 101 102 @property 103 def minor(self) -> str: 104 return self._get_version_split(1) 105 106 @property 107 def patch(self) -> str: 108 return self._get_version_split(2) 109 110 @property 111 def valid_version(self) -> str: 112 if self.is_full_semver: 113 return str(semver.VersionInfo.parse(self.version).finalize_version()) 114 return self.version 115 116 @staticmethod 117 def finalize_partial_version(version: str) -> str: 118 """Finalizes a partial semver version 119 120 Args: 121 version: 122 version to finalize 123 Returns: 124 str: finalized version 125 """ 126 version_splits = version.split(".") 127 128 if len(version_splits) == 1: 129 return f"{version}.0.0" 130 if len(version_splits) == 2: 131 return f"{version}.0" 132 133 return version 134 135 def get_version_to_search(self, version_type: VersionType) -> Optional[str]: 136 """Gets a version to search for in the database 137 138 Args: 139 version_type: 140 type of version to search for 141 Returns: 142 str: version to search for 143 """ 144 145 if version_type == VersionType.PATCH: # want to search major and minor if exists 146 if self.has_major_minor: 147 return f"{self.major}.{self.minor}" 148 return str(self.major) 149 150 if version_type == VersionType.MINOR: # want to search major 151 return str(self.major) 152 153 if version_type in [VersionType.PRE, VersionType.BUILD, VersionType.PRE_BUILD]: 154 return self.valid_version 155 156 return None 157 158 159class SemVerUtils: 160 """Class for general semver-related functions""" 161 162 @staticmethod 163 def sort_semvers(versions: List[str]) -> List[str]: 164 """Implements bubble sort for semvers 165 166 Args: 167 versions: 168 list of versions to sort 169 170 Returns: 171 sorted list of versions with highest version first 172 """ 173 174 n_ver = len(versions) 175 176 for i in range(n_ver): 177 already_sorted = True 178 179 for j in range(n_ver - i - 1): 180 j_version = semver.VersionInfo.parse(versions[j]) 181 j1_version = semver.VersionInfo.parse(versions[j + 1]) 182 183 # use semver comparison logic 184 if j_version > j1_version: 185 # swap 186 versions[j], versions[j + 1] = versions[j + 1], versions[j] 187 188 already_sorted = False 189 190 if already_sorted: 191 break 192 193 versions.reverse() 194 return versions 195 196 @staticmethod 197 def is_release_candidate(version: str) -> bool: 198 """Ignores pre-release versions""" 199 ver = semver.VersionInfo.parse(version) 200 return bool(ver.prerelease) 201 202 @staticmethod 203 def increment_version( 204 version: str, 205 version_type: VersionType, 206 pre_tag: str, 207 build_tag: str, 208 ) -> str: 209 """ 210 Increments a version based on version type 211 212 Args: 213 version: 214 Current version 215 version_type: 216 Type of version increment. 217 pre_tag: 218 Pre-release tag 219 build_tag: 220 Build tag 221 222 Raises: 223 ValueError: 224 unknown version_type 225 226 Returns: 227 New version 228 """ 229 ver: semver.VersionInfo = semver.VersionInfo.parse(version) 230 231 # Set major, minor, patch 232 if version_type == VersionType.MAJOR: 233 return str(ver.bump_major()) 234 if version_type == VersionType.MINOR: 235 return str(ver.bump_minor()) 236 if version_type == VersionType.PATCH: 237 return str(ver.bump_patch()) 238 239 # Set pre-release 240 if version_type == VersionType.PRE: 241 return str(ver.bump_prerelease(token=pre_tag)) 242 243 # Set build 244 if version_type == VersionType.BUILD: 245 return str(ver.bump_build(token=build_tag)) 246 247 if version_type == VersionType.PRE_BUILD: 248 ver = ver.bump_prerelease(token=pre_tag) 249 ver = ver.bump_build(token=build_tag) 250 251 return str(ver) 252 253 raise ValueError(f"Unknown version_type: {version_type}") 254 255 @staticmethod 256 def add_tags( 257 version: str, 258 pre_tag: Optional[str] = None, 259 build_tag: Optional[str] = None, 260 ) -> str: 261 if pre_tag is not None: 262 version = f"{version}-{pre_tag}" 263 if build_tag is not None: 264 version = f"{version}+{build_tag}" 265 266 return version 267 268 269class SemVerRegistryValidator: 270 """Class for obtaining the correct registry version""" 271 272 def __init__( 273 self, 274 name: str, 275 version_type: VersionType, 276 pre_tag: str, 277 build_tag: str, 278 version: Optional[CardVersion] = None, 279 ) -> None: 280 """Instantiate SemverValidator 281 282 Args: 283 name: 284 name of the artifact 285 version_type: 286 type of version increment 287 version: 288 version to use 289 pre_tag: 290 pre-release tag 291 build_tag: 292 build tag 293 294 Returns: 295 None 296 """ 297 self.version = version 298 self._version_to_search = None 299 self.final_version = None 300 self.version_type = version_type 301 self.name = name 302 self.pre_tag = pre_tag 303 self.build_tag = build_tag 304 305 @property 306 def version_to_search(self) -> Optional[str]: 307 """Parses version and returns version to search for in the registry""" 308 if self.version is not None: 309 return self.version.get_version_to_search(version_type=self.version_type) 310 return self._version_to_search 311 312 def _set_version_from_existing(self, versions: List[str]) -> str: 313 """Search existing versions to find the correct version to use 314 315 Args: 316 versions: 317 list of existing versions 318 319 Returns: 320 str: version to use 321 """ 322 version = versions[0] 323 recent_ver = semver.VersionInfo.parse(version) 324 # first need to check if increment is mmp 325 if self.version_type in [VersionType.MAJOR, VersionType.MINOR, VersionType.PATCH]: 326 # check if most recent version is a pre-release or build 327 if recent_ver.prerelease is not None: 328 version = str(recent_ver.finalize_version()) 329 try: 330 # if all versions are pre-release use finalized version 331 # if not, increment version 332 for ver in versions: 333 parsed_ver = semver.VersionInfo.parse(ver) 334 if parsed_ver.prerelease is None: 335 raise VersionError("Major, minor and patch version combination already exists") 336 return version 337 except VersionError: 338 logger.info("Major, minor and patch version combination already exists") 339 340 while version in versions: 341 version = SemVerUtils.increment_version( 342 version=version, 343 version_type=self.version_type, 344 pre_tag=self.pre_tag, 345 build_tag=self.build_tag, 346 ) 347 348 return version 349 350 def set_version(self, versions: List[str]) -> str: 351 """Sets the correct version to use for incrementing and adding the the registry 352 353 Args: 354 versions: 355 list of existing versions 356 357 Returns: 358 str: version to use 359 """ 360 if bool(versions): 361 return self._set_version_from_existing(versions=versions) 362 363 final_version = None 364 if self.version is not None: 365 final_version = CardVersion.finalize_partial_version(version=self.version.valid_version) 366 367 version = final_version or "1.0.0" 368 369 if self.version_type in [VersionType.PRE, VersionType.BUILD, VersionType.PRE_BUILD]: 370 return SemVerUtils.increment_version( 371 version=version, 372 version_type=self.version_type, 373 pre_tag=self.pre_tag, 374 build_tag=self.build_tag, 375 ) 376 377 return version 378 379 380class SemVerSymbols(str, Enum): 381 STAR = "*" 382 CARET = "^" 383 TILDE = "~" 384 385 386class SemVerParser: 387 """Base class for semver parsing""" 388 389 @staticmethod 390 def parse_version(version: str) -> str: 391 raise NotImplementedError 392 393 @staticmethod 394 def validate(version: str) -> bool: 395 raise NotImplementedError 396 397 398class StarParser(SemVerParser): 399 """Parses versions that contain * symbol""" 400 401 @staticmethod 402 def parse_version(version: str) -> str: 403 version_ = version.split(SemVerSymbols.STAR)[0] 404 return re.sub(".$", "", version_) 405 406 @staticmethod 407 def validate(version: str) -> bool: 408 return SemVerSymbols.STAR in version 409 410 411class CaretParser(SemVerParser): 412 """Parses versions that contain ^ symbol""" 413 414 @staticmethod 415 def parse_version(version: str) -> str: 416 return version.split(".")[0].replace(SemVerSymbols.CARET, "") 417 418 @staticmethod 419 def validate(version: str) -> bool: 420 return SemVerSymbols.CARET in version 421 422 423class TildeParser(SemVerParser): 424 """Parses versions that contain ~ symbol""" 425 426 @staticmethod 427 def parse_version(version: str) -> str: 428 return ".".join(version.split(".")[0:2]).replace(SemVerSymbols.TILDE, "") 429 430 @staticmethod 431 def validate(version: str) -> bool: 432 return SemVerSymbols.TILDE in version 433 434 435class NoParser(SemVerParser): 436 """Does not parse version""" 437 438 @staticmethod 439 def parse_version(version: str) -> str: 440 return version 441 442 @staticmethod 443 def validate(version: str) -> bool: 444 return version not in list(SemVerSymbols) 445 446 447def get_version_to_search(version: str) -> str: 448 """Parses a current version based on SemVer characters. 449 450 Args: 451 version (str): ArtifactCard version 452 453 Returns: 454 Version (str) to search based on presence of SemVer characters 455 """ 456 457 # gut check 458 if sum(symbol in version for symbol in SemVerSymbols) > 1: 459 raise ValueError("Only one SemVer character is allowed in the version string") 460 461 parser = next( 462 (parser for parser in SemVerParser.__subclasses__() if parser.validate(version=version)), 463 NoParser, 464 ) 465 return parser.parse_version(version=version)
18class VersionType(str, Enum): 19 MAJOR = "major" 20 MINOR = "minor" 21 PATCH = "patch" 22 PRE = "pre" 23 BUILD = "build" 24 PRE_BUILD = "pre_build" 25 26 @staticmethod 27 def from_str(name: str) -> "VersionType": 28 l_name = name.strip().lower() 29 if l_name == "major": 30 return VersionType.MAJOR 31 if l_name == "minor": 32 return VersionType.MINOR 33 if l_name == "patch": 34 return VersionType.PATCH 35 if l_name == "pre": 36 return VersionType.PRE 37 if l_name == "build": 38 return VersionType.BUILD 39 if l_name == "pre_build": 40 return VersionType.PRE_BUILD 41 raise NotImplementedError()
An enumeration.
26 @staticmethod 27 def from_str(name: str) -> "VersionType": 28 l_name = name.strip().lower() 29 if l_name == "major": 30 return VersionType.MAJOR 31 if l_name == "minor": 32 return VersionType.MINOR 33 if l_name == "patch": 34 return VersionType.PATCH 35 if l_name == "pre": 36 return VersionType.PRE 37 if l_name == "build": 38 return VersionType.BUILD 39 if l_name == "pre_build": 40 return VersionType.PRE_BUILD 41 raise NotImplementedError()
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
44class CardVersion(BaseModel): 45 version: str 46 version_splits: List[str] = [] 47 is_full_semver: bool = False 48 49 @model_validator(mode="before") 50 @classmethod 51 def validate_inputs(cls, values: Any) -> Any: 52 """Validates a user-supplied version""" 53 version = values.get("version") 54 splits = version.split(".") 55 values["version_splits"] = splits 56 57 if cls.check_full_semver(splits): 58 values["is_full_semver"] = True 59 cls._validate_full_semver(version) 60 else: 61 values["is_full_semver"] = False 62 cls._validate_partial_semver(splits) 63 64 return values 65 66 @classmethod 67 def check_full_semver(cls, version_splits: List[str]) -> bool: 68 """Checks if a version is a full semver""" 69 return len(version_splits) >= 3 70 71 @classmethod 72 def _validate_full_semver(cls, version: str) -> None: 73 """Validates a full semver""" 74 if not semver.VersionInfo.isvalid(version): 75 raise ValueError("Version is not a valid Semver") 76 77 @classmethod 78 def _validate_partial_semver(cls, version_splits: List[str]) -> None: 79 """Validates a partial semver""" 80 try: 81 assert all((i.isdigit() for i in version_splits)) 82 except AssertionError as exc: 83 version = ".".join(version_splits) 84 raise AssertionError(f"Version {version} is not a valid semver or partial semver") from exc 85 86 def _get_version_split(self, split: int) -> str: 87 """Splits a version into its major, minor, and patch components""" 88 89 try: 90 return self.version_splits[split] 91 except IndexError as exc: 92 raise IndexError(f"Version split {split} not found: {self.version}") from exc 93 94 @property 95 def has_major_minor(self) -> bool: 96 """Checks if a version has a major and minor component""" 97 return len(self.version_splits) >= 2 98 99 @property 100 def major(self) -> str: 101 return self._get_version_split(0) 102 103 @property 104 def minor(self) -> str: 105 return self._get_version_split(1) 106 107 @property 108 def patch(self) -> str: 109 return self._get_version_split(2) 110 111 @property 112 def valid_version(self) -> str: 113 if self.is_full_semver: 114 return str(semver.VersionInfo.parse(self.version).finalize_version()) 115 return self.version 116 117 @staticmethod 118 def finalize_partial_version(version: str) -> str: 119 """Finalizes a partial semver version 120 121 Args: 122 version: 123 version to finalize 124 Returns: 125 str: finalized version 126 """ 127 version_splits = version.split(".") 128 129 if len(version_splits) == 1: 130 return f"{version}.0.0" 131 if len(version_splits) == 2: 132 return f"{version}.0" 133 134 return version 135 136 def get_version_to_search(self, version_type: VersionType) -> Optional[str]: 137 """Gets a version to search for in the database 138 139 Args: 140 version_type: 141 type of version to search for 142 Returns: 143 str: version to search for 144 """ 145 146 if version_type == VersionType.PATCH: # want to search major and minor if exists 147 if self.has_major_minor: 148 return f"{self.major}.{self.minor}" 149 return str(self.major) 150 151 if version_type == VersionType.MINOR: # want to search major 152 return str(self.major) 153 154 if version_type in [VersionType.PRE, VersionType.BUILD, VersionType.PRE_BUILD]: 155 return self.valid_version 156 157 return None
Usage docs: https://docs.pydantic.dev/2.6/concepts/models/
A base class for creating Pydantic models.
Attributes:
- __class_vars__: The names of classvars defined on the model.
- __private_attributes__: Metadata about the private attributes of the model.
- __signature__: The signature for instantiating the model.
- __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
- __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer.
- __pydantic_custom_init__: Whether the model has a custom
__init__
function. - __pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces
Model.__validators__
andModel.__root_validators__
from Pydantic V1. - __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
- __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
- __pydantic_post_init__: The name of the post-init method for the model, if defined.
- __pydantic_root_model__: Whether the model is a
RootModel
. - __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the model.
- __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the model.
- __pydantic_extra__: An instance attribute with the values of extra fields from validation when
model_config['extra'] == 'allow'
. - __pydantic_fields_set__: An instance attribute with the names of fields explicitly set.
- __pydantic_private__: Instance attribute with the values of private attributes set on the model instance.
49 @model_validator(mode="before") 50 @classmethod 51 def validate_inputs(cls, values: Any) -> Any: 52 """Validates a user-supplied version""" 53 version = values.get("version") 54 splits = version.split(".") 55 values["version_splits"] = splits 56 57 if cls.check_full_semver(splits): 58 values["is_full_semver"] = True 59 cls._validate_full_semver(version) 60 else: 61 values["is_full_semver"] = False 62 cls._validate_partial_semver(splits) 63 64 return values
Validates a user-supplied version
66 @classmethod 67 def check_full_semver(cls, version_splits: List[str]) -> bool: 68 """Checks if a version is a full semver""" 69 return len(version_splits) >= 3
Checks if a version is a full semver
94 @property 95 def has_major_minor(self) -> bool: 96 """Checks if a version has a major and minor component""" 97 return len(self.version_splits) >= 2
Checks if a version has a major and minor component
117 @staticmethod 118 def finalize_partial_version(version: str) -> str: 119 """Finalizes a partial semver version 120 121 Args: 122 version: 123 version to finalize 124 Returns: 125 str: finalized version 126 """ 127 version_splits = version.split(".") 128 129 if len(version_splits) == 1: 130 return f"{version}.0.0" 131 if len(version_splits) == 2: 132 return f"{version}.0" 133 134 return version
Finalizes a partial semver version
Arguments:
- version: version to finalize
Returns:
str: finalized version
136 def get_version_to_search(self, version_type: VersionType) -> Optional[str]: 137 """Gets a version to search for in the database 138 139 Args: 140 version_type: 141 type of version to search for 142 Returns: 143 str: version to search for 144 """ 145 146 if version_type == VersionType.PATCH: # want to search major and minor if exists 147 if self.has_major_minor: 148 return f"{self.major}.{self.minor}" 149 return str(self.major) 150 151 if version_type == VersionType.MINOR: # want to search major 152 return str(self.major) 153 154 if version_type in [VersionType.PRE, VersionType.BUILD, VersionType.PRE_BUILD]: 155 return self.valid_version 156 157 return None
Gets a version to search for in the database
Arguments:
- version_type: type of version to search for
Returns:
str: version to search for
Inherited Members
- pydantic.main.BaseModel
- BaseModel
- model_extra
- model_fields_set
- model_construct
- model_copy
- model_dump
- model_dump_json
- model_json_schema
- model_parametrized_name
- model_post_init
- model_rebuild
- model_validate
- model_validate_json
- model_validate_strings
- dict
- json
- parse_obj
- parse_raw
- parse_file
- from_orm
- construct
- copy
- schema
- schema_json
- validate
- update_forward_refs
160class SemVerUtils: 161 """Class for general semver-related functions""" 162 163 @staticmethod 164 def sort_semvers(versions: List[str]) -> List[str]: 165 """Implements bubble sort for semvers 166 167 Args: 168 versions: 169 list of versions to sort 170 171 Returns: 172 sorted list of versions with highest version first 173 """ 174 175 n_ver = len(versions) 176 177 for i in range(n_ver): 178 already_sorted = True 179 180 for j in range(n_ver - i - 1): 181 j_version = semver.VersionInfo.parse(versions[j]) 182 j1_version = semver.VersionInfo.parse(versions[j + 1]) 183 184 # use semver comparison logic 185 if j_version > j1_version: 186 # swap 187 versions[j], versions[j + 1] = versions[j + 1], versions[j] 188 189 already_sorted = False 190 191 if already_sorted: 192 break 193 194 versions.reverse() 195 return versions 196 197 @staticmethod 198 def is_release_candidate(version: str) -> bool: 199 """Ignores pre-release versions""" 200 ver = semver.VersionInfo.parse(version) 201 return bool(ver.prerelease) 202 203 @staticmethod 204 def increment_version( 205 version: str, 206 version_type: VersionType, 207 pre_tag: str, 208 build_tag: str, 209 ) -> str: 210 """ 211 Increments a version based on version type 212 213 Args: 214 version: 215 Current version 216 version_type: 217 Type of version increment. 218 pre_tag: 219 Pre-release tag 220 build_tag: 221 Build tag 222 223 Raises: 224 ValueError: 225 unknown version_type 226 227 Returns: 228 New version 229 """ 230 ver: semver.VersionInfo = semver.VersionInfo.parse(version) 231 232 # Set major, minor, patch 233 if version_type == VersionType.MAJOR: 234 return str(ver.bump_major()) 235 if version_type == VersionType.MINOR: 236 return str(ver.bump_minor()) 237 if version_type == VersionType.PATCH: 238 return str(ver.bump_patch()) 239 240 # Set pre-release 241 if version_type == VersionType.PRE: 242 return str(ver.bump_prerelease(token=pre_tag)) 243 244 # Set build 245 if version_type == VersionType.BUILD: 246 return str(ver.bump_build(token=build_tag)) 247 248 if version_type == VersionType.PRE_BUILD: 249 ver = ver.bump_prerelease(token=pre_tag) 250 ver = ver.bump_build(token=build_tag) 251 252 return str(ver) 253 254 raise ValueError(f"Unknown version_type: {version_type}") 255 256 @staticmethod 257 def add_tags( 258 version: str, 259 pre_tag: Optional[str] = None, 260 build_tag: Optional[str] = None, 261 ) -> str: 262 if pre_tag is not None: 263 version = f"{version}-{pre_tag}" 264 if build_tag is not None: 265 version = f"{version}+{build_tag}" 266 267 return version
Class for general semver-related functions
163 @staticmethod 164 def sort_semvers(versions: List[str]) -> List[str]: 165 """Implements bubble sort for semvers 166 167 Args: 168 versions: 169 list of versions to sort 170 171 Returns: 172 sorted list of versions with highest version first 173 """ 174 175 n_ver = len(versions) 176 177 for i in range(n_ver): 178 already_sorted = True 179 180 for j in range(n_ver - i - 1): 181 j_version = semver.VersionInfo.parse(versions[j]) 182 j1_version = semver.VersionInfo.parse(versions[j + 1]) 183 184 # use semver comparison logic 185 if j_version > j1_version: 186 # swap 187 versions[j], versions[j + 1] = versions[j + 1], versions[j] 188 189 already_sorted = False 190 191 if already_sorted: 192 break 193 194 versions.reverse() 195 return versions
Implements bubble sort for semvers
Arguments:
- versions: list of versions to sort
Returns:
sorted list of versions with highest version first
197 @staticmethod 198 def is_release_candidate(version: str) -> bool: 199 """Ignores pre-release versions""" 200 ver = semver.VersionInfo.parse(version) 201 return bool(ver.prerelease)
Ignores pre-release versions
203 @staticmethod 204 def increment_version( 205 version: str, 206 version_type: VersionType, 207 pre_tag: str, 208 build_tag: str, 209 ) -> str: 210 """ 211 Increments a version based on version type 212 213 Args: 214 version: 215 Current version 216 version_type: 217 Type of version increment. 218 pre_tag: 219 Pre-release tag 220 build_tag: 221 Build tag 222 223 Raises: 224 ValueError: 225 unknown version_type 226 227 Returns: 228 New version 229 """ 230 ver: semver.VersionInfo = semver.VersionInfo.parse(version) 231 232 # Set major, minor, patch 233 if version_type == VersionType.MAJOR: 234 return str(ver.bump_major()) 235 if version_type == VersionType.MINOR: 236 return str(ver.bump_minor()) 237 if version_type == VersionType.PATCH: 238 return str(ver.bump_patch()) 239 240 # Set pre-release 241 if version_type == VersionType.PRE: 242 return str(ver.bump_prerelease(token=pre_tag)) 243 244 # Set build 245 if version_type == VersionType.BUILD: 246 return str(ver.bump_build(token=build_tag)) 247 248 if version_type == VersionType.PRE_BUILD: 249 ver = ver.bump_prerelease(token=pre_tag) 250 ver = ver.bump_build(token=build_tag) 251 252 return str(ver) 253 254 raise ValueError(f"Unknown version_type: {version_type}")
Increments a version based on version type
Arguments:
- version: Current version
- version_type: Type of version increment.
- pre_tag: Pre-release tag
- build_tag: Build tag
Raises:
- ValueError: unknown version_type
Returns:
New version
270class SemVerRegistryValidator: 271 """Class for obtaining the correct registry version""" 272 273 def __init__( 274 self, 275 name: str, 276 version_type: VersionType, 277 pre_tag: str, 278 build_tag: str, 279 version: Optional[CardVersion] = None, 280 ) -> None: 281 """Instantiate SemverValidator 282 283 Args: 284 name: 285 name of the artifact 286 version_type: 287 type of version increment 288 version: 289 version to use 290 pre_tag: 291 pre-release tag 292 build_tag: 293 build tag 294 295 Returns: 296 None 297 """ 298 self.version = version 299 self._version_to_search = None 300 self.final_version = None 301 self.version_type = version_type 302 self.name = name 303 self.pre_tag = pre_tag 304 self.build_tag = build_tag 305 306 @property 307 def version_to_search(self) -> Optional[str]: 308 """Parses version and returns version to search for in the registry""" 309 if self.version is not None: 310 return self.version.get_version_to_search(version_type=self.version_type) 311 return self._version_to_search 312 313 def _set_version_from_existing(self, versions: List[str]) -> str: 314 """Search existing versions to find the correct version to use 315 316 Args: 317 versions: 318 list of existing versions 319 320 Returns: 321 str: version to use 322 """ 323 version = versions[0] 324 recent_ver = semver.VersionInfo.parse(version) 325 # first need to check if increment is mmp 326 if self.version_type in [VersionType.MAJOR, VersionType.MINOR, VersionType.PATCH]: 327 # check if most recent version is a pre-release or build 328 if recent_ver.prerelease is not None: 329 version = str(recent_ver.finalize_version()) 330 try: 331 # if all versions are pre-release use finalized version 332 # if not, increment version 333 for ver in versions: 334 parsed_ver = semver.VersionInfo.parse(ver) 335 if parsed_ver.prerelease is None: 336 raise VersionError("Major, minor and patch version combination already exists") 337 return version 338 except VersionError: 339 logger.info("Major, minor and patch version combination already exists") 340 341 while version in versions: 342 version = SemVerUtils.increment_version( 343 version=version, 344 version_type=self.version_type, 345 pre_tag=self.pre_tag, 346 build_tag=self.build_tag, 347 ) 348 349 return version 350 351 def set_version(self, versions: List[str]) -> str: 352 """Sets the correct version to use for incrementing and adding the the registry 353 354 Args: 355 versions: 356 list of existing versions 357 358 Returns: 359 str: version to use 360 """ 361 if bool(versions): 362 return self._set_version_from_existing(versions=versions) 363 364 final_version = None 365 if self.version is not None: 366 final_version = CardVersion.finalize_partial_version(version=self.version.valid_version) 367 368 version = final_version or "1.0.0" 369 370 if self.version_type in [VersionType.PRE, VersionType.BUILD, VersionType.PRE_BUILD]: 371 return SemVerUtils.increment_version( 372 version=version, 373 version_type=self.version_type, 374 pre_tag=self.pre_tag, 375 build_tag=self.build_tag, 376 ) 377 378 return version
Class for obtaining the correct registry version
273 def __init__( 274 self, 275 name: str, 276 version_type: VersionType, 277 pre_tag: str, 278 build_tag: str, 279 version: Optional[CardVersion] = None, 280 ) -> None: 281 """Instantiate SemverValidator 282 283 Args: 284 name: 285 name of the artifact 286 version_type: 287 type of version increment 288 version: 289 version to use 290 pre_tag: 291 pre-release tag 292 build_tag: 293 build tag 294 295 Returns: 296 None 297 """ 298 self.version = version 299 self._version_to_search = None 300 self.final_version = None 301 self.version_type = version_type 302 self.name = name 303 self.pre_tag = pre_tag 304 self.build_tag = build_tag
Instantiate SemverValidator
Arguments:
- name: name of the artifact
- version_type: type of version increment
- version: version to use
- pre_tag: pre-release tag
- build_tag: build tag
Returns:
None
306 @property 307 def version_to_search(self) -> Optional[str]: 308 """Parses version and returns version to search for in the registry""" 309 if self.version is not None: 310 return self.version.get_version_to_search(version_type=self.version_type) 311 return self._version_to_search
Parses version and returns version to search for in the registry
351 def set_version(self, versions: List[str]) -> str: 352 """Sets the correct version to use for incrementing and adding the the registry 353 354 Args: 355 versions: 356 list of existing versions 357 358 Returns: 359 str: version to use 360 """ 361 if bool(versions): 362 return self._set_version_from_existing(versions=versions) 363 364 final_version = None 365 if self.version is not None: 366 final_version = CardVersion.finalize_partial_version(version=self.version.valid_version) 367 368 version = final_version or "1.0.0" 369 370 if self.version_type in [VersionType.PRE, VersionType.BUILD, VersionType.PRE_BUILD]: 371 return SemVerUtils.increment_version( 372 version=version, 373 version_type=self.version_type, 374 pre_tag=self.pre_tag, 375 build_tag=self.build_tag, 376 ) 377 378 return version
Sets the correct version to use for incrementing and adding the the registry
Arguments:
- versions: list of existing versions
Returns:
str: version to use
An enumeration.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
387class SemVerParser: 388 """Base class for semver parsing""" 389 390 @staticmethod 391 def parse_version(version: str) -> str: 392 raise NotImplementedError 393 394 @staticmethod 395 def validate(version: str) -> bool: 396 raise NotImplementedError
Base class for semver parsing
399class StarParser(SemVerParser): 400 """Parses versions that contain * symbol""" 401 402 @staticmethod 403 def parse_version(version: str) -> str: 404 version_ = version.split(SemVerSymbols.STAR)[0] 405 return re.sub(".$", "", version_) 406 407 @staticmethod 408 def validate(version: str) -> bool: 409 return SemVerSymbols.STAR in version
Parses versions that contain * symbol
412class CaretParser(SemVerParser): 413 """Parses versions that contain ^ symbol""" 414 415 @staticmethod 416 def parse_version(version: str) -> str: 417 return version.split(".")[0].replace(SemVerSymbols.CARET, "") 418 419 @staticmethod 420 def validate(version: str) -> bool: 421 return SemVerSymbols.CARET in version
Parses versions that contain ^ symbol
424class TildeParser(SemVerParser): 425 """Parses versions that contain ~ symbol""" 426 427 @staticmethod 428 def parse_version(version: str) -> str: 429 return ".".join(version.split(".")[0:2]).replace(SemVerSymbols.TILDE, "") 430 431 @staticmethod 432 def validate(version: str) -> bool: 433 return SemVerSymbols.TILDE in version
Parses versions that contain ~ symbol
436class NoParser(SemVerParser): 437 """Does not parse version""" 438 439 @staticmethod 440 def parse_version(version: str) -> str: 441 return version 442 443 @staticmethod 444 def validate(version: str) -> bool: 445 return version not in list(SemVerSymbols)
Does not parse version
448def get_version_to_search(version: str) -> str: 449 """Parses a current version based on SemVer characters. 450 451 Args: 452 version (str): ArtifactCard version 453 454 Returns: 455 Version (str) to search based on presence of SemVer characters 456 """ 457 458 # gut check 459 if sum(symbol in version for symbol in SemVerSymbols) > 1: 460 raise ValueError("Only one SemVer character is allowed in the version string") 461 462 parser = next( 463 (parser for parser in SemVerParser.__subclasses__() if parser.validate(version=version)), 464 NoParser, 465 ) 466 return parser.parse_version(version=version)
Parses a current version based on SemVer characters.
Arguments:
- version (str): ArtifactCard version
Returns:
Version (str) to search based on presence of SemVer characters