https://github.com/python/cpython/commit/4a5a7b9f6d528c9be272d876ed7ca620f0e40038
commit: 4a5a7b9f6d528c9be272d876ed7ca620f0e40038
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-29T15:55:12+03:00
summary:

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) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-09-14-18-36-32.gh-issue-157514.02FZED.rst
M Doc/library/tkinter.rst
M Doc/whatsnew/3.16.rst
M Lib/test/pythoninfo.py
M Lib/test/test_tkinter/test_misc.py
M Lib/tkinter/__init__.py
M Modules/_tkinter.c

diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst
index 44f688ad0d6422b..a6a30928ce83d21 100644
--- a/Doc/library/tkinter.rst
+++ b/Doc/library/tkinter.rst
@@ -6898,3 +6898,32 @@ and :mod:`!tkinter.constants` namespaces.
           DOTBOX
 
    Other option values: ``'underline'``, ``'numeric'`` and ``'dotbox'``.
+
+
+Version information
+^^^^^^^^^^^^^^^^^^^
+
+.. data:: TCL_VERSION_INFO
+          TK_VERSION_INFO
+
+   The versions of the Tcl and Tk libraries that were used for building
+   the :mod:`!_tkinter` module, as named tuples with the same five fields
+   as :data:`sys.version_info`: *major*, *minor*, *micro*, *releaselevel*
+   and *serial*.
+   *releaselevel* is ``'alpha'``, ``'beta'`` or ``'final'``.
+   Converting them to a string gives the version in the usual Tcl/Tk notation,
+   for example ``'9.0.3'`` for a final release or ``'9.1b2'`` for a
+   pre-release.
+   These may be different from the libraries actually used at runtime,
+   which are available as :meth:`Misc.info_patchlevel` (the Tcl version)
+   and the ``tk_patchLevel`` Tcl variable.
+
+   .. versionadded:: next
+
+.. data:: TkVersion
+          TclVersion
+
+   The major and minor version of the Tk and Tcl libraries that were used
+   for building the :mod:`!_tkinter` module, as floats, for example ``8.6``.
+   Prefer :data:`TK_VERSION_INFO` and :data:`TCL_VERSION_INFO`, which are
+   more precise and compare correctly.
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 45fba68402fd3d8..9dd73254b080c4c 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -632,6 +632,11 @@ tkinter
   methods (without recursion) also work on Tk older than 9.1.
   (Contributed by Serhiy Storchaka in :gh:`151910`.)
 
+* Added constants :const:`~tkinter.TCL_VERSION_INFO` and
+  :const:`~tkinter.TK_VERSION_INFO`, named tuples with the versions of
+  the Tcl and Tk libraries that were used for building the module.
+  (Contributed by Serhiy Storchaka in :gh:`157514`.)
+
 * Added new :class:`!tkinter.Text` methods :meth:`~tkinter.Text.edit_canundo`
   and :meth:`~tkinter.Text.edit_canredo` which return whether an undo or redo
   is possible.
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index d48a149fd399f49..e66d82ce5244c70 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -491,7 +491,7 @@ def collect_tkinter(info_add):
     except ImportError:
         pass
     else:
-        attributes = ('TK_VERSION', 'TCL_VERSION')
+        attributes = ('TK_PATCH_LEVEL', 'TCL_PATCH_LEVEL')
         copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes)
 
     try:
diff --git a/Lib/test/test_tkinter/test_misc.py 
b/Lib/test/test_tkinter/test_misc.py
index 5c1eea786698595..b32b58ab4340d20 100644
--- a/Lib/test/test_tkinter/test_misc.py
+++ b/Lib/test/test_tkinter/test_misc.py
@@ -9,6 +9,7 @@
 import time
 import unittest
 import weakref
+import _tkinter
 import tkinter
 from tkinter import TclError, ttk
 import enum
@@ -24,6 +25,49 @@
 
 support.requires('gui')
 
+def check_version_info(test, vi):
+    # The following is almost a copy of tests for sys.version_info.
+    test.assertIsInstance(vi[:], tuple)
+    test.assertEqual(len(vi), 5)
+    test.assertIsInstance(vi[0], int)
+    test.assertIsInstance(vi[1], int)
+    test.assertIsInstance(vi[2], int)
+    test.assertIn(vi[3], ("alpha", "beta", "candidate", "final"))
+    test.assertIsInstance(vi[4], int)
+    test.assertIsInstance(vi.major, int)
+    test.assertIsInstance(vi.minor, int)
+    test.assertIsInstance(vi.micro, int)
+    test.assertIn(vi.releaselevel, ("alpha", "beta", "final"))
+    test.assertIsInstance(vi.serial, int)
+    test.assertEqual(vi[0], vi.major)
+    test.assertEqual(vi[1], vi.minor)
+    test.assertEqual(vi[2], vi.micro)
+    test.assertEqual(vi[3], vi.releaselevel)
+    test.assertEqual(vi[4], vi.serial)
+    test.assertTrue(vi > (1,0,0))
+    if vi.releaselevel == 'final':
+        test.assertEqual(vi.serial, 0)
+    else:
+        test.assertEqual(vi.micro, 0)
+    test.assertStartsWith(str(vi), f'{vi.major}.{vi.minor}')
+
+
+class VersionTest(unittest.TestCase):
+
+    def test_version_info(self):
+        for vi, version, patchlevel in (
+            (tkinter.TCL_VERSION_INFO, _tkinter.TCL_VERSION, 
_tkinter.TCL_PATCH_LEVEL),
+            (tkinter.TK_VERSION_INFO, _tkinter.TK_VERSION, 
_tkinter.TK_PATCH_LEVEL),
+        ):
+            with self.subTest(patchlevel=patchlevel):
+                check_version_info(self, vi)
+                self.assertEqual(str(vi), patchlevel)
+                self.assertEqual(f'{vi.major}.{vi.minor}', version)
+        self.assertEqual(tkinter.TclVersion,
+                         float(f'{tkinter.TCL_VERSION_INFO.major}.'
+                               f'{tkinter.TCL_VERSION_INFO.minor}'))
+
+
 class MiscTest(AbstractTkTest, unittest.TestCase):
 
     def test_all(self):
@@ -830,30 +874,10 @@ def test_info_patchlevel(self):
         vi = self.root.info_patchlevel()
         f = tkinter.Frame(self.root)
         self.assertEqual(f.info_patchlevel(), vi)
-        # The following is almost a copy of tests for sys.version_info.
-        self.assertIsInstance(vi[:], tuple)
-        self.assertEqual(len(vi), 5)
-        self.assertIsInstance(vi[0], int)
-        self.assertIsInstance(vi[1], int)
-        self.assertIsInstance(vi[2], int)
-        self.assertIn(vi[3], ("alpha", "beta", "candidate", "final"))
-        self.assertIsInstance(vi[4], int)
-        self.assertIsInstance(vi.major, int)
-        self.assertIsInstance(vi.minor, int)
-        self.assertIsInstance(vi.micro, int)
-        self.assertIn(vi.releaselevel, ("alpha", "beta", "final"))
-        self.assertIsInstance(vi.serial, int)
-        self.assertEqual(vi[0], vi.major)
-        self.assertEqual(vi[1], vi.minor)
-        self.assertEqual(vi[2], vi.micro)
-        self.assertEqual(vi[3], vi.releaselevel)
-        self.assertEqual(vi[4], vi.serial)
-        self.assertTrue(vi > (1,0,0))
-        if vi.releaselevel == 'final':
-            self.assertEqual(vi.serial, 0)
-        else:
-            self.assertEqual(vi.micro, 0)
-        self.assertStartsWith(str(vi), f'{vi.major}.{vi.minor}')
+        check_version_info(self, vi)
+        # The Tcl library loaded at runtime should be compatible with
+        # the one used for building the module.
+        self.assertEqual(vi[:2], tkinter.TCL_VERSION_INFO[:2])
 
     def test_embedded_null(self):
         widget = tkinter.Entry(self.root)
diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py
index dde4accecf61ba4..11bb0ba9c89c4ac 100644
--- a/Lib/tkinter/__init__.py
+++ b/Lib/tkinter/__init__.py
@@ -170,6 +170,9 @@ def _parse_version(version):
         releaselevel = {'a': 'alpha', 'b': 'beta'}[releaselevel]
     return _VersionInfoType(major, minor, micro, releaselevel, serial)
 
+TCL_VERSION_INFO = _parse_version(_tkinter.TCL_PATCH_LEVEL)
+TK_VERSION_INFO = _parse_version(_tkinter.TK_PATCH_LEVEL)
+
 
 @enum._simple_enum(enum.StrEnum)
 class EventType:
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-14-18-36-32.gh-issue-157514.02FZED.rst 
b/Misc/NEWS.d/next/Library/2026-09-14-18-36-32.gh-issue-157514.02FZED.rst
new file mode 100644
index 000000000000000..80c2ad9a7d2aa97
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-14-18-36-32.gh-issue-157514.02FZED.rst
@@ -0,0 +1,4 @@
+Add constants :const:`tkinter.TCL_VERSION_INFO` and
+:const:`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.
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index cff855d61cdbda6..07798dfaa42a0c9 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -3880,6 +3880,14 @@ PyInit__tkinter(void)
         Py_DECREF(m);
         return NULL;
     }
+    if (PyModule_AddStringConstant(m, "TK_PATCH_LEVEL", TK_PATCH_LEVEL)) {
+        Py_DECREF(m);
+        return NULL;
+    }
+    if (PyModule_AddStringConstant(m, "TCL_PATCH_LEVEL", TCL_PATCH_LEVEL)) {
+        Py_DECREF(m);
+        return NULL;
+    }
 
     Tkapp_Type = PyType_FromSpec(&Tkapp_Type_spec);
     if (PyModule_AddObjectRef(m, "TkappType", Tkapp_Type)) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to