Skip to content

Commit 1b967f1

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.14] gh-78335: Complete the widget option lists in tkinter docstrings (GH-152485) (GH-152501)
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 ba0c0e6) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 07672e9 commit 1b967f1

6 files changed

Lines changed: 161 additions & 74 deletions

File tree

Lib/test/test_tkinter/test_geometry_managers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
class PackTest(AbstractWidgetTest, unittest.TestCase):
2020

2121
test_keys = None
22+
test_options_in_docstring = None
2223

2324
def create2(self):
2425
pack = tkinter.Toplevel(self.root, name='pack')
@@ -302,6 +303,7 @@ def test_pack_short_aliases(self):
302303
class PlaceTest(AbstractWidgetTest, unittest.TestCase):
303304

304305
test_keys = None
306+
test_options_in_docstring = None
305307

306308
def create2(self):
307309
t = tkinter.Toplevel(self.root, width=300, height=200, bd=0)
@@ -518,6 +520,7 @@ def test_place_method_aliases(self):
518520
class GridTest(AbstractWidgetTest, unittest.TestCase):
519521

520522
test_keys = None
523+
test_options_in_docstring = None
521524

522525
def tearDown(self):
523526
cols, rows = self.root.grid_size()

Lib/test/test_tkinter/widget_tests.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88

99
_sentinel = object()
1010

11+
# Abbreviated option names accepted by Tk in addition to the full names.
12+
_OPTION_ALIASES = {
13+
'bd': 'borderwidth',
14+
'bg': 'background',
15+
'bgimg': 'backgroundimage',
16+
'fg': 'foreground',
17+
'invcmd': 'invalidcommand',
18+
'vcmd': 'validatecommand',
19+
}
20+
1121
# Options which accept all values allowed by Tk_GetPixels
1222
# borderwidth = bd
1323

@@ -214,23 +224,44 @@ def test_keys(self):
214224
widget[k]
215225
# Test if OPTIONS contains all keys
216226
if test.support.verbose:
217-
aliases = {
218-
'bd': 'borderwidth',
219-
'bg': 'background',
220-
'bgimg': 'backgroundimage',
221-
'fg': 'foreground',
222-
'invcmd': 'invalidcommand',
223-
'vcmd': 'validatecommand',
224-
}
225227
keys = set(keys)
226228
expected = set(self.OPTIONS)
227229
for k in sorted(keys - expected):
228-
if not (k in aliases and
229-
aliases[k] in keys and
230-
aliases[k] in expected):
230+
if not (k in _OPTION_ALIASES and
231+
_OPTION_ALIASES[k] in keys and
232+
_OPTION_ALIASES[k] in expected):
231233
print('%s.OPTIONS doesn\'t contain "%s"' %
232234
(self.__class__.__name__, k))
233235

236+
def test_options_in_docstring(self):
237+
# Every option in OPTIONS must be listed in the docstring of the
238+
# __init__ method (see gh-78335). Options reported by keys() but
239+
# missing from the docstring are only printed in verbose mode, as
240+
# some of them depend on the Tk version.
241+
widget = self.create()
242+
doc = type(widget).__init__.__doc__
243+
if doc is None:
244+
self.skipTest('docstrings are not available (run with -OO)')
245+
# Look at the option list only, not the leading description.
246+
start = doc.find('Valid option names')
247+
if start < 0:
248+
start = doc.find('OPTIONS')
249+
if start < 0:
250+
self.skipTest('the __init__ docstring does not list options')
251+
documented = set(re.findall(r'[a-z][a-z0-9]+', doc[start:]))
252+
def is_documented(option):
253+
return (option in documented or
254+
_OPTION_ALIASES.get(option) in documented)
255+
missing = sorted(o for o in self.OPTIONS if not is_documented(o))
256+
self.assertEqual(missing, [],
257+
'%s options missing from the __init__ docstring: %s'
258+
% (type(widget).__name__, missing))
259+
if test.support.verbose:
260+
for key in sorted(set(widget.keys())):
261+
if not is_documented(key):
262+
print('%s.__init__ docstring doesn\'t contain "%s"'
263+
% (type(widget).__name__, key))
264+
234265
class PixelOptionsTests:
235266
"""Standard options that accept all formats acceptable to Tk_GetPixels.
236267

Lib/test/test_ttk/test_extensions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ def test_resize(self):
199199

200200
class OptionMenuTest(test_widgets.MenubuttonTest, unittest.TestCase):
201201

202+
# OptionMenu documents only its own options, not the inherited
203+
# Menubutton options (like the classic tkinter.OptionMenu).
204+
test_options_in_docstring = None
205+
202206
def setUp(self):
203207
super().setUp()
204208
self.textvar = tkinter.StringVar(self.root)

Lib/tkinter/__init__.py

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2855,10 +2855,11 @@ class Toplevel(BaseWidget, Wm):
28552855
def __init__(self, master=None, cnf={}, **kw):
28562856
"""Construct a toplevel widget with the parent MASTER.
28572857
2858-
Valid option names: background, bd, bg, borderwidth, class,
2859-
colormap, container, cursor, height, highlightbackground,
2860-
highlightcolor, highlightthickness, menu, relief, screen, takefocus,
2861-
use, visual, width."""
2858+
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
2859+
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
2860+
cursor, height, highlightbackground, highlightcolor,
2861+
highlightthickness, menu, padx, pady, relief, screen,
2862+
takefocus, tile (Tk 9.0+), use, visual, width."""
28622863
if kw:
28632864
cnf = _cnfmerge((cnf, kw))
28642865
extra = ()
@@ -3241,12 +3242,13 @@ def __init__(self, master=None, cnf={}, **kw):
32413242
"""Construct a checkbutton widget with the parent MASTER.
32423243
32433244
Valid option names: activebackground, activeforeground, anchor,
3244-
background, bd, bg, bitmap, borderwidth, command, cursor,
3245-
disabledforeground, fg, font, foreground, height,
3246-
highlightbackground, highlightcolor, highlightthickness, image,
3247-
indicatoron, justify, offvalue, onvalue, padx, pady, relief,
3248-
selectcolor, selectimage, state, takefocus, text, textvariable,
3249-
underline, variable, width, wraplength."""
3245+
background, bd, bg, bitmap, borderwidth, command, compound,
3246+
cursor, disabledforeground, fg, font, foreground, height,
3247+
highlightbackground, highlightcolor, highlightthickness,
3248+
image, indicatoron, justify, offrelief, offvalue, onvalue,
3249+
overrelief, padx, pady, relief, selectcolor, selectimage,
3250+
state, takefocus, text, textvariable, tristateimage,
3251+
tristatevalue, underline, variable, width, wraplength."""
32503252
Widget.__init__(self, master, 'checkbutton', cnf, kw)
32513253

32523254
def _setup(self, master, cnf):
@@ -3290,13 +3292,15 @@ def __init__(self, master=None, cnf={}, **kw):
32903292
"""Construct an entry widget with the parent MASTER.
32913293
32923294
Valid option names: background, bd, bg, borderwidth, cursor,
3293-
exportselection, fg, font, foreground, highlightbackground,
3294-
highlightcolor, highlightthickness, insertbackground,
3295-
insertborderwidth, insertofftime, insertontime, insertwidth,
3296-
invalidcommand, invcmd, justify, relief, selectbackground,
3297-
selectborderwidth, selectforeground, show, state, takefocus,
3298-
textvariable, validate, validatecommand, vcmd, width,
3299-
xscrollcommand."""
3295+
disabledbackground, disabledforeground, exportselection, fg,
3296+
font, foreground, highlightbackground, highlightcolor,
3297+
highlightthickness, insertbackground, insertborderwidth,
3298+
insertofftime, insertontime, insertwidth, invalidcommand,
3299+
invcmd, justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
3300+
placeholderforeground (Tk 9.0+), readonlybackground, relief,
3301+
selectbackground, selectborderwidth, selectforeground, show,
3302+
state, takefocus, textvariable, validate, validatecommand,
3303+
vcmd, width, xscrollcommand."""
33003304
Widget.__init__(self, master, 'entry', cnf, kw)
33013305

33023306
def delete(self, first, last=None):
@@ -3375,9 +3379,11 @@ class Frame(Widget):
33753379
def __init__(self, master=None, cnf={}, **kw):
33763380
"""Construct a frame widget with the parent MASTER.
33773381
3378-
Valid option names: background, bd, bg, borderwidth, class,
3379-
colormap, container, cursor, height, highlightbackground,
3380-
highlightcolor, highlightthickness, relief, takefocus, visual, width."""
3382+
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
3383+
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
3384+
cursor, height, highlightbackground, highlightcolor,
3385+
highlightthickness, padx, pady, relief, takefocus, tile (Tk
3386+
9.0+), visual, width."""
33813387
cnf = _cnfmerge((cnf, kw))
33823388
extra = ()
33833389
if 'class_' in cnf:
@@ -3407,7 +3413,8 @@ def __init__(self, master=None, cnf={}, **kw):
34073413
34083414
WIDGET-SPECIFIC OPTIONS
34093415
3410-
height, state, width
3416+
compound, height, state,
3417+
textangle (Tk 9.1+), width
34113418
34123419
"""
34133420
Widget.__init__(self, master, 'label', cnf, kw)
@@ -3419,11 +3426,14 @@ class Listbox(Widget, XView, YView):
34193426
def __init__(self, master=None, cnf={}, **kw):
34203427
"""Construct a listbox widget with the parent MASTER.
34213428
3422-
Valid option names: background, bd, bg, borderwidth, cursor,
3423-
exportselection, fg, font, foreground, height, highlightbackground,
3424-
highlightcolor, highlightthickness, relief, selectbackground,
3425-
selectborderwidth, selectforeground, selectmode, setgrid, takefocus,
3426-
width, xscrollcommand, yscrollcommand, listvariable."""
3429+
Valid option names: activestyle, background, bd, bg, borderwidth,
3430+
cursor, disabledforeground, exportselection, fg, font,
3431+
foreground, height, highlightbackground, highlightcolor,
3432+
highlightthickness, inactiveselectbackground (Tk 9.1+),
3433+
inactiveselectforeground (Tk 9.1+), justify, listvariable,
3434+
relief, selectbackground, selectborderwidth, selectforeground,
3435+
selectmode, setgrid, state, takefocus, width, xscrollcommand,
3436+
yscrollcommand."""
34273437
Widget.__init__(self, master, 'listbox', cnf, kw)
34283438

34293439
def activate(self, index):
@@ -3533,7 +3543,8 @@ def __init__(self, master=None, cnf={}, **kw):
35333543
"""Construct menu widget with the parent MASTER.
35343544
35353545
Valid option names: activebackground, activeborderwidth,
3536-
activeforeground, background, bd, bg, borderwidth, cursor,
3546+
activeforeground, activerelief (Tk 9.0+), background, bd, bg,
3547+
borderwidth, cursor,
35373548
disabledforeground, fg, font, foreground, postcommand, relief,
35383549
selectcolor, takefocus, tearoff, tearoffcommand, title, type."""
35393550
Widget.__init__(self, master, 'menu', cnf, kw)
@@ -3663,13 +3674,27 @@ class Menubutton(Widget):
36633674
"""Menubutton widget, obsolete since Tk8.0."""
36643675

36653676
def __init__(self, master=None, cnf={}, **kw):
3677+
"""Construct a menubutton widget with the parent MASTER.
3678+
3679+
Valid option names: activebackground, activeforeground, anchor,
3680+
background, bd, bg, bitmap, borderwidth, compound, cursor,
3681+
direction, disabledforeground, fg, font, foreground, height,
3682+
highlightbackground, highlightcolor, highlightthickness,
3683+
image, indicatoron, justify, menu, padx, pady, relief, state,
3684+
takefocus, text, textvariable, underline, width, wraplength."""
36663685
Widget.__init__(self, master, 'menubutton', cnf, kw)
36673686

36683687

36693688
class Message(Widget):
36703689
"""Message widget to display multiline text. Obsolete since Label does it too."""
36713690

36723691
def __init__(self, master=None, cnf={}, **kw):
3692+
"""Construct a message widget with the parent MASTER.
3693+
3694+
Valid option names: anchor, aspect, background, bd, bg, borderwidth,
3695+
cursor, fg, font, foreground, highlightbackground,
3696+
highlightcolor, highlightthickness, justify, padx, pady,
3697+
relief, takefocus, text, textvariable, width."""
36733698
Widget.__init__(self, master, 'message', cnf, kw)
36743699

36753700

@@ -3680,12 +3705,13 @@ def __init__(self, master=None, cnf={}, **kw):
36803705
"""Construct a radiobutton widget with the parent MASTER.
36813706
36823707
Valid option names: activebackground, activeforeground, anchor,
3683-
background, bd, bg, bitmap, borderwidth, command, cursor,
3684-
disabledforeground, fg, font, foreground, height,
3685-
highlightbackground, highlightcolor, highlightthickness, image,
3686-
indicatoron, justify, padx, pady, relief, selectcolor, selectimage,
3687-
state, takefocus, text, textvariable, underline, value, variable,
3688-
width, wraplength."""
3708+
background, bd, bg, bitmap, borderwidth, command, compound,
3709+
cursor, disabledforeground, fg, font, foreground, height,
3710+
highlightbackground, highlightcolor, highlightthickness,
3711+
image, indicatoron, justify, offrelief, overrelief, padx,
3712+
pady, relief, selectcolor, selectimage, state, takefocus,
3713+
text, textvariable, tristateimage, tristatevalue, underline,
3714+
value, variable, width, wraplength."""
36893715
Widget.__init__(self, master, 'radiobutton', cnf, kw)
36903716

36913717
def deselect(self):
@@ -3816,9 +3842,11 @@ def __init__(self, master=None, cnf={}, **kw):
38163842
38173843
WIDGET-SPECIFIC OPTIONS
38183844
3819-
autoseparators, height, maxundo,
3820-
spacing1, spacing2, spacing3,
3821-
state, tabs, undo, width, wrap,
3845+
autoseparators, blockcursor, endline,
3846+
height, inactiveselectbackground,
3847+
insertunfocussed, locale (Tk 9.1+), maxundo,
3848+
spacing1, spacing2, spacing3, startline,
3849+
state, tabs, tabstyle, undo, width, wrap,
38223850
38233851
"""
38243852
Widget.__init__(self, master, 'text', cnf, kw)
@@ -4646,9 +4674,12 @@ def __init__(self, master=None, cnf={}, **kw):
46464674
buttondownrelief, buttonuprelief,
46474675
command, disabledbackground,
46484676
disabledforeground, format, from,
4649-
invalidcommand, increment,
4677+
invalidcommand, invcmd, increment,
4678+
locale (Tk 9.1+),
4679+
placeholder (Tk 9.0+),
4680+
placeholderforeground (Tk 9.0+),
46504681
readonlybackground, state, to,
4651-
validate, validatecommand values,
4682+
validate, validatecommand, vcmd, values,
46524683
width, wrap,
46534684
"""
46544685
Widget.__init__(self, master, 'spinbox', cnf, kw)
@@ -4837,8 +4868,9 @@ def __init__(self, master=None, cnf={}, **kw):
48374868
WIDGET-SPECIFIC OPTIONS
48384869
48394870
handlepad, handlesize, opaqueresize,
4840-
sashcursor, sashpad, sashrelief,
4841-
sashwidth, showhandle,
4871+
proxybackground, proxyborderwidth,
4872+
proxyrelief, sashcursor, sashpad,
4873+
sashrelief, sashwidth, showhandle,
48424874
"""
48434875
Widget.__init__(self, master, 'panedwindow', cnf, kw)
48444876

0 commit comments

Comments
 (0)