"""Extract version information from Include/patchlevel.h.""" import re import sys from pathlib import Path from typing import Literal, NamedTuple CPYTHON_ROOT = Path( __file__, # cpython/Doc/tools/extensions/patchlevel.py "..", # cpython/Doc/tools/extensions "..", # cpython/Doc/tools "..", # cpython/Doc "..", # cpython ).resolve() PATCHLEVEL_H = CPYTHON_ROOT / "Include" / "patchlevel.h" RELEASE_LEVELS = { "PY_RELEASE_LEVEL_ALPHA": "alpha", "PY_RELEASE_LEVEL_BETA": "beta", "PY_RELEASE_LEVEL_GAMMA": "candidate", "PY_RELEASE_LEVEL_FINAL": "final", } class version_info(NamedTuple): # noqa: N801 major: int #: Major release number minor: int #: Minor release number micro: int #: Patch release number releaselevel: Literal["alpha", "beta", "candidate", "final"] serial: int #: Serial release number def get_header_version_info() -> version_info: # Capture PY_ prefixed #defines. pat = re.compile(r"\s*#define\s+(PY_\w*)\s+(\w+)", re.ASCII) defines = {} patchlevel_h = PATCHLEVEL_H.read_text(encoding="utf-8") for line in patchlevel_h.splitlines(): if (m := pat.match(line)) is not None: name, value = m.groups() defines[name] = value return version_info( major=int(defines["PY_MAJOR_VERSION"]), minor=int(defines["PY_MINOR_VERSION"]), micro=int(defines["PY_MICRO_VERSION"]), releaselevel=RELEASE_LEVELS[defines["PY_RELEASE_LEVEL"]], serial=int(defines["PY_RELEASE_SERIAL"]), ) def format_version_info(info: version_info) -> tuple[str, str]: version = f"{info.major}.{info.minor}" release = f"{info.major}.{info.minor}.{info.micro}" if info.releaselevel != "final": suffix = {"alpha": "a", "beta": "b", "candidate": "rc"} release += f"{suffix[info.releaselevel]}{info.serial}" return version, release def get_version_info(): try: info = get_header_version_info() return format_version_info(info) except OSError: version, release = format_version_info(sys.version_info) print( f"Failed to get version info from Include/patchlevel.h, " f"using version of this interpreter ({release}).", file=sys.stderr, ) return version, release if __name__ == "__main__": short_ver, full_ver = format_version_info(get_header_version_info()) if sys.argv[1:2] == ["--short"]: print(short_ver) else: print(full_ver)