Skip to content

Commit efc7d1c

Browse files
[3.13] GH-121970: Modernise the patchlevel extension (GH-121995) (#122060)
GH-121970: Modernise the patchlevel extension (GH-121995) (cherry picked from commit b7ad711) Co-authored-by: Adam Turner <[email protected]>
1 parent 1b4c5fe commit efc7d1c

File tree

3 files changed

+62
-54
lines changed

3 files changed

+62
-54
lines changed

Doc/.ruff.toml

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ extend-exclude = [
66
"includes/*",
77
# Temporary exclusions:
88
"tools/extensions/escape4chm.py",
9-
"tools/extensions/patchlevel.py",
109
"tools/extensions/pyspecific.py",
1110
]
1211

Doc/conf.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# The contents of this file are pickled, so don't put values in the namespace
77
# that aren't pickleable (module imports are okay, they're removed automatically).
88

9+
import importlib
910
import os
1011
import sys
1112
import time
@@ -64,9 +65,8 @@
6465

6566
# We look for the Include/patchlevel.h file in the current Python source tree
6667
# and replace the values accordingly.
67-
import patchlevel # noqa: E402
68-
69-
version, release = patchlevel.get_version_info()
68+
# See Doc/tools/extensions/patchlevel.py
69+
version, release = importlib.import_module('patchlevel').get_version_info()
7070

7171
rst_epilog = f"""
7272
.. |python_version_literal| replace:: ``Python {version}``

Doc/tools/extensions/patchlevel.py

+59-50
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,77 @@
1-
# -*- coding: utf-8 -*-
2-
"""
3-
patchlevel.py
4-
~~~~~~~~~~~~~
1+
"""Extract version information from Include/patchlevel.h."""
52

6-
Extract version info from Include/patchlevel.h.
7-
Adapted from Doc/tools/getversioninfo.
3+
import re
4+
import sys
5+
from pathlib import Path
6+
from typing import Literal, NamedTuple
87

9-
:copyright: 2007-2008 by Georg Brandl.
10-
:license: Python license.
11-
"""
8+
CPYTHON_ROOT = Path(
9+
__file__, # cpython/Doc/tools/extensions/patchlevel.py
10+
"..", # cpython/Doc/tools/extensions
11+
"..", # cpython/Doc/tools
12+
"..", # cpython/Doc
13+
"..", # cpython
14+
).resolve()
15+
PATCHLEVEL_H = CPYTHON_ROOT / "Include" / "patchlevel.h"
1216

13-
from __future__ import print_function
17+
RELEASE_LEVELS = {
18+
"PY_RELEASE_LEVEL_ALPHA": "alpha",
19+
"PY_RELEASE_LEVEL_BETA": "beta",
20+
"PY_RELEASE_LEVEL_GAMMA": "candidate",
21+
"PY_RELEASE_LEVEL_FINAL": "final",
22+
}
1423

15-
import os
16-
import re
17-
import sys
1824

19-
def get_header_version_info(srcdir):
20-
patchlevel_h = os.path.join(srcdir, '..', 'Include', 'patchlevel.h')
25+
class version_info(NamedTuple): # noqa: N801
26+
major: int #: Major release number
27+
minor: int #: Minor release number
28+
micro: int #: Patch release number
29+
releaselevel: Literal["alpha", "beta", "candidate", "final"]
30+
serial: int #: Serial release number
2131

22-
# This won't pick out all #defines, but it will pick up the ones we
23-
# care about.
24-
rx = re.compile(r'\s*#define\s+([a-zA-Z][a-zA-Z_0-9]*)\s+([a-zA-Z_0-9]+)')
2532

26-
d = {}
27-
with open(patchlevel_h) as f:
28-
for line in f:
29-
m = rx.match(line)
30-
if m is not None:
31-
name, value = m.group(1, 2)
32-
d[name] = value
33+
def get_header_version_info() -> version_info:
34+
# Capture PY_ prefixed #defines.
35+
pat = re.compile(r"\s*#define\s+(PY_\w*)\s+(\w+)", re.ASCII)
3336

34-
release = version = '%s.%s' % (d['PY_MAJOR_VERSION'], d['PY_MINOR_VERSION'])
35-
micro = int(d['PY_MICRO_VERSION'])
36-
release += '.' + str(micro)
37+
defines = {}
38+
patchlevel_h = PATCHLEVEL_H.read_text(encoding="utf-8")
39+
for line in patchlevel_h.splitlines():
40+
if (m := pat.match(line)) is not None:
41+
name, value = m.groups()
42+
defines[name] = value
3743

38-
level = d['PY_RELEASE_LEVEL']
39-
suffixes = {
40-
'PY_RELEASE_LEVEL_ALPHA': 'a',
41-
'PY_RELEASE_LEVEL_BETA': 'b',
42-
'PY_RELEASE_LEVEL_GAMMA': 'rc',
43-
}
44-
if level != 'PY_RELEASE_LEVEL_FINAL':
45-
release += suffixes[level] + str(int(d['PY_RELEASE_SERIAL']))
46-
return version, release
44+
return version_info(
45+
major=int(defines["PY_MAJOR_VERSION"]),
46+
minor=int(defines["PY_MINOR_VERSION"]),
47+
micro=int(defines["PY_MICRO_VERSION"]),
48+
releaselevel=RELEASE_LEVELS[defines["PY_RELEASE_LEVEL"]],
49+
serial=int(defines["PY_RELEASE_SERIAL"]),
50+
)
4751

4852

49-
def get_sys_version_info():
50-
major, minor, micro, level, serial = sys.version_info
51-
release = version = '%s.%s' % (major, minor)
52-
release += '.%s' % micro
53-
if level != 'final':
54-
release += '%s%s' % (level[0], serial)
53+
def format_version_info(info: version_info) -> tuple[str, str]:
54+
version = f"{info.major}.{info.minor}"
55+
release = f"{info.major}.{info.minor}.{info.micro}"
56+
if info.releaselevel != "final":
57+
suffix = {"alpha": "a", "beta": "b", "candidate": "rc"}
58+
release += f"{suffix[info.releaselevel]}{info.serial}"
5559
return version, release
5660

5761

5862
def get_version_info():
5963
try:
60-
return get_header_version_info('.')
61-
except (IOError, OSError):
62-
version, release = get_sys_version_info()
63-
print('Can\'t get version info from Include/patchlevel.h, ' \
64-
'using version of this interpreter (%s).' % release, file=sys.stderr)
64+
info = get_header_version_info()
65+
return format_version_info(info)
66+
except OSError:
67+
version, release = format_version_info(sys.version_info)
68+
print(
69+
f"Failed to get version info from Include/patchlevel.h, "
70+
f"using version of this interpreter ({release}).",
71+
file=sys.stderr,
72+
)
6573
return version, release
6674

67-
if __name__ == '__main__':
68-
print(get_header_version_info('.')[1])
75+
76+
if __name__ == "__main__":
77+
print(format_version_info(get_header_version_info())[1])

0 commit comments

Comments
 (0)