Module cli_tool_audit.version_parsing

Expand source code
import re
from typing import Optional

import packaging.specifiers as ps
import packaging.version
import semver
from semver import Version


def extract_first_two_part_version(input_string: str) -> Optional[str]:
    """
    Extract the first 2 part version from a string.
    Args:
        input_string (str): The string to parse.

    Returns:
        Optional[str]: The first 2 part version if found, None otherwise.

    Examples:
        >>> extract_first_two_part_version("1.2.3")
        '1.2'
        >>> extract_first_two_part_version("1.2")
        '1.2'
        >>> extract_first_two_part_version("1") is None
        True
        >>> extract_first_two_part_version("1.2.3a1")
        '1.2'
        >>> extract_first_two_part_version("1.2.3a1.post1")
        '1.2'
    """
    # Regular expression for 2 part version
    semver_regex = r"\d+\.\d+"

    # Find all matches in the string
    matches = re.findall(semver_regex, input_string)

    # Return the first match or None if no match is found
    return matches[0] if matches else None


def extract_first_semver_version(input_string: str) -> Optional[str]:
    """
    Extract the first semver version from a string.
    Args:
        input_string (str): The string to parse.

    Returns:
        Optional[str]: The first semver version if found, None otherwise.

    Examples:
        >>> extract_first_semver_version("1.2.3")
        '1.2.3'
        >>> extract_first_semver_version("1.2") is None
        True
        >>> extract_first_semver_version("1.2.3a1")
        '1.2.3'
        >>> extract_first_semver_version("1.2.3a1.post1")
        '1.2.3'

    """
    # Regular expression for semver version
    semver_regex = r"\d+\.\d+\.\d+"

    # Find all matches in the string
    matches = re.findall(semver_regex, input_string)

    # Return the first match or None if no match is found
    return matches[0] if matches else None


# packaging.version.Version
def convert2semver(version: packaging.version.Version) -> semver.Version:
    """Converts a PyPI version into a semver version

    Args:
        version (packaging.version.Version): the PyPI version

    Returns:
        semver.Version: the semver version

    Raises:
        ValueError: if epoch or post parts are used
    """
    if version.epoch:
        raise ValueError("Can't convert an epoch to semver")
    if version.post:
        raise ValueError("Can't convert a post part to semver")

    pre = None if not version.pre else "".join([str(i) for i in version.pre])

    if len(version.release) == 3:
        major, minor, patch = version.release
        return semver.Version(major, minor, patch, prerelease=pre, build=version.dev)
    major, minor = version.release
    return semver.Version(major, minor, prerelease=pre, build=version.dev)


def two_pass_semver_parse(input_string: str) -> Optional[Version]:
    """
    Parse a string into a semver version. This function will attempt to parse the string twice.
    The first pass will attempt to parse the string as a semver version. If that fails, the second pass will attempt to
    parse the string as a PyPI version. If that fails, the third pass will attempt to parse the string as a 2 part
    version. If that fails, the fourth pass will attempt to parse the string as a 1 part version. If that fails, None
    is returned.

    Args:
        input_string (str): The string to parse.

    Returns:
        Optional[Version]: A semver version if the string can be parsed, None otherwise.

    Examples:
        >>> two_pass_semver_parse("1.2.3")
        Version(major=1, minor=2, patch=3, prerelease=None, build=None)
        >>> two_pass_semver_parse("1.2")
        Version(major=1, minor=2, patch=0, prerelease=None, build=None)
        >>> two_pass_semver_parse("1.2.3a1")
        Version(major=1, minor=2, patch=3, prerelease='a1', build=None)
        >>> two_pass_semver_parse("1.2.3a1.post1")
        Version(major=1, minor=2, patch=3, prerelease=None, build=None)
    """
    # empty never works
    if not input_string:
        return None

    # Clean semver string
    try:
        possible = Version.parse(input_string)
        return possible
    except ValueError:
        pass
    except TypeError:
        pass

    # Clean pypi version, including 2 part versions

    # pylint: disable=broad-exception-caught
    try:
        pypi_version = ps.Version(input_string)
        possible = convert2semver(pypi_version)
        if possible:
            return possible
    except BaseException:
        pass

    possible_first = extract_first_semver_version(input_string)
    if possible_first:
        try:
            possible = Version.parse(possible_first)
            return possible
        except ValueError:
            pass
        except TypeError:
            pass

    possible_first = extract_first_two_part_version(input_string)
    if possible_first:
        try:
            pypi_version = ps.Version(possible_first)
            possible = convert2semver(pypi_version)
            return possible
        except ValueError:
            pass
        except TypeError:
            pass
    # Give up. This doesn't appear to be semver
    return None


if __name__ == "__main__":
    convert2semver(packaging.version.Version("1.2"))

    convert2semver(packaging.version.Version("1.2.3"))

Functions

def convert2semver(version: packaging.version.Version) ‑> semver.version.Version

Converts a PyPI version into a semver version

Args

version : packaging.version.Version
the PyPI version

Returns

semver.Version
the semver version

Raises

ValueError
if epoch or post parts are used
Expand source code
def convert2semver(version: packaging.version.Version) -> semver.Version:
    """Converts a PyPI version into a semver version

    Args:
        version (packaging.version.Version): the PyPI version

    Returns:
        semver.Version: the semver version

    Raises:
        ValueError: if epoch or post parts are used
    """
    if version.epoch:
        raise ValueError("Can't convert an epoch to semver")
    if version.post:
        raise ValueError("Can't convert a post part to semver")

    pre = None if not version.pre else "".join([str(i) for i in version.pre])

    if len(version.release) == 3:
        major, minor, patch = version.release
        return semver.Version(major, minor, patch, prerelease=pre, build=version.dev)
    major, minor = version.release
    return semver.Version(major, minor, prerelease=pre, build=version.dev)
def extract_first_semver_version(input_string: str) ‑> Optional[str]

Extract the first semver version from a string.

Args

input_string : str
The string to parse.

Returns

Optional[str]
The first semver version if found, None otherwise.

Examples

>>> extract_first_semver_version("1.2.3")
'1.2.3'
>>> extract_first_semver_version("1.2") is None
True
>>> extract_first_semver_version("1.2.3a1")
'1.2.3'
>>> extract_first_semver_version("1.2.3a1.post1")
'1.2.3'
Expand source code
def extract_first_semver_version(input_string: str) -> Optional[str]:
    """
    Extract the first semver version from a string.
    Args:
        input_string (str): The string to parse.

    Returns:
        Optional[str]: The first semver version if found, None otherwise.

    Examples:
        >>> extract_first_semver_version("1.2.3")
        '1.2.3'
        >>> extract_first_semver_version("1.2") is None
        True
        >>> extract_first_semver_version("1.2.3a1")
        '1.2.3'
        >>> extract_first_semver_version("1.2.3a1.post1")
        '1.2.3'

    """
    # Regular expression for semver version
    semver_regex = r"\d+\.\d+\.\d+"

    # Find all matches in the string
    matches = re.findall(semver_regex, input_string)

    # Return the first match or None if no match is found
    return matches[0] if matches else None
def extract_first_two_part_version(input_string: str) ‑> Optional[str]

Extract the first 2 part version from a string.

Args

input_string : str
The string to parse.

Returns

Optional[str]
The first 2 part version if found, None otherwise.

Examples

>>> extract_first_two_part_version("1.2.3")
'1.2'
>>> extract_first_two_part_version("1.2")
'1.2'
>>> extract_first_two_part_version("1") is None
True
>>> extract_first_two_part_version("1.2.3a1")
'1.2'
>>> extract_first_two_part_version("1.2.3a1.post1")
'1.2'
Expand source code
def extract_first_two_part_version(input_string: str) -> Optional[str]:
    """
    Extract the first 2 part version from a string.
    Args:
        input_string (str): The string to parse.

    Returns:
        Optional[str]: The first 2 part version if found, None otherwise.

    Examples:
        >>> extract_first_two_part_version("1.2.3")
        '1.2'
        >>> extract_first_two_part_version("1.2")
        '1.2'
        >>> extract_first_two_part_version("1") is None
        True
        >>> extract_first_two_part_version("1.2.3a1")
        '1.2'
        >>> extract_first_two_part_version("1.2.3a1.post1")
        '1.2'
    """
    # Regular expression for 2 part version
    semver_regex = r"\d+\.\d+"

    # Find all matches in the string
    matches = re.findall(semver_regex, input_string)

    # Return the first match or None if no match is found
    return matches[0] if matches else None
def two_pass_semver_parse(input_string: str) ‑> Optional[semver.version.Version]

Parse a string into a semver version. This function will attempt to parse the string twice. The first pass will attempt to parse the string as a semver version. If that fails, the second pass will attempt to parse the string as a PyPI version. If that fails, the third pass will attempt to parse the string as a 2 part version. If that fails, the fourth pass will attempt to parse the string as a 1 part version. If that fails, None is returned.

Args

input_string : str
The string to parse.

Returns

Optional[Version]
A semver version if the string can be parsed, None otherwise.

Examples

>>> two_pass_semver_parse("1.2.3")
Version(major=1, minor=2, patch=3, prerelease=None, build=None)
>>> two_pass_semver_parse("1.2")
Version(major=1, minor=2, patch=0, prerelease=None, build=None)
>>> two_pass_semver_parse("1.2.3a1")
Version(major=1, minor=2, patch=3, prerelease='a1', build=None)
>>> two_pass_semver_parse("1.2.3a1.post1")
Version(major=1, minor=2, patch=3, prerelease=None, build=None)
Expand source code
def two_pass_semver_parse(input_string: str) -> Optional[Version]:
    """
    Parse a string into a semver version. This function will attempt to parse the string twice.
    The first pass will attempt to parse the string as a semver version. If that fails, the second pass will attempt to
    parse the string as a PyPI version. If that fails, the third pass will attempt to parse the string as a 2 part
    version. If that fails, the fourth pass will attempt to parse the string as a 1 part version. If that fails, None
    is returned.

    Args:
        input_string (str): The string to parse.

    Returns:
        Optional[Version]: A semver version if the string can be parsed, None otherwise.

    Examples:
        >>> two_pass_semver_parse("1.2.3")
        Version(major=1, minor=2, patch=3, prerelease=None, build=None)
        >>> two_pass_semver_parse("1.2")
        Version(major=1, minor=2, patch=0, prerelease=None, build=None)
        >>> two_pass_semver_parse("1.2.3a1")
        Version(major=1, minor=2, patch=3, prerelease='a1', build=None)
        >>> two_pass_semver_parse("1.2.3a1.post1")
        Version(major=1, minor=2, patch=3, prerelease=None, build=None)
    """
    # empty never works
    if not input_string:
        return None

    # Clean semver string
    try:
        possible = Version.parse(input_string)
        return possible
    except ValueError:
        pass
    except TypeError:
        pass

    # Clean pypi version, including 2 part versions

    # pylint: disable=broad-exception-caught
    try:
        pypi_version = ps.Version(input_string)
        possible = convert2semver(pypi_version)
        if possible:
            return possible
    except BaseException:
        pass

    possible_first = extract_first_semver_version(input_string)
    if possible_first:
        try:
            possible = Version.parse(possible_first)
            return possible
        except ValueError:
            pass
        except TypeError:
            pass

    possible_first = extract_first_two_part_version(input_string)
    if possible_first:
        try:
            pypi_version = ps.Version(possible_first)
            possible = convert2semver(pypi_version)
            return possible
        except ValueError:
            pass
        except TypeError:
            pass
    # Give up. This doesn't appear to be semver
    return None