Source code for ramble.definitions.versions

# Copyright 2022-2026 The Ramble Authors
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.

from typing import Callable, Optional

from packaging.specifiers import SpecifierSet
from packaging.version import InvalidVersion, Version

import ramble.util.colors as rucolor
from ramble.language.language_base import DirectiveError


[docs] class ObjectVersion:
[docs] def default_conversion(self, version_str): return version_str
def __init__( self, version_number: str = "", version: Optional[Version] = None, description: str = "", origin_type: str = "", preferred: bool = False, version_to_pep440: Optional[Callable[[str], str]] = None, pep440_to_version: Optional[Callable[[str], str]] = None, ): self.version_to_pep440 = ( version_to_pep440 if version_to_pep440 is not None else self.default_conversion ) self.pep440_to_version = ( pep440_to_version if pep440_to_version is not None else self.default_conversion ) if version: self.version = version elif version_number: pep440_version = self.version_to_pep440(version_number) try: self.version = Version(pep440_version) except InvalidVersion as err: raise DirectiveError( f"Version number '{version_number}' (converted: {pep440_version}) must be " "converted to a valid PEP 440 version specifier format to use Ramble's " "versioning functionality. If this object uses version numbering that differs " "from PEP 440, please define the `version_to_pep440()` method. See " "https://peps.python.org/pep-0440/ for valid formats." ) from err else: raise DirectiveError( "An ObjectVersion requires either a Version object or a version number" ) self.version_number = version_number self.description = description self.origin_type = origin_type self.preferred = preferred
[docs] def copy(self): """Construct a copy of self and return it""" return ObjectVersion( version_number=self.get_version_num(), version=Version(str(self.version)), description=self.description, origin_type=self.origin_type, preferred=self.preferred, version_to_pep440=self.version_to_pep440, pep440_to_version=self.pep440_to_version, )
def __str__(self): return self.get_version_num() def __eq__(self, other): return str(self) == str(other)
[docs] def as_str(self, n_indent: int = 0, verbose: bool = False): """String representation of this version Args: n_indent (int): Number of spaces to indent string with verbose: Print verbose Returns: (str): Representation of this version """ indentation = " " * n_indent out_str = rucolor.section_title(f"{indentation}{self.version}") + "\n" out_str += rucolor.nested_1(f"{indentation} Description: ") + f"{self.description}\n" out_str += rucolor.nested_1(f"{indentation} Preferred: ") + f"{self.preferred}\n" return out_str
[docs] def get_version(self): """Returns the packaging.version.Version representation of this version""" return self.version
[docs] def get_version_num(self): """Returns the version number of this version""" if not self.version_number: self.version_number = self.pep440_to_version(str(self.version)) return self.version_number
[docs] def evaluate_conflicts(self, variant): """Error if this version conflicts with a variant that is used""" # TODO(dapomeroy): Implement logic to allow conflicts to be defined pass
[docs] def satisfies(self, variant): """Determine if an experiment's variant satisfies this version Args: variant: A version variant containing the "@" sigil Returns: (bool): True or False, based if the experiment's variant satisfies the version """ # Convert the variant syntax to a python packaging specifier set variant_name, value = variant.split("@") satisfied = False if value: sep_index = value.find(":") if sep_index == -1: spec_set = SpecifierSet(f"=={self.version_to_pep440(value)}", prereleases=True) elif sep_index == 0: spec_set = SpecifierSet( f"<={self.version_to_pep440(value.lstrip(':'))}", prereleases=True ) elif sep_index == len(value) - 1: spec_set = SpecifierSet( f">={self.version_to_pep440(value.rstrip(':'))}", prereleases=True ) else: start = value[:sep_index] end = value[sep_index + 1 :] spec_set = SpecifierSet( (f">={self.version_to_pep440(start)}," f"<={self.version_to_pep440(end)}"), prereleases=True, ) satisfied = spec_set.contains(self.version) return satisfied