From af4cc378780ef1765562d2a743fa965a1063060d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:43:39 +0100 Subject: [PATCH 1/3] GH-121970: Modernise patchlevel --- Doc/conf.py | 2 +- Doc/tools/extensions/patchlevel.py | 107 ++++++++++++++++------------- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 5addee0378984a..443bac4d70e853 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -63,7 +63,7 @@ # We look for the Include/patchlevel.h file in the current Python source tree # and replace the values accordingly. -import patchlevel +import patchlevel # tools/extensions/patchlevel.py version, release = patchlevel.get_version_info() rst_epilog = f""" diff --git a/Doc/tools/extensions/patchlevel.py b/Doc/tools/extensions/patchlevel.py index 617f28c2527ddf..f116e7a0c08fbb 100644 --- a/Doc/tools/extensions/patchlevel.py +++ b/Doc/tools/extensions/patchlevel.py @@ -1,68 +1,77 @@ -# -*- coding: utf-8 -*- -""" - patchlevel.py - ~~~~~~~~~~~~~ - - Extract version info from Include/patchlevel.h. - Adapted from Doc/tools/getversioninfo. +"""Extract version info from Include/patchlevel.h. - :copyright: 2007-2008 by Georg Brandl. - :license: Python license. +Adapted from Doc/tools/getversioninfo. """ -from __future__ import print_function - -import os import re import sys +import typing +from pathlib import Path + +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', +} -def get_header_version_info(srcdir): - patchlevel_h = os.path.join(srcdir, '..', 'Include', 'patchlevel.h') - # This won't pick out all #defines, but it will pick up the ones we - # care about. - rx = re.compile(r'\s*#define\s+([a-zA-Z][a-zA-Z_0-9]*)\s+([a-zA-Z_0-9]+)') +class version_info(typing.NamedTuple): + major: int #: Major release number + minor: int #: Minor release number + micro: int #: Patch release number + releaselevel: typing.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) d = {} - with open(patchlevel_h) as f: - for line in f: - m = rx.match(line) - if m is not None: - name, value = m.group(1, 2) - d[name] = value - - release = version = '%s.%s' % (d['PY_MAJOR_VERSION'], d['PY_MINOR_VERSION']) - micro = int(d['PY_MICRO_VERSION']) - release += '.' + str(micro) - - level = d['PY_RELEASE_LEVEL'] - suffixes = { - 'PY_RELEASE_LEVEL_ALPHA': 'a', - 'PY_RELEASE_LEVEL_BETA': 'b', - 'PY_RELEASE_LEVEL_GAMMA': 'rc', - } - if level != 'PY_RELEASE_LEVEL_FINAL': - release += suffixes[level] + str(int(d['PY_RELEASE_SERIAL'])) - return version, release + 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() + d[name] = value + return version_info( + major=int(d['PY_MAJOR_VERSION']), + minor=int(d['PY_MINOR_VERSION']), + micro=int(d['PY_MICRO_VERSION']), + releaselevel=RELEASE_LEVELS[d['PY_RELEASE_LEVEL']], + serial=int(d['PY_RELEASE_SERIAL']), + ) -def get_sys_version_info(): - major, minor, micro, level, serial = sys.version_info - release = version = '%s.%s' % (major, minor) - release += '.%s' % micro - if level != 'final': - release += '%s%s' % (level[0], 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: - return get_header_version_info('.') - except (IOError, OSError): - version, release = get_sys_version_info() - print('Can\'t get version info from Include/patchlevel.h, ' \ - 'using version of this interpreter (%s).' % release, file=sys.stderr) + info = get_header_version_info() + return format_version_info(info) + except OSError: + version, release = format_version_info(sys.version_info) + print(f"Can't get version info from Include/patchlevel.h, " + f"using version of this interpreter ({release}).", file=sys.stderr) return version, release + if __name__ == '__main__': - print(get_header_version_info('.')[1]) + print(format_version_info(get_header_version_info())[1]) From c1966bfff3c53bd0a588c328bccc6110215d9d60 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:50:22 +0100 Subject: [PATCH 2/3] Apply Ruff --- Doc/.ruff.toml | 1 - Doc/conf.py | 5 ++- Doc/tools/extensions/patchlevel.py | 55 ++++++++++++++++-------------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Doc/.ruff.toml b/Doc/.ruff.toml index 5c40d90b8f000b..24f1c4f2ff6801 100644 --- a/Doc/.ruff.toml +++ b/Doc/.ruff.toml @@ -6,7 +6,6 @@ extend-exclude = [ "includes/*", # Temporary exclusions: "tools/extensions/escape4chm.py", - "tools/extensions/patchlevel.py", "tools/extensions/pyspecific.py", ] diff --git a/Doc/conf.py b/Doc/conf.py index bef9d2da1965c7..17e98e1a01ed21 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -6,6 +6,7 @@ # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). +import importlib import os import sys import time @@ -65,9 +66,7 @@ # We look for the Include/patchlevel.h file in the current Python source tree # and replace the values accordingly. # See Doc/tools/extensions/patchlevel.py -import patchlevel # noqa: E402 - -version, release = patchlevel.get_version_info() +version, release = importlib.import_module('patchlevel').get_version_info() rst_epilog = f""" .. |python_version_literal| replace:: ``Python {version}`` diff --git a/Doc/tools/extensions/patchlevel.py b/Doc/tools/extensions/patchlevel.py index f116e7a0c08fbb..f0671aea0902d9 100644 --- a/Doc/tools/extensions/patchlevel.py +++ b/Doc/tools/extensions/patchlevel.py @@ -10,55 +10,55 @@ CPYTHON_ROOT = Path( __file__, # cpython/Doc/tools/extensions/patchlevel.py - '..', # cpython/Doc/tools/extensions - '..', # cpython/Doc/tools - '..', # cpython/Doc - '..', # cpython + "..", # cpython/Doc/tools/extensions + "..", # cpython/Doc/tools + "..", # cpython/Doc + "..", # cpython ).resolve() -PATCHLEVEL_H = CPYTHON_ROOT / 'Include' / 'patchlevel.h' +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', + "PY_RELEASE_LEVEL_ALPHA": "alpha", + "PY_RELEASE_LEVEL_BETA": "beta", + "PY_RELEASE_LEVEL_GAMMA": "candidate", + "PY_RELEASE_LEVEL_FINAL": "final", } -class version_info(typing.NamedTuple): +class version_info(typing.NamedTuple): # noqa: N801 major: int #: Major release number minor: int #: Minor release number micro: int #: Patch release number - releaselevel: typing.Literal['alpha', 'beta', 'candidate', 'final'] + releaselevel: typing.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) + pat = re.compile(r"\s*#define\s+(PY_\w*)\s+(\w+)", re.ASCII) d = {} - patchlevel_h = PATCHLEVEL_H.read_text(encoding='utf-8') + 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() d[name] = value return version_info( - major=int(d['PY_MAJOR_VERSION']), - minor=int(d['PY_MINOR_VERSION']), - micro=int(d['PY_MICRO_VERSION']), - releaselevel=RELEASE_LEVELS[d['PY_RELEASE_LEVEL']], - serial=int(d['PY_RELEASE_SERIAL']), + major=int(d["PY_MAJOR_VERSION"]), + minor=int(d["PY_MINOR_VERSION"]), + micro=int(d["PY_MICRO_VERSION"]), + releaselevel=RELEASE_LEVELS[d["PY_RELEASE_LEVEL"]], + serial=int(d["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}' + 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 @@ -68,10 +68,13 @@ def get_version_info(): return format_version_info(info) except OSError: version, release = format_version_info(sys.version_info) - print(f"Can't get version info from Include/patchlevel.h, " - f"using version of this interpreter ({release}).", file=sys.stderr) + print( + f"Can't get version info from Include/patchlevel.h, " + f"using version of this interpreter ({release}).", + file=sys.stderr, + ) return version, release -if __name__ == '__main__': +if __name__ == "__main__": print(format_version_info(get_header_version_info())[1]) From a7c389a8efc0dbd73d74e688705c3dfe62d4b5ac Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:28:32 +0100 Subject: [PATCH 3/3] Formatting niceties --- Doc/tools/extensions/patchlevel.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Doc/tools/extensions/patchlevel.py b/Doc/tools/extensions/patchlevel.py index f0671aea0902d9..f2df6db47a2227 100644 --- a/Doc/tools/extensions/patchlevel.py +++ b/Doc/tools/extensions/patchlevel.py @@ -1,12 +1,9 @@ -"""Extract version info from Include/patchlevel.h. - -Adapted from Doc/tools/getversioninfo. -""" +"""Extract version information from Include/patchlevel.h.""" import re import sys -import typing from pathlib import Path +from typing import Literal, NamedTuple CPYTHON_ROOT = Path( __file__, # cpython/Doc/tools/extensions/patchlevel.py @@ -25,11 +22,11 @@ } -class version_info(typing.NamedTuple): # noqa: N801 +class version_info(NamedTuple): # noqa: N801 major: int #: Major release number minor: int #: Minor release number micro: int #: Patch release number - releaselevel: typing.Literal["alpha", "beta", "candidate", "final"] + releaselevel: Literal["alpha", "beta", "candidate", "final"] serial: int #: Serial release number @@ -37,19 +34,19 @@ def get_header_version_info() -> version_info: # Capture PY_ prefixed #defines. pat = re.compile(r"\s*#define\s+(PY_\w*)\s+(\w+)", re.ASCII) - d = {} + 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() - d[name] = value + defines[name] = value return version_info( - major=int(d["PY_MAJOR_VERSION"]), - minor=int(d["PY_MINOR_VERSION"]), - micro=int(d["PY_MICRO_VERSION"]), - releaselevel=RELEASE_LEVELS[d["PY_RELEASE_LEVEL"]], - serial=int(d["PY_RELEASE_SERIAL"]), + 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"]), ) @@ -69,7 +66,7 @@ def get_version_info(): except OSError: version, release = format_version_info(sys.version_info) print( - f"Can't get version info from Include/patchlevel.h, " + f"Failed to get version info from Include/patchlevel.h, " f"using version of this interpreter ({release}).", file=sys.stderr, )