From: Serhiy Storchaka Date: Sun, 28 Jun 2026 17:29:48 +0000 (+0300) Subject: gh-78335: Complete the widget option lists in tkinter docstrings (GH-152485) (GH... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1e011ab10956c418f7ab19e2b46855ed6ee87626;p=thirdparty%2FPython%2Fcpython.git gh-78335: Complete the widget option lists in tkinter docstrings (GH-152485) (GH-152513) 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: Claude Opus 4.8 --- diff --git a/Lib/test/test_tkinter/test_geometry_managers.py b/Lib/test/test_tkinter/test_geometry_managers.py index dff9ee35231d..5c38c7044e5c 100644 --- a/Lib/test/test_tkinter/test_geometry_managers.py +++ b/Lib/test/test_tkinter/test_geometry_managers.py @@ -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() diff --git a/Lib/test/test_tkinter/widget_tests.py b/Lib/test/test_tkinter/widget_tests.py index 8ab2f7424509..efa9299d2f8b 100644 --- a/Lib/test/test_tkinter/widget_tests.py +++ b/Lib/test/test_tkinter/widget_tests.py @@ -9,6 +9,19 @@ 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 + class AbstractWidgetTest(AbstractTkTest): _default_pixels = '' if tk_version >= (9, 0) else -1 if tk_version >= (8, 7) else '' _conv_pixels = round @@ -198,23 +211,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 StandardOptionsTests: STANDARD_OPTIONS = ( diff --git a/Lib/test/test_ttk/test_extensions.py b/Lib/test/test_ttk/test_extensions.py index 2097364cbcd2..0bde06bc3dd1 100644 --- a/Lib/test/test_ttk/test_extensions.py +++ b/Lib/test/test_ttk/test_extensions.py @@ -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) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 2dfe1b7bf82e..831281e505f1 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -2841,10 +2841,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 = () @@ -3227,12 +3228,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): @@ -3276,13 +3278,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): @@ -3361,9 +3365,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: @@ -3393,7 +3399,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) @@ -3405,11 +3412,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): @@ -3519,7 +3529,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) @@ -3649,6 +3660,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) @@ -3656,6 +3675,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) @@ -3666,12 +3691,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): @@ -3802,9 +3828,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) @@ -4632,9 +4660,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) @@ -4823,8 +4854,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) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index 96c4fe79cd5c..3233c8973a80 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -579,8 +579,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 @@ -602,8 +602,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 @@ -636,8 +636,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 @@ -674,12 +676,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) @@ -728,13 +732,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) @@ -752,8 +756,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) @@ -769,8 +774,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 @@ -983,7 +988,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 @@ -1022,8 +1029,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 @@ -1050,7 +1057,7 @@ class Scale(Widget, tkinter.Scale): STANDARD OPTIONS - class, cursor, style, takefocus + class, cursor, state, style, takefocus WIDGET-SPECIFIC OPTIONS @@ -1147,7 +1154,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) @@ -1174,7 +1184,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 index 000000000000..641889cfa9ef --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-28-11-00-40.gh-issue-78335.Kp3mWq.rst @@ -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.