Skip to content

Commit 4a5a7b9

Browse files
gh-157514: Add structured version info for tkinter (GH-157516)
Add constants tkinter.TCL_VERSION_INFO and tkinter.TK_VERSION_INFO, named tuples with the versions of the Tcl and Tk libraries that were used for building the module, and _tkinter.TCL_PATCH_LEVEL and _tkinter.TK_PATCH_LEVEL strings. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent cbecd5e commit 4a5a7b9

7 files changed

Lines changed: 98 additions & 25 deletions

File tree

‎Doc/library/tkinter.rst‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6898,3 +6898,32 @@ and :mod:`!tkinter.constants` namespaces.
68986898
DOTBOX
68996899

69006900
Other option values: ``'underline'``, ``'numeric'`` and ``'dotbox'``.
6901+
6902+
6903+
Version information
6904+
^^^^^^^^^^^^^^^^^^^
6905+
6906+
.. data:: TCL_VERSION_INFO
6907+
TK_VERSION_INFO
6908+
6909+
The versions of the Tcl and Tk libraries that were used for building
6910+
the :mod:`!_tkinter` module, as named tuples with the same five fields
6911+
as :data:`sys.version_info`: *major*, *minor*, *micro*, *releaselevel*
6912+
and *serial*.
6913+
*releaselevel* is ``'alpha'``, ``'beta'`` or ``'final'``.
6914+
Converting them to a string gives the version in the usual Tcl/Tk notation,
6915+
for example ``'9.0.3'`` for a final release or ``'9.1b2'`` for a
6916+
pre-release.
6917+
These may be different from the libraries actually used at runtime,
6918+
which are available as :meth:`Misc.info_patchlevel` (the Tcl version)
6919+
and the ``tk_patchLevel`` Tcl variable.
6920+
6921+
.. versionadded:: next
6922+
6923+
.. data:: TkVersion
6924+
TclVersion
6925+
6926+
The major and minor version of the Tk and Tcl libraries that were used
6927+
for building the :mod:`!_tkinter` module, as floats, for example ``8.6``.
6928+
Prefer :data:`TK_VERSION_INFO` and :data:`TCL_VERSION_INFO`, which are
6929+
more precise and compare correctly.

‎Doc/whatsnew/3.16.rst‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,11 @@ tkinter
632632
methods (without recursion) also work on Tk older than 9.1.
633633
(Contributed by Serhiy Storchaka in :gh:`151910`.)
634634

635+
* Added constants :const:`~tkinter.TCL_VERSION_INFO` and
636+
:const:`~tkinter.TK_VERSION_INFO`, named tuples with the versions of
637+
the Tcl and Tk libraries that were used for building the module.
638+
(Contributed by Serhiy Storchaka in :gh:`157514`.)
639+
635640
* Added new :class:`!tkinter.Text` methods :meth:`~tkinter.Text.edit_canundo`
636641
and :meth:`~tkinter.Text.edit_canredo` which return whether an undo or redo
637642
is possible.

‎Lib/test/pythoninfo.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ def collect_tkinter(info_add):
491491
except ImportError:
492492
pass
493493
else:
494-
attributes = ('TK_VERSION', 'TCL_VERSION')
494+
attributes = ('TK_PATCH_LEVEL', 'TCL_PATCH_LEVEL')
495495
copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes)
496496

497497
try:

‎Lib/test/test_tkinter/test_misc.py‎

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import time
1010
import unittest
1111
import weakref
12+
import _tkinter
1213
import tkinter
1314
from tkinter import TclError, ttk
1415
import enum
@@ -24,6 +25,49 @@
2425

2526
support.requires('gui')
2627

28+
def check_version_info(test, vi):
29+
# The following is almost a copy of tests for sys.version_info.
30+
test.assertIsInstance(vi[:], tuple)
31+
test.assertEqual(len(vi), 5)
32+
test.assertIsInstance(vi[0], int)
33+
test.assertIsInstance(vi[1], int)
34+
test.assertIsInstance(vi[2], int)
35+
test.assertIn(vi[3], ("alpha", "beta", "candidate", "final"))
36+
test.assertIsInstance(vi[4], int)
37+
test.assertIsInstance(vi.major, int)
38+
test.assertIsInstance(vi.minor, int)
39+
test.assertIsInstance(vi.micro, int)
40+
test.assertIn(vi.releaselevel, ("alpha", "beta", "final"))
41+
test.assertIsInstance(vi.serial, int)
42+
test.assertEqual(vi[0], vi.major)
43+
test.assertEqual(vi[1], vi.minor)
44+
test.assertEqual(vi[2], vi.micro)
45+
test.assertEqual(vi[3], vi.releaselevel)
46+
test.assertEqual(vi[4], vi.serial)
47+
test.assertTrue(vi > (1,0,0))
48+
if vi.releaselevel == 'final':
49+
test.assertEqual(vi.serial, 0)
50+
else:
51+
test.assertEqual(vi.micro, 0)
52+
test.assertStartsWith(str(vi), f'{vi.major}.{vi.minor}')
53+
54+
55+
class VersionTest(unittest.TestCase):
56+
57+
def test_version_info(self):
58+
for vi, version, patchlevel in (
59+
(tkinter.TCL_VERSION_INFO, _tkinter.TCL_VERSION, _tkinter.TCL_PATCH_LEVEL),
60+
(tkinter.TK_VERSION_INFO, _tkinter.TK_VERSION, _tkinter.TK_PATCH_LEVEL),
61+
):
62+
with self.subTest(patchlevel=patchlevel):
63+
check_version_info(self, vi)
64+
self.assertEqual(str(vi), patchlevel)
65+
self.assertEqual(f'{vi.major}.{vi.minor}', version)
66+
self.assertEqual(tkinter.TclVersion,
67+
float(f'{tkinter.TCL_VERSION_INFO.major}.'
68+
f'{tkinter.TCL_VERSION_INFO.minor}'))
69+
70+
2771
class MiscTest(AbstractTkTest, unittest.TestCase):
2872

2973
def test_all(self):
@@ -830,30 +874,10 @@ def test_info_patchlevel(self):
830874
vi = self.root.info_patchlevel()
831875
f = tkinter.Frame(self.root)
832876
self.assertEqual(f.info_patchlevel(), vi)
833-
# The following is almost a copy of tests for sys.version_info.
834-
self.assertIsInstance(vi[:], tuple)
835-
self.assertEqual(len(vi), 5)
836-
self.assertIsInstance(vi[0], int)
837-
self.assertIsInstance(vi[1], int)
838-
self.assertIsInstance(vi[2], int)
839-
self.assertIn(vi[3], ("alpha", "beta", "candidate", "final"))
840-
self.assertIsInstance(vi[4], int)
841-
self.assertIsInstance(vi.major, int)
842-
self.assertIsInstance(vi.minor, int)
843-
self.assertIsInstance(vi.micro, int)
844-
self.assertIn(vi.releaselevel, ("alpha", "beta", "final"))
845-
self.assertIsInstance(vi.serial, int)
846-
self.assertEqual(vi[0], vi.major)
847-
self.assertEqual(vi[1], vi.minor)
848-
self.assertEqual(vi[2], vi.micro)
849-
self.assertEqual(vi[3], vi.releaselevel)
850-
self.assertEqual(vi[4], vi.serial)
851-
self.assertTrue(vi > (1,0,0))
852-
if vi.releaselevel == 'final':
853-
self.assertEqual(vi.serial, 0)
854-
else:
855-
self.assertEqual(vi.micro, 0)
856-
self.assertStartsWith(str(vi), f'{vi.major}.{vi.minor}')
877+
check_version_info(self, vi)
878+
# The Tcl library loaded at runtime should be compatible with
879+
# the one used for building the module.
880+
self.assertEqual(vi[:2], tkinter.TCL_VERSION_INFO[:2])
857881

858882
def test_embedded_null(self):
859883
widget = tkinter.Entry(self.root)

‎Lib/tkinter/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ def _parse_version(version):
170170
releaselevel = {'a': 'alpha', 'b': 'beta'}[releaselevel]
171171
return _VersionInfoType(major, minor, micro, releaselevel, serial)
172172

173+
TCL_VERSION_INFO = _parse_version(_tkinter.TCL_PATCH_LEVEL)
174+
TK_VERSION_INFO = _parse_version(_tkinter.TK_PATCH_LEVEL)
175+
173176

174177
@enum._simple_enum(enum.StrEnum)
175178
class EventType:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add constants :const:`tkinter.TCL_VERSION_INFO` and
2+
:const:`tkinter.TK_VERSION_INFO`, named tuples with the versions of the Tcl
3+
and Tk libraries that were used for building the module, and
4+
``_tkinter.TCL_PATCH_LEVEL`` and ``_tkinter.TK_PATCH_LEVEL`` strings.

‎Modules/_tkinter.c‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3880,6 +3880,14 @@ PyInit__tkinter(void)
38803880
Py_DECREF(m);
38813881
return NULL;
38823882
}
3883+
if (PyModule_AddStringConstant(m, "TK_PATCH_LEVEL", TK_PATCH_LEVEL)) {
3884+
Py_DECREF(m);
3885+
return NULL;
3886+
}
3887+
if (PyModule_AddStringConstant(m, "TCL_PATCH_LEVEL", TCL_PATCH_LEVEL)) {
3888+
Py_DECREF(m);
3889+
return NULL;
3890+
}
38833891

38843892
Tkapp_Type = PyType_FromSpec(&Tkapp_Type_spec);
38853893
if (PyModule_AddObjectRef(m, "TkappType", Tkapp_Type)) {

0 commit comments

Comments
 (0)