File: utiltextversion.py

package info (click to toggle)
python-beartype 0.22.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,504 kB
  • sloc: python: 85,502; sh: 328; makefile: 30; javascript: 18
file content (137 lines) | stat: -rw-r--r-- 6,035 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.

'''
Project-wide **version specifier utilities** (i.e., low-level callables handling
human-readable ``.``-delimited version strings).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilTextVersionException
from beartype.typing import Tuple
from re import compile as re_compile

# ....................{ CONVERTERS                         }....................
def convert_str_version_to_tuple(version: str) -> Tuple[int, ...]:
    '''
    Convert the passed human-readable ``.``-delimited version string into a
    machine-readable version tuple of corresponding integers, suitable for
    efficient comparison against other such version tuples via standard rich
    comparison operators (e.g., ``<``, ``==``).

    Caveats
    -------
    **This converter strictly requires each ``.``-delimited substring of this
    string to be a non-negative integer.** The exception is the last
    ``.``-prefixed substring of this string, which this converter permits to
    *not* be a non-negative integer. Specifically, that last substring:

    * *Must* be prefixed by a non-negative integer.
    * *May* be followed by any other arbitrary characters, which this converter
      silently ignores as supplementary software-specific version metadata
      (e.g., release candidates, alpha releases, beta releases). Since that
      metadata does *not* cleanly generalize to all possible use cases, that
      metadata *cannot* be safely converted into a non-negative integer.

    For example, this converter:

    * Converts the valid version string ``"1.26.0"`` to ``(1, 26, 0)``.
    * Converts the valid version string ``"1.26.0rc1"`` to ``(1, 26, 0)`` by
      simply ignoring the non-numeric suffix ``"rc1"``.
    * Raises an exception for the invalid version string ``"1.26.rc1"``.

    Parameters
    ----------
    text : str
        Version string to be converted.

    Returns
    -------
    Tuple[int, ...]
        Machine-readable version tuple of corresponding integers.

    Raises
    ------
    _BeartypeUtilTextVersionException
        If this string is syntactically invalid as a version.
    '''
    assert isinstance(version, str), f'{repr(version)} not version string.'

    # List of either:
    # * If this version contains one or more "." delimiters, all "."-delimited
    #   version components split from this version.
    # * If this version contains *NO* "." delimiters, the 1-list "[version,]".
    version_substrs = version.split('.')

    # 0-based index of the last version component in this list.
    version_substr_index_last = len(version_substrs) - 1

    # List of all version components to be returned as a tuple.
    version_list = []

    # For the 0-based index of each "."-delimited version component of this
    # version string and that component...
    for version_substr_index, version_substr in enumerate(version_substrs):
        # Attempt to...
        try:
            # Coerce this version component into an integer.
            version_part = int(version_substr)

            # If this component is negative, raise an exception.
            if version_part < 0:
                raise _BeartypeUtilTextVersionException(
                    f'Version {repr(version)} syntactically invalid '
                    f'(i.e., version component {repr(version_substr)} negative).'
                )
            # Else, this component is non-negative.
        # If doing so raises a "ValueError", this version component is *NOT*
        # syntactically valid as an integer. In this case...
        except ValueError as exception:
            # If the 0-based index of this version component is that of the last
            # version component in this list, this is *NOT* the last version
            # component. In this case, this component is syntactically invalid.
            # Raise an exception.
            if version_substr_index != version_substr_index_last:
                raise _BeartypeUtilTextVersionException(
                    f'Version {repr(version)} syntactically invalid '
                    f'(i.e., version component {repr(version_substr)} '
                    f'not an integer).'
                ) from exception
            # Else, this is the last version component. In this case, reduce
            # this component to its non-negative integer prefix.

            # Match result if this component is prefixed by a non-negative
            # integer *OR* "None" otherwise (i.e., if this component is
            # syntactically invalid).
            version_substr_match = _VERSION_SUBSTR_LAST_REGEX.match(
                version_substr)

            # If this component is syntactically invalid, raise an exception.
            if version_substr_match is None:
                raise _BeartypeUtilTextVersionException(
                    f'Version {repr(version)} syntactically invalid '
                    f'(i.e., version component {repr(version_substr)} '
                    f'not an integer).'
                ) from exception
            # Else, this component is syntactically valid.

            # Non-negative integer prefixing this component.
            version_part = int(version_substr_match.group(1))

        # Append this version component to this list.
        version_list.append(version_part)

    # Return this list coerced into a tuple.
    return tuple(version_list)

# ....................{ PRIVATE ~ constants                }....................
_VERSION_SUBSTR_LAST_REGEX = re_compile(r'([0-9]+).+')
'''
Compiled regular expression matching the non-negative integer prefixing the last
``.``-delimited version component in a version string (e.g., ``"5"`` in the
version string ``"5rc27"``).
'''