diff --git a/Lib/test/test_tkinter/test_geometry_managers.py b/Lib/test/test_tkinter/test_geometry_managers.py index dff9ee35231dfa7..5c38c7044e5cfb1 100644 --- a/Lib/test/test_tkinter/test_geometry_managers.py +++ b/Lib/test/test_tkinter/test_geometry_managers.py @@ -19,6 +19,7 @@ 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 @@ def test_pack_short_aliases(self): 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 @@ def test_place_method_aliases(self): 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 8ab2f74245095d5..efa9299d2f8b1b8 100644 --- a/Lib/test/test_tkinter/widget_tests.py +++ b/Lib/test/test_tkinter/widget_tests.py @@ -9,6 +9,19 @@ _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 @@ def test_keys(self): 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 2097364cbcd26f3..0bde06bc3dd17ec 100644 --- a/Lib/test/test_ttk/test_extensions.py +++ b/Lib/test/test_ttk/test_extensions.py @@ -199,6 +199,10 @@ def test_resize(self): 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 2dfe1b7bf82e363..831281e505f115a 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 @@ def __init__(self, master=None, cnf={}, **kw): """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 @@ def __init__(self, master=None, cnf={}, **kw): """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 @@ def __init__(self, master=None, cnf={}, **kw): 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 @@ def __init__(self, master=None, cnf={}, **kw): """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 @@ def __init__(self, master=None, cnf={}, **kw): """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 @@ def __init__(self, master=None, cnf={}, **kw): 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 @@ def __init__(self, master=None, cnf={}, **kw): 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 @@ def __init__(self, master=None, cnf={}, **kw): 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 96c4fe79cd5c5c1..3233c8973a8078b 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -579,8 +579,8 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, widget=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): STANDARD OPTIONS - class, cursor, style, takefocus + class, cursor, state, style, takefocus WIDGET-SPECIFIC OPTIONS @@ -1147,7 +1154,10 @@ def __init__(self, master=None, **kw): 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 @@ def __init__(self, master=None, **kw): 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 000000000000000..641889cfa9ef41d --- /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.