]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-78335: Complete the widget option lists in tkinter docstrings (GH-152485...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sun, 28 Jun 2026 16:53:29 +0000 (18:53 +0200)
committerGitHub <noreply@github.com>
Sun, 28 Jun 2026 16:53:29 +0000 (19:53 +0300)
Several widget __init__ docstrings omitted valid options, and Menubutton and
Message had no option list at all.  List every option supported by the widget,
tagging those added in Tk 9.0 and 9.1.

Add test_options_in_docstring, asserting that every option in OPTIONS is named
in the widget's __init__ docstring.  Options reported by keys() but not in the
docstring are only printed in verbose mode, as some depend on the Tk version.
(cherry picked from commit ba0c0e6be18ea56d00b4534cc9e32d1acaba96d6)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Lib/test/test_tkinter/test_geometry_managers.py
Lib/test/test_tkinter/widget_tests.py
Lib/test/test_ttk/test_extensions.py
Lib/tkinter/__init__.py
Lib/tkinter/ttk.py
Misc/NEWS.d/next/Library/2026-06-28-11-00-40.gh-issue-78335.Kp3mWq.rst [new file with mode: 0644]

index dff9ee35231dfa79883c2ad6a7930827b33a3df6..5c38c7044e5cfb1217d7ba453927809df4b1afc4 100644 (file)
@@ -19,6 +19,7 @@ EXPECTED_SCREEN_DISTANCE_OR_EMPTY_ERRMSG = '(bad|expected) screen distance (or "
 class PackTest(AbstractWidgetTest, unittest.TestCase):
 
     test_keys = None
+    test_options_in_docstring = None
 
     def create2(self):
         pack = tkinter.Toplevel(self.root, name='pack')
@@ -302,6 +303,7 @@ class PackTest(AbstractWidgetTest, unittest.TestCase):
 class PlaceTest(AbstractWidgetTest, unittest.TestCase):
 
     test_keys = None
+    test_options_in_docstring = None
 
     def create2(self):
         t = tkinter.Toplevel(self.root, width=300, height=200, bd=0)
@@ -518,6 +520,7 @@ class PlaceTest(AbstractWidgetTest, unittest.TestCase):
 class GridTest(AbstractWidgetTest, unittest.TestCase):
 
     test_keys = None
+    test_options_in_docstring = None
 
     def tearDown(self):
         cols, rows = self.root.grid_size()
index 94244a8b3fe2448be7114e015afd170ab1a0ab8c..014906cba2902c34656159c11414197518fc52e5 100644 (file)
@@ -8,6 +8,16 @@ import test.support
 
 _sentinel = object()
 
+# Abbreviated option names accepted by Tk in addition to the full names.
+_OPTION_ALIASES = {
+    'bd': 'borderwidth',
+    'bg': 'background',
+    'bgimg': 'backgroundimage',
+    'fg': 'foreground',
+    'invcmd': 'invalidcommand',
+    'vcmd': 'validatecommand',
+}
+
 # Options which accept all values allowed by Tk_GetPixels
 # borderwidth = bd
 
@@ -214,23 +224,44 @@ class AbstractWidgetTest(AbstractTkTest):
             widget[k]
         # Test if OPTIONS contains all keys
         if test.support.verbose:
-            aliases = {
-                'bd': 'borderwidth',
-                'bg': 'background',
-                'bgimg': 'backgroundimage',
-                'fg': 'foreground',
-                'invcmd': 'invalidcommand',
-                'vcmd': 'validatecommand',
-            }
             keys = set(keys)
             expected = set(self.OPTIONS)
             for k in sorted(keys - expected):
-                if not (k in aliases and
-                        aliases[k] in keys and
-                        aliases[k] in expected):
+                if not (k in _OPTION_ALIASES and
+                        _OPTION_ALIASES[k] in keys and
+                        _OPTION_ALIASES[k] in expected):
                     print('%s.OPTIONS doesn\'t contain "%s"' %
                           (self.__class__.__name__, k))
 
+    def test_options_in_docstring(self):
+        # Every option in OPTIONS must be listed in the docstring of the
+        # __init__ method (see gh-78335).  Options reported by keys() but
+        # missing from the docstring are only printed in verbose mode, as
+        # some of them depend on the Tk version.
+        widget = self.create()
+        doc = type(widget).__init__.__doc__
+        if doc is None:
+            self.skipTest('docstrings are not available (run with -OO)')
+        # Look at the option list only, not the leading description.
+        start = doc.find('Valid option names')
+        if start < 0:
+            start = doc.find('OPTIONS')
+        if start < 0:
+            self.skipTest('the __init__ docstring does not list options')
+        documented = set(re.findall(r'[a-z][a-z0-9]+', doc[start:]))
+        def is_documented(option):
+            return (option in documented or
+                    _OPTION_ALIASES.get(option) in documented)
+        missing = sorted(o for o in self.OPTIONS if not is_documented(o))
+        self.assertEqual(missing, [],
+                         '%s options missing from the __init__ docstring: %s'
+                         % (type(widget).__name__, missing))
+        if test.support.verbose:
+            for key in sorted(set(widget.keys())):
+                if not is_documented(key):
+                    print('%s.__init__ docstring doesn\'t contain "%s"'
+                          % (type(widget).__name__, key))
+
 class PixelOptionsTests:
     """Standard options that accept all formats acceptable to Tk_GetPixels.
 
index 0460f1b9aa3117286c08b8030d81a9df39f9c971..fb6bd24b4b9147b9ad42e48f4e0b5649e4d43f42 100644 (file)
@@ -199,6 +199,10 @@ class LabeledScaleTest(AbstractTkTest, unittest.TestCase):
 
 class OptionMenuTest(test_widgets.MenubuttonTest, unittest.TestCase):
 
+    # OptionMenu documents only its own options, not the inherited
+    # Menubutton options (like the classic tkinter.OptionMenu).
+    test_options_in_docstring = None
+
     def setUp(self):
         super().setUp()
         self.textvar = tkinter.StringVar(self.root)
index db38fe97dbd334b13e78199a8d807d286bc5945f..00c38b08812ec329085f7041a3ee7344565c87ea 100644 (file)
@@ -2855,10 +2855,11 @@ class Toplevel(BaseWidget, Wm):
     def __init__(self, master=None, cnf={}, **kw):
         """Construct a toplevel widget with the parent MASTER.
 
-        Valid option names: background, bd, bg, borderwidth, class,
-        colormap, container, cursor, height, highlightbackground,
-        highlightcolor, highlightthickness, menu, relief, screen, takefocus,
-        use, visual, width."""
+        Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
+        bgimg (Tk 9.0+), borderwidth, class, colormap, container,
+        cursor, height, highlightbackground, highlightcolor,
+        highlightthickness, menu, padx, pady, relief, screen,
+        takefocus, tile (Tk 9.0+), use, visual, width."""
         if kw:
             cnf = _cnfmerge((cnf, kw))
         extra = ()
@@ -3241,12 +3242,13 @@ class Checkbutton(Widget):
         """Construct a checkbutton widget with the parent MASTER.
 
         Valid option names: activebackground, activeforeground, anchor,
-        background, bd, bg, bitmap, borderwidth, command, cursor,
-        disabledforeground, fg, font, foreground, height,
-        highlightbackground, highlightcolor, highlightthickness, image,
-        indicatoron, justify, offvalue, onvalue, padx, pady, relief,
-        selectcolor, selectimage, state, takefocus, text, textvariable,
-        underline, variable, width, wraplength."""
+        background, bd, bg, bitmap, borderwidth, command, compound,
+        cursor, disabledforeground, fg, font, foreground, height,
+        highlightbackground, highlightcolor, highlightthickness,
+        image, indicatoron, justify, offrelief, offvalue, onvalue,
+        overrelief, padx, pady, relief, selectcolor, selectimage,
+        state, takefocus, text, textvariable, tristateimage,
+        tristatevalue, underline, variable, width, wraplength."""
         Widget.__init__(self, master, 'checkbutton', cnf, kw)
 
     def _setup(self, master, cnf):
@@ -3290,13 +3292,15 @@ class Entry(Widget, XView):
         """Construct an entry widget with the parent MASTER.
 
         Valid option names: background, bd, bg, borderwidth, cursor,
-        exportselection, fg, font, foreground, highlightbackground,
-        highlightcolor, highlightthickness, insertbackground,
-        insertborderwidth, insertofftime, insertontime, insertwidth,
-        invalidcommand, invcmd, justify, relief, selectbackground,
-        selectborderwidth, selectforeground, show, state, takefocus,
-        textvariable, validate, validatecommand, vcmd, width,
-        xscrollcommand."""
+        disabledbackground, disabledforeground, exportselection, fg,
+        font, foreground, highlightbackground, highlightcolor,
+        highlightthickness, insertbackground, insertborderwidth,
+        insertofftime, insertontime, insertwidth, invalidcommand,
+        invcmd, justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
+        placeholderforeground (Tk 9.0+), readonlybackground, relief,
+        selectbackground, selectborderwidth, selectforeground, show,
+        state, takefocus, textvariable, validate, validatecommand,
+        vcmd, width, xscrollcommand."""
         Widget.__init__(self, master, 'entry', cnf, kw)
 
     def delete(self, first, last=None):
@@ -3375,9 +3379,11 @@ class Frame(Widget):
     def __init__(self, master=None, cnf={}, **kw):
         """Construct a frame widget with the parent MASTER.
 
-        Valid option names: background, bd, bg, borderwidth, class,
-        colormap, container, cursor, height, highlightbackground,
-        highlightcolor, highlightthickness, relief, takefocus, visual, width."""
+        Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
+        bgimg (Tk 9.0+), borderwidth, class, colormap, container,
+        cursor, height, highlightbackground, highlightcolor,
+        highlightthickness, padx, pady, relief, takefocus, tile (Tk
+        9.0+), visual, width."""
         cnf = _cnfmerge((cnf, kw))
         extra = ()
         if 'class_' in cnf:
@@ -3407,7 +3413,8 @@ class Label(Widget):
 
         WIDGET-SPECIFIC OPTIONS
 
-            height, state, width
+            compound, height, state,
+            textangle (Tk 9.1+), width
 
         """
         Widget.__init__(self, master, 'label', cnf, kw)
@@ -3419,11 +3426,14 @@ class Listbox(Widget, XView, YView):
     def __init__(self, master=None, cnf={}, **kw):
         """Construct a listbox widget with the parent MASTER.
 
-        Valid option names: background, bd, bg, borderwidth, cursor,
-        exportselection, fg, font, foreground, height, highlightbackground,
-        highlightcolor, highlightthickness, relief, selectbackground,
-        selectborderwidth, selectforeground, selectmode, setgrid, takefocus,
-        width, xscrollcommand, yscrollcommand, listvariable."""
+        Valid option names: activestyle, background, bd, bg, borderwidth,
+        cursor, disabledforeground, exportselection, fg, font,
+        foreground, height, highlightbackground, highlightcolor,
+        highlightthickness, inactiveselectbackground (Tk 9.1+),
+        inactiveselectforeground (Tk 9.1+), justify, listvariable,
+        relief, selectbackground, selectborderwidth, selectforeground,
+        selectmode, setgrid, state, takefocus, width, xscrollcommand,
+        yscrollcommand."""
         Widget.__init__(self, master, 'listbox', cnf, kw)
 
     def activate(self, index):
@@ -3533,7 +3543,8 @@ class Menu(Widget):
         """Construct menu widget with the parent MASTER.
 
         Valid option names: activebackground, activeborderwidth,
-        activeforeground, background, bd, bg, borderwidth, cursor,
+        activeforeground, activerelief (Tk 9.0+), background, bd, bg,
+        borderwidth, cursor,
         disabledforeground, fg, font, foreground, postcommand, relief,
         selectcolor, takefocus, tearoff, tearoffcommand, title, type."""
         Widget.__init__(self, master, 'menu', cnf, kw)
@@ -3663,6 +3674,14 @@ class Menubutton(Widget):
     """Menubutton widget, obsolete since Tk8.0."""
 
     def __init__(self, master=None, cnf={}, **kw):
+        """Construct a menubutton widget with the parent MASTER.
+
+        Valid option names: activebackground, activeforeground, anchor,
+        background, bd, bg, bitmap, borderwidth, compound, cursor,
+        direction, disabledforeground, fg, font, foreground, height,
+        highlightbackground, highlightcolor, highlightthickness,
+        image, indicatoron, justify, menu, padx, pady, relief, state,
+        takefocus, text, textvariable, underline, width, wraplength."""
         Widget.__init__(self, master, 'menubutton', cnf, kw)
 
 
@@ -3670,6 +3689,12 @@ class Message(Widget):
     """Message widget to display multiline text. Obsolete since Label does it too."""
 
     def __init__(self, master=None, cnf={}, **kw):
+        """Construct a message widget with the parent MASTER.
+
+        Valid option names: anchor, aspect, background, bd, bg, borderwidth,
+        cursor, fg, font, foreground, highlightbackground,
+        highlightcolor, highlightthickness, justify, padx, pady,
+        relief, takefocus, text, textvariable, width."""
         Widget.__init__(self, master, 'message', cnf, kw)
 
 
@@ -3680,12 +3705,13 @@ class Radiobutton(Widget):
         """Construct a radiobutton widget with the parent MASTER.
 
         Valid option names: activebackground, activeforeground, anchor,
-        background, bd, bg, bitmap, borderwidth, command, cursor,
-        disabledforeground, fg, font, foreground, height,
-        highlightbackground, highlightcolor, highlightthickness, image,
-        indicatoron, justify, padx, pady, relief, selectcolor, selectimage,
-        state, takefocus, text, textvariable, underline, value, variable,
-        width, wraplength."""
+        background, bd, bg, bitmap, borderwidth, command, compound,
+        cursor, disabledforeground, fg, font, foreground, height,
+        highlightbackground, highlightcolor, highlightthickness,
+        image, indicatoron, justify, offrelief, overrelief, padx,
+        pady, relief, selectcolor, selectimage, state, takefocus,
+        text, textvariable, tristateimage, tristatevalue, underline,
+        value, variable, width, wraplength."""
         Widget.__init__(self, master, 'radiobutton', cnf, kw)
 
     def deselect(self):
@@ -3816,9 +3842,11 @@ class Text(Widget, XView, YView):
 
         WIDGET-SPECIFIC OPTIONS
 
-            autoseparators, height, maxundo,
-            spacing1, spacing2, spacing3,
-            state, tabs, undo, width, wrap,
+            autoseparators, blockcursor, endline,
+            height, inactiveselectbackground,
+            insertunfocussed, locale (Tk 9.1+), maxundo,
+            spacing1, spacing2, spacing3, startline,
+            state, tabs, tabstyle, undo, width, wrap,
 
         """
         Widget.__init__(self, master, 'text', cnf, kw)
@@ -4646,9 +4674,12 @@ class Spinbox(Widget, XView):
             buttondownrelief, buttonuprelief,
             command, disabledbackground,
             disabledforeground, format, from,
-            invalidcommand, increment,
+            invalidcommand, invcmd, increment,
+            locale (Tk 9.1+),
+            placeholder (Tk 9.0+),
+            placeholderforeground (Tk 9.0+),
             readonlybackground, state, to,
-            validate, validatecommand values,
+            validate, validatecommand, vcmd, values,
             width, wrap,
         """
         Widget.__init__(self, master, 'spinbox', cnf, kw)
@@ -4837,8 +4868,9 @@ class PanedWindow(Widget):
         WIDGET-SPECIFIC OPTIONS
 
             handlepad, handlesize, opaqueresize,
-            sashcursor, sashpad, sashrelief,
-            sashwidth, showhandle,
+            proxybackground, proxyborderwidth,
+            proxyrelief, sashcursor, sashpad,
+            sashrelief, sashwidth, showhandle,
         """
         Widget.__init__(self, master, 'panedwindow', cnf, kw)
 
index afe951d91b9eef5f601e7aaef84715e89b4b60ee..b3114a4666579d79ecd6355de63da053f078359c 100644 (file)
@@ -581,8 +581,8 @@ class Button(Widget):
 
         STANDARD OPTIONS
 
-            class, compound, cursor, image, state, style, takefocus,
-            text, textvariable, underline, width
+            class, compound, cursor, image, justify (Tk 9.0+), padding,
+            state, style, takefocus, text, textvariable, underline, width
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -604,8 +604,8 @@ class Checkbutton(Widget):
 
         STANDARD OPTIONS
 
-            class, compound, cursor, image, state, style, takefocus,
-            text, textvariable, underline, width
+            class, compound, cursor, image, justify (Tk 9.0+), padding,
+            state, style, takefocus, text, textvariable, underline, width
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -638,8 +638,10 @@ class Entry(Widget, tkinter.Entry):
 
         WIDGET-SPECIFIC OPTIONS
 
-            exportselection, invalidcommand, justify, show, state,
-            textvariable, validate, validatecommand, width
+            background, exportselection, font, foreground, invalidcommand,
+            justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
+            placeholderforeground (Tk 9.0+), show, state, textvariable,
+            validate, validatecommand, width
 
         VALIDATION MODES
 
@@ -676,12 +678,14 @@ class Combobox(Entry):
 
         STANDARD OPTIONS
 
-            class, cursor, style, takefocus
+            class, cursor, style, takefocus, xscrollcommand
 
         WIDGET-SPECIFIC OPTIONS
 
-            exportselection, justify, height, postcommand, state,
-            textvariable, values, width
+            background, exportselection, font, foreground, height,
+            invalidcommand, justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
+            placeholderforeground (Tk 9.0+), postcommand, show, state,
+            textvariable, validate, validatecommand, values, width
         """
         Entry.__init__(self, master, "ttk::combobox", **kw)
 
@@ -730,13 +734,13 @@ class Label(Widget):
 
         STANDARD OPTIONS
 
-            class, compound, cursor, image, style, takefocus, text,
-            textvariable, underline, width
+            class, compound, cursor, image, state, style, takefocus,
+            text, textvariable, underline, width
 
         WIDGET-SPECIFIC OPTIONS
 
-            anchor, background, font, foreground, justify, padding,
-            relief, text, wraplength
+            anchor, background, borderwidth, font, foreground, justify,
+            padding, relief, text, textangle (Tk 9.1+), wraplength
         """
         Widget.__init__(self, master, "ttk::label", kw)
 
@@ -754,8 +758,9 @@ class Labelframe(Widget):
             class, cursor, style, takefocus
 
         WIDGET-SPECIFIC OPTIONS
-            labelanchor, text, underline, padding, labelwidget, width,
-            height
+
+            borderwidth, height, labelanchor, labelwidget, padding,
+            relief, text, underline, width
         """
         Widget.__init__(self, master, "ttk::labelframe", kw)
 
@@ -771,8 +776,8 @@ class Menubutton(Widget):
 
         STANDARD OPTIONS
 
-            class, compound, cursor, image, state, style, takefocus,
-            text, textvariable, underline, width
+            class, compound, cursor, image, justify (Tk 9.0+), padding,
+            state, style, takefocus, text, textvariable, underline, width
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -985,7 +990,9 @@ class Progressbar(Widget):
 
         STANDARD OPTIONS
 
-            class, cursor, style, takefocus
+            anchor (Tk 9.0+), class, cursor, font (Tk 9.0+),
+            foreground (Tk 9.0+), justify (Tk 9.0+), style, takefocus,
+            text (Tk 9.0+), wraplength (Tk 9.0+)
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -1024,8 +1031,8 @@ class Radiobutton(Widget):
 
         STANDARD OPTIONS
 
-            class, compound, cursor, image, state, style, takefocus,
-            text, textvariable, underline, width
+            class, compound, cursor, image, justify (Tk 9.0+), padding,
+            state, style, takefocus, text, textvariable, underline, width
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -1052,7 +1059,7 @@ class Scale(Widget, tkinter.Scale):
 
         STANDARD OPTIONS
 
-            class, cursor, style, takefocus
+            class, cursor, state, style, takefocus
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -1149,7 +1156,10 @@ class Spinbox(Entry):
 
         WIDGET-SPECIFIC OPTIONS
 
-            to, from_, increment, values, wrap, format, command
+            background, command, exportselection, font, foreground,
+            format, from_, increment, justify, locale (Tk 9.1+),
+            placeholder (Tk 9.0+), placeholderforeground (Tk 9.0+), show,
+            state, textvariable, to, values, width, wrap
         """
         Entry.__init__(self, master, "ttk::spinbox", **kw)
 
@@ -1176,7 +1186,10 @@ class Treeview(Widget, tkinter.XView, tkinter.YView):
 
         WIDGET-SPECIFIC OPTIONS
 
-            columns, displaycolumns, height, padding, selectmode, show
+            columns, displaycolumns, headingheight (Tk 9.1+), height,
+            padding, rowheight (Tk 9.1+), selectmode, selecttype (Tk 9.0+),
+            show, striped (Tk 9.0+), titlecolumns (Tk 9.0+),
+            titleitems (Tk 9.0+)
 
         ITEM OPTIONS
 
diff --git a/Misc/NEWS.d/next/Library/2026-06-28-11-00-40.gh-issue-78335.Kp3mWq.rst b/Misc/NEWS.d/next/Library/2026-06-28-11-00-40.gh-issue-78335.Kp3mWq.rst
new file mode 100644 (file)
index 0000000..641889c
--- /dev/null
@@ -0,0 +1,4 @@
+Update the docstrings of :mod:`tkinter` and :mod:`tkinter.ttk` widget classes
+to list all supported widget options, including options added in Tk 9.0 and
+9.1.  ``tkinter.Menubutton`` and ``tkinter.Message`` previously had no option
+list at all.