xref: /petsc/lib/petsc/bin/maint/petsclinter/petsclinter/__version__.py (revision 4c7cc9c8e01791debde927bc0816c9a347055c8f)
1#!/usr/bin/env python3
2"""
3# Created: Thu Dec  8 10:23:34 2022 (-0500)
4# @author: Jacob Faibussowitsch
5"""
6from __future__ import annotations
7
8import sys
9
10def _read_versions() -> tuple[tuple[int, int, int], str, tuple[int, int, int]]:
11  import os
12  import re
13
14  config_file = 'pyproject.toml'
15  try:
16    # since 3.11
17    # novermin
18    import tomllib # type: ignore[import]
19  except (ModuleNotFoundError, ImportError):
20    try:
21      import tomli as tomllib # type: ignore[import]
22    except (ModuleNotFoundError, ImportError):
23      try:
24        from pip._vendor import tomli as tomllib # type: ignore[import]
25      except (ModuleNotFoundError, ImportError) as mnfe:
26        raise RuntimeError(
27          f'No package installed to read the {config_file} file! Install tomli via '
28          'python3 -m pip install tomli'
29        ) from mnfe
30
31  def tuplify_version_str(version_str: str) -> tuple[int, int, int]:
32    assert isinstance(version_str, str)
33    version = list(map(int, version_str.split('.')))
34    while len(version) < 3:
35      version.append(0)
36    # type checkers complain:
37    #
38    # Incompatible return value type (got "Tuple[int, ...]", expected
39    # "Tuple[int, int, int]")  [return-value]
40    #
41    # but we know that version is of length 3, so we can safely ignore this
42    return tuple(version) # type: ignore[return-value]
43
44  # open ./../pyproject.toml
45  toml_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, config_file))
46  with open(toml_path, 'rb') as fd:
47    toml_data = tomllib.load(fd)
48
49  project_data     = toml_data['project']
50  version_str      = project_data['version']
51  version_tup      = tuplify_version_str(version_str)
52  py_version_match = re.search(r'([\d.]+)', project_data['requires-python'])
53  assert py_version_match is not None # pacify type checkers
54  min_py_version = tuplify_version_str(py_version_match.group(1))
55  return version_tup, version_str, min_py_version
56
57__version__, __version_str__, __MIN_PYTHON_VERSION__ = _read_versions()
58
59class RedundantMinVersionCheckError(Exception):
60  """
61  Exception thrown when code checks for minimum python version which is less than the minimum
62  requirement for petsclinter
63  """
64  pass
65
66def py_version_lt(major: int, minor: int, sub_minor: int = 0) -> bool:
67  r"""Determines if python version is less than a particular version.
68
69  This should be used whenever back-porting some code as it will automatically raise an
70  error if the version check is useless.
71
72  Parameters
73  ----------
74  major :
75    major version number, e.g. 3
76  minor :
77    minor version number
78  sub_minor : optional
79    sub-minor or patch version
80
81  Returns
82  -------
83  ret :
84    True if python version is less than `major`.`minor`.`sub_minor`, False otherwise
85
86  Raises
87  ------
88  RedundantMinVersionCheckError
89    If the given version is below petsclinter.__MIN_PYTHON_VERSION__ (and therefore the version check
90    is pointless) this raises RedundantMinVersionCheckError. This should not be caught.
91  """
92  version = (major, minor, sub_minor)
93  if version <= __MIN_PYTHON_VERSION__:
94    raise RedundantMinVersionCheckError(
95      f'Minimum required version {version_str()} already >= checked version {version}. '
96      f'There is no need to include this version check!'
97    )
98  return sys.version_info < version
99
100def version_tuple() -> tuple[int, int, int]:
101  r"""Return the package version as a tuple
102
103  Returns
104  -------
105  version :
106    the package version as a tuple
107  """
108  return __version__
109
110def version_str() -> str:
111  r"""Return the package version as a string
112
113  Returns
114  -------
115  version :
116    the package version as a string
117  """
118  return __version_str__
119