From 7ad1d0e0af695fa7f872b740a1bb7b2897eb41bd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 May 2023 11:25:01 +0200 Subject: Adding upstream version 0.8.1. Signed-off-by: Daniel Baumann --- eos_downloader/models/version.py | 272 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 eos_downloader/models/version.py (limited to 'eos_downloader/models/version.py') diff --git a/eos_downloader/models/version.py b/eos_downloader/models/version.py new file mode 100644 index 0000000..14448c1 --- /dev/null +++ b/eos_downloader/models/version.py @@ -0,0 +1,272 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +"""Module for EOS version management""" + +from __future__ import annotations + +import re +import typing +from typing import Any, Optional + +from loguru import logger +from pydantic import BaseModel + +from eos_downloader.tools import exc_to_str + +# logger = logging.getLogger(__name__) + +BASE_VERSION_STR = '4.0.0F' +BASE_BRANCH_STR = '4.0' + +RTYPE_FEATURE = 'F' +RTYPE_MAINTENANCE = 'M' +RTYPES = [RTYPE_FEATURE, RTYPE_MAINTENANCE] + +# Regular Expression to capture multiple EOS version format +# 4.24 +# 4.23.0 +# 4.21.1M +# 4.28.10.F +# 4.28.6.1M +REGEX_EOS_VERSION = re.compile(r"^.*(?P4)\.(?P\d{1,2})\.(?P\d{1,2})(?P\.\d*)*(?P[M,F])*$") +REGEX_EOS_BRANCH = re.compile(r"^.*(?P4)\.(?P\d{1,2})(\.?P\d)*(\.\d)*(?P[M,F])*$") + + +class EosVersion(BaseModel): + """ + EosVersion object to play with version management in code + + Since EOS is not using strictly semver approach, this class mimic some functions from semver lib for Arista EOS versions + It is based on Pydantic and provides helpers for comparison: + + Examples: + >>> eos_version_str = '4.23.2F' + >>> eos_version = EosVersion.from_str(eos_version_str) + >>> print(f'str representation is: {str(eos_version)}') + str representation is: 4.23.2F + + >>> other_version = EosVersion.from_str(other_version_str) + >>> print(f'eos_version < other_version: {eos_version < other_version}') + eos_version < other_version: True + + >>> print(f'Is eos_version match("<=4.23.3M"): {eos_version.match("<=4.23.3M")}') + Is eos_version match("<=4.23.3M"): True + + >>> print(f'Is eos_version in branch 4.23: {eos_version.is_in_branch("4.23.0")}') + Is eos_version in branch 4.23: True + + Args: + BaseModel (Pydantic): Pydantic Base Model + """ + major: int = 4 + minor: int = 0 + patch: int = 0 + rtype: Optional[str] = 'F' + other: Any + + @classmethod + def from_str(cls, eos_version: str) -> EosVersion: + """ + Class constructor from a string representing EOS version + + Use regular expresion to extract fields from string. + It supports following formats: + - 4.24 + - 4.23.0 + - 4.21.1M + - 4.28.10.F + - 4.28.6.1M + + Args: + eos_version (str): EOS version in str format + + Returns: + EosVersion object + """ + logger.debug(f'receiving version: {eos_version}') + if REGEX_EOS_VERSION.match(eos_version): + matches = REGEX_EOS_VERSION.match(eos_version) + # assert matches is not None + assert matches is not None + return cls(**matches.groupdict()) + if REGEX_EOS_BRANCH.match(eos_version): + matches = REGEX_EOS_BRANCH.match(eos_version) + # assert matches is not None + assert matches is not None + return cls(**matches.groupdict()) + logger.error(f'Error occured with {eos_version}') + return EosVersion() + + @property + def branch(self) -> str: + """ + Extract branch of version + + Returns: + str: branch from version + """ + return f'{self.major}.{self.minor}' + + def __str__(self) -> str: + """ + Standard str representation + + Return string for EOS version like 4.23.3M + + Returns: + str: A standard EOS version string representing .. + """ + if self.other is None: + return f'{self.major}.{self.minor}.{self.patch}{self.rtype}' + return f'{self.major}.{self.minor}.{self.patch}{self.other}{self.rtype}' + + def _compare(self, other: EosVersion) -> float: + """ + An internal comparison function to compare 2 EosVersion objects + + Do a deep comparison from Major to Release Type + The return value is + - negative if ver1 < ver2, + - zero if ver1 == ver2 + - strictly positive if ver1 > ver2 + + Args: + other (EosVersion): An EosVersion to compare with this object + + Raises: + ValueError: Raise ValueError if input is incorrect type + + Returns: + float: -1 if ver1 < ver2, 0 if ver1 == ver2, 1 if ver1 > ver2 + """ + if not isinstance(other, EosVersion): + raise ValueError(f'could not compare {other} as it is not an EosVersion object') + comparison_flag: float = 0 + logger.warning(f'current version {self.__str__()} - other {str(other)}') # pylint: disable = unnecessary-dunder-call + for key, _ in self.dict().items(): + if comparison_flag == 0 and self.dict()[key] is None or other.dict()[key] is None: + logger.debug(f'{key}: local None - remote None') + logger.debug(f'{key}: local {self.dict()} - remote {other.dict()}') + return comparison_flag + logger.debug(f'{key}: local {self.dict()[key]} - remote {other.dict()[key]}') + if comparison_flag == 0 and self.dict()[key] < other.dict()[key]: + comparison_flag = -1 + if comparison_flag == 0 and self.dict()[key] > other.dict()[key]: + comparison_flag = 1 + if comparison_flag != 0: + logger.info(f'comparison result is {comparison_flag}') + return comparison_flag + logger.info(f'comparison result is {comparison_flag}') + return comparison_flag + + @typing.no_type_check + def __eq__(self, other): + """ Implement __eq__ function (==) """ + return self._compare(other) == 0 + + @typing.no_type_check + def __ne__(self, other): + # type: ignore + """ Implement __nw__ function (!=) """ + return self._compare(other) != 0 + + @typing.no_type_check + def __lt__(self, other): + # type: ignore + """ Implement __lt__ function (<) """ + return self._compare(other) < 0 + + @typing.no_type_check + def __le__(self, other): + # type: ignore + """ Implement __le__ function (<=) """ + return self._compare(other) <= 0 + + @typing.no_type_check + def __gt__(self, other): + # type: ignore + """ Implement __gt__ function (>) """ + return self._compare(other) > 0 + + @typing.no_type_check + def __ge__(self, other): + # type: ignore + """ Implement __ge__ function (>=) """ + return self._compare(other) >= 0 + + def match(self, match_expr: str) -> bool: + """ + Compare self to match a match expression. + + Example: + >>> eos_version.match("<=4.23.3M") + True + >>> eos_version.match("==4.23.3M") + False + + Args: + match_expr (str): optional operator and version; valid operators are + ``<`` smaller than + ``>`` greater than + ``>=`` greator or equal than + ``<=`` smaller or equal than + ``==`` equal + ``!=`` not equal + + Raises: + ValueError: If input has no match_expr nor match_ver + + Returns: + bool: True if the expression matches the version, otherwise False + """ + prefix = match_expr[:2] + if prefix in (">=", "<=", "==", "!="): + match_version = match_expr[2:] + elif prefix and prefix[0] in (">", "<"): + prefix = prefix[0] + match_version = match_expr[1:] + elif match_expr and match_expr[0] in "0123456789": + prefix = "==" + match_version = match_expr + else: + raise ValueError( + "match_expr parameter should be in format , " + "where is one of " + "['<', '>', '==', '<=', '>=', '!=']. " + f"You provided: {match_expr}" + ) + logger.debug(f'work on comparison {prefix} with base release {match_version}') + possibilities_dict = { + ">": (1,), + "<": (-1,), + "==": (0,), + "!=": (-1, 1), + ">=": (0, 1), + "<=": (-1, 0), + } + possibilities = possibilities_dict[prefix] + cmp_res = self._compare(EosVersion.from_str(match_version)) + + return cmp_res in possibilities + + def is_in_branch(self, branch_str: str) -> bool: + """ + Check if current version is part of a branch version + + Comparison is done across MAJOR and MINOR + + Args: + branch_str (str): a string for EOS branch. It supports following formats 4.23 or 4.23.0 + + Returns: + bool: True if current version is in provided branch, otherwise False + """ + try: + logger.debug(f'reading branch str:{branch_str}') + branch = EosVersion.from_str(branch_str) + except Exception as error: # pylint: disable = broad-exception-caught + logger.error(exc_to_str(error)) + else: + return self.major == branch.major and self.minor == branch.minor + return False -- cgit v1.2.3