Module cli_tool_audit.compatibility
Functions for checking compatibility between versions.
Expand source code
"""
Functions for checking compatibility between versions.
"""
import logging
import re
from typing import Any, Optional
from semver import Version
import cli_tool_audit.compatibility_complex as compatibility_complex
import cli_tool_audit.version_parsing as version_parsing
logger = logging.getLogger(__name__)
def split_version_match_pattern(pattern: str) -> tuple[Any, ...]:
"""
Split a version match pattern into a comparator and a version number.
Args:
pattern (str): The version match pattern.
Returns:
Tuple[Optional[str],Optional[str]]: A tuple with the first element being the comparator and the second element
being the version number.
Examples:
>>> split_version_match_pattern(">=1.1.1")
('>=', '1.1.1')
>>> split_version_match_pattern("1.1.1")
('', '1.1.1')
>>> split_version_match_pattern("==1.1.1")
('==', '1.1.1')
>>> split_version_match_pattern("~=1.1.1")
('~=', '1.1.1')
>>> split_version_match_pattern("!=1.1.1")
('!=', '1.1.1')
>>> split_version_match_pattern("<=1.1.1")
('<=', '1.1.1')
>>> split_version_match_pattern("<1.1.1")
('<', '1.1.1')
>>> split_version_match_pattern(">1.1.1")
('>', '1.1.1')
"""
# Regular expression for version match pattern
match_pattern_regex = r"(>=|<=|!=|>|<|==|~=|~|^)?(.*)"
# Search for the pattern
match = re.match(match_pattern_regex, pattern)
# Return the comparator and version number if match is found
if match:
return match.groups()
return None, None
CANT_TELL = "Can't tell"
def check_compatibility(desired_version: str, found_version: Optional[str]) -> tuple[str, Optional[Version]]:
"""
Check if a found version is compatible with a desired version. Uses semantic versioning.
When a version isn't semver, we attempt to convert it to semver.
Args:
desired_version (str): The desired version.
found_version (str): The found version.
Returns:
str: A string indicating if the versions are compatible or not.
Version: The parsed version if found.
Examples:
>>> check_compatibility(">=1.1.1", "1.1.1")
('Compatible', Version(major=1, minor=1, patch=1, prerelease=None, build=None))
>>> check_compatibility(">=1.1.1", "1.1.0")
('>=1.1.1 != 1.1.0', Version(major=1, minor=1, patch=0, prerelease=None, build=None))
>>> check_compatibility(">=1.1.1", "1.1.2")
('Compatible', Version(major=1, minor=1, patch=2, prerelease=None, build=None))
"""
if not found_version:
logger.info(f"Tool provided no versions, so can't tell. {desired_version}/{found_version}")
return CANT_TELL, None
# desired is a match expression, e.g. >=1.1.1
# Handle non-semver match patterns
symbols, desired_version_text = split_version_match_pattern(desired_version)
clean_desired_version = version_parsing.two_pass_semver_parse(desired_version_text)
if clean_desired_version:
desired_version = f"{symbols}{clean_desired_version}"
found_semversion = None
try:
found_semversion = version_parsing.two_pass_semver_parse(found_version)
if found_semversion is None:
logger.warning(f"SemVer failed to parse {desired_version}/{found_version}")
is_compatible = CANT_TELL
elif desired_version == "*":
# not picky, short circuit the logic.
is_compatible = "Compatible"
elif desired_version.startswith("^") or desired_version.startswith("~") or "*" in desired_version:
is_compatible = compatibility_complex.check_range_compatibility(desired_version, found_semversion)
elif found_semversion.match(desired_version):
is_compatible = "Compatible"
else:
is_compatible = f"{desired_version} != {found_semversion}"
except ValueError as value_error:
logger.warning(f"Can't tell {desired_version}/{found_version}: {value_error}")
is_compatible = CANT_TELL
except TypeError as type_error:
logger.warning(f"Can't tell {desired_version}/{found_version}: {type_error}")
is_compatible = CANT_TELL
return is_compatible, found_semversion
if __name__ == "__main__":
print(check_compatibility("^1.0.0", "8.2.3"))
Functions
def check_compatibility(desired_version: str, found_version: Optional[str]) ‑> tuple[str, typing.Optional[semver.version.Version]]-
Check if a found version is compatible with a desired version. Uses semantic versioning. When a version isn't semver, we attempt to convert it to semver.
Args
desired_version:str- The desired version.
found_version:str- The found version.
Returns
str- A string indicating if the versions are compatible or not.
Version- The parsed version if found.
Examples
>>> check_compatibility(">=1.1.1", "1.1.1") ('Compatible', Version(major=1, minor=1, patch=1, prerelease=None, build=None)) >>> check_compatibility(">=1.1.1", "1.1.0") ('>=1.1.1 != 1.1.0', Version(major=1, minor=1, patch=0, prerelease=None, build=None)) >>> check_compatibility(">=1.1.1", "1.1.2") ('Compatible', Version(major=1, minor=1, patch=2, prerelease=None, build=None))Expand source code
def check_compatibility(desired_version: str, found_version: Optional[str]) -> tuple[str, Optional[Version]]: """ Check if a found version is compatible with a desired version. Uses semantic versioning. When a version isn't semver, we attempt to convert it to semver. Args: desired_version (str): The desired version. found_version (str): The found version. Returns: str: A string indicating if the versions are compatible or not. Version: The parsed version if found. Examples: >>> check_compatibility(">=1.1.1", "1.1.1") ('Compatible', Version(major=1, minor=1, patch=1, prerelease=None, build=None)) >>> check_compatibility(">=1.1.1", "1.1.0") ('>=1.1.1 != 1.1.0', Version(major=1, minor=1, patch=0, prerelease=None, build=None)) >>> check_compatibility(">=1.1.1", "1.1.2") ('Compatible', Version(major=1, minor=1, patch=2, prerelease=None, build=None)) """ if not found_version: logger.info(f"Tool provided no versions, so can't tell. {desired_version}/{found_version}") return CANT_TELL, None # desired is a match expression, e.g. >=1.1.1 # Handle non-semver match patterns symbols, desired_version_text = split_version_match_pattern(desired_version) clean_desired_version = version_parsing.two_pass_semver_parse(desired_version_text) if clean_desired_version: desired_version = f"{symbols}{clean_desired_version}" found_semversion = None try: found_semversion = version_parsing.two_pass_semver_parse(found_version) if found_semversion is None: logger.warning(f"SemVer failed to parse {desired_version}/{found_version}") is_compatible = CANT_TELL elif desired_version == "*": # not picky, short circuit the logic. is_compatible = "Compatible" elif desired_version.startswith("^") or desired_version.startswith("~") or "*" in desired_version: is_compatible = compatibility_complex.check_range_compatibility(desired_version, found_semversion) elif found_semversion.match(desired_version): is_compatible = "Compatible" else: is_compatible = f"{desired_version} != {found_semversion}" except ValueError as value_error: logger.warning(f"Can't tell {desired_version}/{found_version}: {value_error}") is_compatible = CANT_TELL except TypeError as type_error: logger.warning(f"Can't tell {desired_version}/{found_version}: {type_error}") is_compatible = CANT_TELL return is_compatible, found_semversion def split_version_match_pattern(pattern: str) ‑> tuple[typing.Any, ...]-
Split a version match pattern into a comparator and a version number.
Args
pattern:str- The version match pattern.
Returns
Tuple[Optional[str],Optional[str]]- A tuple with the first element being the comparator and the second element being the version number.
Examples
>>> split_version_match_pattern(">=1.1.1") ('>=', '1.1.1') >>> split_version_match_pattern("1.1.1") ('', '1.1.1') >>> split_version_match_pattern("==1.1.1") ('==', '1.1.1') >>> split_version_match_pattern("~=1.1.1") ('~=', '1.1.1') >>> split_version_match_pattern("!=1.1.1") ('!=', '1.1.1') >>> split_version_match_pattern("<=1.1.1") ('<=', '1.1.1') >>> split_version_match_pattern("<1.1.1") ('<', '1.1.1') >>> split_version_match_pattern(">1.1.1") ('>', '1.1.1')Expand source code
def split_version_match_pattern(pattern: str) -> tuple[Any, ...]: """ Split a version match pattern into a comparator and a version number. Args: pattern (str): The version match pattern. Returns: Tuple[Optional[str],Optional[str]]: A tuple with the first element being the comparator and the second element being the version number. Examples: >>> split_version_match_pattern(">=1.1.1") ('>=', '1.1.1') >>> split_version_match_pattern("1.1.1") ('', '1.1.1') >>> split_version_match_pattern("==1.1.1") ('==', '1.1.1') >>> split_version_match_pattern("~=1.1.1") ('~=', '1.1.1') >>> split_version_match_pattern("!=1.1.1") ('!=', '1.1.1') >>> split_version_match_pattern("<=1.1.1") ('<=', '1.1.1') >>> split_version_match_pattern("<1.1.1") ('<', '1.1.1') >>> split_version_match_pattern(">1.1.1") ('>', '1.1.1') """ # Regular expression for version match pattern match_pattern_regex = r"(>=|<=|!=|>|<|==|~=|~|^)?(.*)" # Search for the pattern match = re.match(match_pattern_regex, pattern) # Return the comparator and version number if match is found if match: return match.groups() return None, None