Skip to content

Commit 5883e23

Browse files
gh-78335: Complete the widget option lists in tkinter docstrings (GH-152485)
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. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> (cherry picked from commit ba0c0e6)
1 parent ecf3713 commit 5883e23

6 files changed

Lines changed: 164 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: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@
99

1010
_sentinel = object()
1111

12+
# Abbreviated option names accepted by Tk in addition to the full names.
13+
_OPTION_ALIASES = {
14+
'bd': 'borderwidth',
15+
'bg': 'background',
16+
'bgimg': 'backgroundimage',
17+
'fg': 'foreground',
18+
'invcmd': 'invalidcommand',
19+
'vcmd': 'validatecommand',
20+
}
21+
22+
# Options which accept all values allowed by Tk_GetPixels
23+
# borderwidth = bd
24+
1225
class AbstractWidgetTest(AbstractTkTest):
1326
_default_pixels = '' if tk_version >= (9, 0) else -1 if tk_version >= (8, 7) else ''
1427
_conv_pixels = round
@@ -198,23 +211,44 @@ def test_keys(self):
198211
widget[k]
199212
# Test if OPTIONS contains all keys
200213
if test.support.verbose:
201-
aliases = {
202-
'bd': 'borderwidth',
203-
'bg': 'background',
204-
'bgimg': 'backgroundimage',
205-
'fg': 'foreground',
206-
'invcmd': 'invalidcommand',
207-
'vcmd': 'validatecommand',
208-
}
209214
keys = set(keys)
210215
expected = set(self.OPTIONS)
211216
for k in sorted(keys - expected):
212-
if not (k in aliases and
213-
aliases[k] in keys and
214-
aliases[k] in expected):
217+
if not (k in _OPTION_ALIASES and
218+
_OPTION_ALIASES[k] in keys and
219+
_OPTION_ALIASES[k] in expected):
215220
print('%s.OPTIONS doesn\'t contain "%s"' %
216221
(self.__class__.__name__, k))
217222

223+
def test_options_in_docstring(self):
224+
# Every option in OPTIONS must be listed in the docstring of the
225+
# __init__ method (see gh-78335). Options reported by keys() but
226+
# missing from the docstring are only printed in verbose mode, as
227+
# some of them depend on the Tk version.
228+
widget = self.create()
229+
doc = type(widget).__init__.__doc__
230+
if doc is None:
231+
self.skipTest('docstrings are not available (run with -OO)')
232+
# Look at the option list only, not the leading description.
233+
start = doc.find('Valid option names')
234+
if start < 0:
235+
start = doc.find('OPTIONS')
236+
if start < 0:
237+
self.skipTest('the __init__ docstring does not list options')
238+
documented = set(re.findall(r'[a-z][a-z0-9]+', doc[start:]))
239+
def is_documented(option):
240+
return (option in documented or
241+
_OPTION_ALIASES.get(option) in documented)
242+
missing = sorted(o for o in self.OPTIONS if not is_documented(o))
243+
self.assertEqual(missing, [],
244+
'%s options missing from the __init__ docstring: %s'
245+
% (type(widget).__name__, missing))
246+
if test.support.verbose:
247+
for key in sorted(set(widget.keys())):
248+
if not is_documented(key):
249+
print('%s.__init__ docstring doesn\'t contain "%s"'
250+
% (type(widget).__name__, key))
251+
218252

219253
class StandardOptionsTests:
220254
STANDARD_OPTIONS = (

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
@@ -2841,10 +2841,11 @@ class Toplevel(BaseWidget, Wm):
28412841
def __init__(self, master=None, cnf={}, **kw):
28422842
"""Construct a toplevel widget with the parent MASTER.
28432843
2844-
Valid option names: background, bd, bg, borderwidth, class,
2845-
colormap, container, cursor, height, highlightbackground,
2846-
highlightcolor, highlightthickness, menu, relief, screen, takefocus,
2847-
use, visual, width."""
2844+
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
2845+
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
2846+
cursor, height, highlightbackground, highlightcolor,
2847+
highlightthickness, menu, padx, pady, relief, screen,
2848+
takefocus, tile (Tk 9.0+), use, visual, width."""
28482849
if kw:
28492850
cnf = _cnfmerge((cnf, kw))
28502851
extra = ()
@@ -3227,12 +3228,13 @@ def __init__(self, master=None, cnf={}, **kw):
32273228
"""Construct a checkbutton widget with the parent MASTER.
32283229
32293230
Valid option names: activebackground, activeforeground, anchor,
3230-
background, bd, bg, bitmap, borderwidth, command, cursor,
3231-
disabledforeground, fg, font, foreground, height,
3232-
highlightbackground, highlightcolor, highlightthickness, image,
3233-
indicatoron, justify, offvalue, onvalue, padx, pady, relief,
3234-
selectcolor, selectimage, state, takefocus, text, textvariable,
3235-
underline, variable, width, wraplength."""
3231+
background, bd, bg, bitmap, borderwidth, command, compound,
3232+
cursor, disabledforeground, fg, font, foreground, height,
3233+
highlightbackground, highlightcolor, highlightthickness,
3234+
image, indicatoron, justify, offrelief, offvalue, onvalue,
3235+
overrelief, padx, pady, relief, selectcolor, selectimage,
3236+
state, takefocus, text, textvariable, tristateimage,
3237+
tristatevalue, underline, variable, width, wraplength."""
32363238
Widget.__init__(self, master, 'checkbutton', cnf, kw)
32373239

32383240
def _setup(self, master, cnf):
@@ -3276,13 +3278,15 @@ def __init__(self, master=None, cnf={}, **kw):
32763278
"""Construct an entry widget with the parent MASTER.
32773279
32783280
Valid option names: background, bd, bg, borderwidth, cursor,
3279-
exportselection, fg, font, foreground, highlightbackground,
3280-
highlightcolor, highlightthickness, insertbackground,
3281-
insertborderwidth, insertofftime, insertontime, insertwidth,
3282-
invalidcommand, invcmd, justify, relief, selectbackground,
3283-
selectborderwidth, selectforeground, show, state, takefocus,
3284-
textvariable, validate, validatecommand, vcmd, width,
3285-
xscrollcommand."""
3281+
disabledbackground, disabledforeground, exportselection, fg,
3282+
font, foreground, highlightbackground, highlightcolor,
3283+
highlightthickness, insertbackground, insertborderwidth,
3284+
insertofftime, insertontime, insertwidth, invalidcommand,
3285+
invcmd, justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
3286+
placeholderforeground (Tk 9.0+), readonlybackground, relief,
3287+
selectbackground, selectborderwidth, selectforeground, show,
3288+
state, takefocus, textvariable, validate, validatecommand,
3289+
vcmd, width, xscrollcommand."""
32863290
Widget.__init__(self, master, 'entry', cnf, kw)
32873291

32883292
def delete(self, first, last=None):
@@ -3361,9 +3365,11 @@ class Frame(Widget):
33613365
def __init__(self, master=None, cnf={}, **kw):
33623366
"""Construct a frame widget with the parent MASTER.
33633367
3364-
Valid option names: background, bd, bg, borderwidth, class,
3365-
colormap, container, cursor, height, highlightbackground,
3366-
highlightcolor, highlightthickness, relief, takefocus, visual, width."""
3368+
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
3369+
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
3370+
cursor, height, highlightbackground, highlightcolor,
3371+
highlightthickness, padx, pady, relief, takefocus, tile (Tk
3372+
9.0+), visual, width."""
33673373
cnf = _cnfmerge((cnf, kw))
33683374
extra = ()
33693375
if 'class_' in cnf:
@@ -3393,7 +3399,8 @@ def __init__(self, master=None, cnf={}, **kw):
33933399
33943400
WIDGET-SPECIFIC OPTIONS
33953401
3396-
height, state, width
3402+
compound, height, state,
3403+
textangle (Tk 9.1+), width
33973404
33983405
"""
33993406
Widget.__init__(self, master, 'label', cnf, kw)
@@ -3405,11 +3412,14 @@ class Listbox(Widget, XView, YView):
34053412
def __init__(self, master=None, cnf={}, **kw):
34063413
"""Construct a listbox widget with the parent MASTER.
34073414
3408-
Valid option names: background, bd, bg, borderwidth, cursor,
3409-
exportselection, fg, font, foreground, height, highlightbackground,
3410-
highlightcolor, highlightthickness, relief, selectbackground,
3411-
selectborderwidth, selectforeground, selectmode, setgrid, takefocus,
3412-
width, xscrollcommand, yscrollcommand, listvariable."""
3415+
Valid option names: activestyle, background, bd, bg, borderwidth,
3416+
cursor, disabledforeground, exportselection, fg, font,
3417+
foreground, height, highlightbackground, highlightcolor,
3418+
highlightthickness, inactiveselectbackground (Tk 9.1+),
3419+
inactiveselectforeground (Tk 9.1+), justify, listvariable,
3420+
relief, selectbackground, selectborderwidth, selectforeground,
3421+
selectmode, setgrid, state, takefocus, width, xscrollcommand,
3422+
yscrollcommand."""
34133423
Widget.__init__(self, master, 'listbox', cnf, kw)
34143424

34153425
def activate(self, index):
@@ -3519,7 +3529,8 @@ def __init__(self, master=None, cnf={}, **kw):
35193529
"""Construct menu widget with the parent MASTER.
35203530
35213531
Valid option names: activebackground, activeborderwidth,
3522-
activeforeground, background, bd, bg, borderwidth, cursor,
3532+
activeforeground, activerelief (Tk 9.0+), background, bd, bg,
3533+
borderwidth, cursor,
35233534
disabledforeground, fg, font, foreground, postcommand, relief,
35243535
selectcolor, takefocus, tearoff, tearoffcommand, title, type."""
35253536
Widget.__init__(self, master, 'menu', cnf, kw)
@@ -3649,13 +3660,27 @@ class Menubutton(Widget):
36493660
"""Menubutton widget, obsolete since Tk8.0."""
36503661

36513662
def __init__(self, master=None, cnf={}, **kw):
3663+
"""Construct a menubutton widget with the parent MASTER.
3664+
3665+
Valid option names: activebackground, activeforeground, anchor,
3666+
background, bd, bg, bitmap, borderwidth, compound, cursor,
3667+
direction, disabledforeground, fg, font, foreground, height,
3668+
highlightbackground, highlightcolor, highlightthickness,
3669+
image, indicatoron, justify, menu, padx, pady, relief, state,
3670+
takefocus, text, textvariable, underline, width, wraplength."""
36523671
Widget.__init__(self, master, 'menubutton', cnf, kw)
36533672

36543673

36553674
class Message(Widget):
36563675
"""Message widget to display multiline text. Obsolete since Label does it too."""
36573676

36583677
def __init__(self, master=None, cnf={}, **kw):
3678+
"""Construct a message widget with the parent MASTER.
3679+
3680+
Valid option names: anchor, aspect, background, bd, bg, borderwidth,
3681+
cursor, fg, font, foreground, highlightbackground,
3682+
highlightcolor, highlightthickness, justify, padx, pady,
3683+
relief, takefocus, text, textvariable, width."""
36593684
Widget.__init__(self, master, 'message', cnf, kw)
36603685

36613686

@@ -3666,12 +3691,13 @@ def __init__(self, master=None, cnf={}, **kw):
36663691
"""Construct a radiobutton widget with the parent MASTER.
36673692
36683693
Valid option names: activebackground, activeforeground, anchor,
3669-
background, bd, bg, bitmap, borderwidth, command, cursor,
3670-
disabledforeground, fg, font, foreground, height,
3671-
highlightbackground, highlightcolor, highlightthickness, image,
3672-
indicatoron, justify, padx, pady, relief, selectcolor, selectimage,
3673-
state, takefocus, text, textvariable, underline, value, variable,
3674-
width, wraplength."""
3694+
background, bd, bg, bitmap, borderwidth, command, compound,
3695+
cursor, disabledforeground, fg, font, foreground, height,
3696+
highlightbackground, highlightcolor, highlightthickness,
3697+
image, indicatoron, justify, offrelief, overrelief, padx,
3698+
pady, relief, selectcolor, selectimage, state, takefocus,
3699+
text, textvariable, tristateimage, tristatevalue, underline,
3700+
value, variable, width, wraplength."""
36753701
Widget.__init__(self, master, 'radiobutton', cnf, kw)
36763702

36773703
def deselect(self):
@@ -3802,9 +3828,11 @@ def __init__(self, master=None, cnf={}, **kw):
38023828
38033829
WIDGET-SPECIFIC OPTIONS
38043830
3805-
autoseparators, height, maxundo,
3806-
spacing1, spacing2, spacing3,
3807-
state, tabs, undo, width, wrap,
3831+
autoseparators, blockcursor, endline,
3832+
height, inactiveselectbackground,
3833+
insertunfocussed, locale (Tk 9.1+), maxundo,
3834+
spacing1, spacing2, spacing3, startline,
3835+
state, tabs, tabstyle, undo, width, wrap,
38083836
38093837
"""
38103838
Widget.__init__(self, master, 'text', cnf, kw)
@@ -4632,9 +4660,12 @@ def __init__(self, master=None, cnf={}, **kw):
46324660
buttondownrelief, buttonuprelief,
46334661
command, disabledbackground,
46344662
disabledforeground, format, from,
4635-
invalidcommand, increment,
4663+
invalidcommand, invcmd, increment,
4664+
locale (Tk 9.1+),
4665+
placeholder (Tk 9.0+),
4666+
placeholderforeground (Tk 9.0+),
46364667
readonlybackground, state, to,
4637-
validate, validatecommand values,
4668+
validate, validatecommand, vcmd, values,
46384669
width, wrap,
46394670
"""
46404671
Widget.__init__(self, master, 'spinbox', cnf, kw)
@@ -4823,8 +4854,9 @@ def __init__(self, master=None, cnf={}, **kw):
48234854
WIDGET-SPECIFIC OPTIONS
48244855
48254856
handlepad, handlesize, opaqueresize,
4826-
sashcursor, sashpad, sashrelief,
4827-
sashwidth, showhandle,
4857+
proxybackground, proxyborderwidth,
4858+
proxyrelief, sashcursor, sashpad,
4859+
sashrelief, sashwidth, showhandle,
48284860
"""
48294861
Widget.__init__(self, master, 'panedwindow', cnf, kw)
48304862

0 commit comments

Comments
 (0)