Skip to content

Commit 25d62d3

Browse files
serhiy-storchakaclaude
authored andcommitted
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. (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 26caf56 commit 25d62d3

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')
@@ -312,6 +313,7 @@ def test_pack_short_aliases(self):
312313
class PlaceTest(AbstractWidgetTest, unittest.TestCase):
313314

314315
test_keys = None
316+
test_options_in_docstring = None
315317

316318
def create2(self):
317319
t = tkinter.Toplevel(self.root, width=300, height=200, bd=0)
@@ -538,6 +540,7 @@ def test_place_method_aliases(self):
538540
class GridTest(AbstractWidgetTest, unittest.TestCase):
539541

540542
test_keys = None
543+
test_options_in_docstring = None
541544

542545
def tearDown(self):
543546
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
@@ -2929,10 +2929,11 @@ class Toplevel(BaseWidget, Wm):
29292929
def __init__(self, master=None, cnf={}, **kw):
29302930
"""Construct a toplevel widget with the parent MASTER.
29312931
2932-
Valid option names: background, bd, bg, borderwidth, class,
2933-
colormap, container, cursor, height, highlightbackground,
2934-
highlightcolor, highlightthickness, menu, relief, screen, takefocus,
2935-
use, visual, width."""
2932+
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
2933+
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
2934+
cursor, height, highlightbackground, highlightcolor,
2935+
highlightthickness, menu, padx, pady, relief, screen,
2936+
takefocus, tile (Tk 9.0+), use, visual, width."""
29362937
if kw:
29372938
cnf = _cnfmerge((cnf, kw))
29382939
extra = ()
@@ -3315,12 +3316,13 @@ def __init__(self, master=None, cnf={}, **kw):
33153316
"""Construct a checkbutton widget with the parent MASTER.
33163317
33173318
Valid option names: activebackground, activeforeground, anchor,
3318-
background, bd, bg, bitmap, borderwidth, command, cursor,
3319-
disabledforeground, fg, font, foreground, height,
3320-
highlightbackground, highlightcolor, highlightthickness, image,
3321-
indicatoron, justify, offvalue, onvalue, padx, pady, relief,
3322-
selectcolor, selectimage, state, takefocus, text, textvariable,
3323-
underline, variable, width, wraplength."""
3319+
background, bd, bg, bitmap, borderwidth, command, compound,
3320+
cursor, disabledforeground, fg, font, foreground, height,
3321+
highlightbackground, highlightcolor, highlightthickness,
3322+
image, indicatoron, justify, offrelief, offvalue, onvalue,
3323+
overrelief, padx, pady, relief, selectcolor, selectimage,
3324+
state, takefocus, text, textvariable, tristateimage,
3325+
tristatevalue, underline, variable, width, wraplength."""
33243326
Widget.__init__(self, master, 'checkbutton', cnf, kw)
33253327

33263328
def _setup(self, master, cnf):
@@ -3364,13 +3366,15 @@ def __init__(self, master=None, cnf={}, **kw):
33643366
"""Construct an entry widget with the parent MASTER.
33653367
33663368
Valid option names: background, bd, bg, borderwidth, cursor,
3367-
exportselection, fg, font, foreground, highlightbackground,
3368-
highlightcolor, highlightthickness, insertbackground,
3369-
insertborderwidth, insertofftime, insertontime, insertwidth,
3370-
invalidcommand, invcmd, justify, relief, selectbackground,
3371-
selectborderwidth, selectforeground, show, state, takefocus,
3372-
textvariable, validate, validatecommand, vcmd, width,
3373-
xscrollcommand."""
3369+
disabledbackground, disabledforeground, exportselection, fg,
3370+
font, foreground, highlightbackground, highlightcolor,
3371+
highlightthickness, insertbackground, insertborderwidth,
3372+
insertofftime, insertontime, insertwidth, invalidcommand,
3373+
invcmd, justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
3374+
placeholderforeground (Tk 9.0+), readonlybackground, relief,
3375+
selectbackground, selectborderwidth, selectforeground, show,
3376+
state, takefocus, textvariable, validate, validatecommand,
3377+
vcmd, width, xscrollcommand."""
33743378
Widget.__init__(self, master, 'entry', cnf, kw)
33753379

33763380
def delete(self, first, last=None):
@@ -3449,9 +3453,11 @@ class Frame(Widget):
34493453
def __init__(self, master=None, cnf={}, **kw):
34503454
"""Construct a frame widget with the parent MASTER.
34513455
3452-
Valid option names: background, bd, bg, borderwidth, class,
3453-
colormap, container, cursor, height, highlightbackground,
3454-
highlightcolor, highlightthickness, relief, takefocus, visual, width."""
3456+
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
3457+
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
3458+
cursor, height, highlightbackground, highlightcolor,
3459+
highlightthickness, padx, pady, relief, takefocus, tile (Tk
3460+
9.0+), visual, width."""
34553461
cnf = _cnfmerge((cnf, kw))
34563462
extra = ()
34573463
if 'class_' in cnf:
@@ -3481,7 +3487,8 @@ def __init__(self, master=None, cnf={}, **kw):
34813487
34823488
WIDGET-SPECIFIC OPTIONS
34833489
3484-
height, state, width
3490+
compound, height, state,
3491+
textangle (Tk 9.1+), width
34853492
34863493
"""
34873494
Widget.__init__(self, master, 'label', cnf, kw)
@@ -3493,11 +3500,14 @@ class Listbox(Widget, XView, YView):
34933500
def __init__(self, master=None, cnf={}, **kw):
34943501
"""Construct a listbox widget with the parent MASTER.
34953502
3496-
Valid option names: background, bd, bg, borderwidth, cursor,
3497-
exportselection, fg, font, foreground, height, highlightbackground,
3498-
highlightcolor, highlightthickness, relief, selectbackground,
3499-
selectborderwidth, selectforeground, selectmode, setgrid, takefocus,
3500-
width, xscrollcommand, yscrollcommand, listvariable."""
3503+
Valid option names: activestyle, background, bd, bg, borderwidth,
3504+
cursor, disabledforeground, exportselection, fg, font,
3505+
foreground, height, highlightbackground, highlightcolor,
3506+
highlightthickness, inactiveselectbackground (Tk 9.1+),
3507+
inactiveselectforeground (Tk 9.1+), justify, listvariable,
3508+
relief, selectbackground, selectborderwidth, selectforeground,
3509+
selectmode, setgrid, state, takefocus, width, xscrollcommand,
3510+
yscrollcommand."""
35013511
Widget.__init__(self, master, 'listbox', cnf, kw)
35023512

35033513
def activate(self, index):
@@ -3607,7 +3617,8 @@ def __init__(self, master=None, cnf={}, **kw):
36073617
"""Construct menu widget with the parent MASTER.
36083618
36093619
Valid option names: activebackground, activeborderwidth,
3610-
activeforeground, background, bd, bg, borderwidth, cursor,
3620+
activeforeground, activerelief (Tk 9.0+), background, bd, bg,
3621+
borderwidth, cursor,
36113622
disabledforeground, fg, font, foreground, postcommand, relief,
36123623
selectcolor, takefocus, tearoff, tearoffcommand, title, type."""
36133624
Widget.__init__(self, master, 'menu', cnf, kw)
@@ -3737,13 +3748,27 @@ class Menubutton(Widget):
37373748
"""Menubutton widget, obsolete since Tk8.0."""
37383749

37393750
def __init__(self, master=None, cnf={}, **kw):
3751+
"""Construct a menubutton widget with the parent MASTER.
3752+
3753+
Valid option names: activebackground, activeforeground, anchor,
3754+
background, bd, bg, bitmap, borderwidth, compound, cursor,
3755+
direction, disabledforeground, fg, font, foreground, height,
3756+
highlightbackground, highlightcolor, highlightthickness,
3757+
image, indicatoron, justify, menu, padx, pady, relief, state,
3758+
takefocus, text, textvariable, underline, width, wraplength."""
37403759
Widget.__init__(self, master, 'menubutton', cnf, kw)
37413760

37423761

37433762
class Message(Widget):
37443763
"""Message widget to display multiline text. Obsolete since Label does it too."""
37453764

37463765
def __init__(self, master=None, cnf={}, **kw):
3766+
"""Construct a message widget with the parent MASTER.
3767+
3768+
Valid option names: anchor, aspect, background, bd, bg, borderwidth,
3769+
cursor, fg, font, foreground, highlightbackground,
3770+
highlightcolor, highlightthickness, justify, padx, pady,
3771+
relief, takefocus, text, textvariable, width."""
37473772
Widget.__init__(self, master, 'message', cnf, kw)
37483773

37493774

@@ -3754,12 +3779,13 @@ def __init__(self, master=None, cnf={}, **kw):
37543779
"""Construct a radiobutton widget with the parent MASTER.
37553780
37563781
Valid option names: activebackground, activeforeground, anchor,
3757-
background, bd, bg, bitmap, borderwidth, command, cursor,
3758-
disabledforeground, fg, font, foreground, height,
3759-
highlightbackground, highlightcolor, highlightthickness, image,
3760-
indicatoron, justify, padx, pady, relief, selectcolor, selectimage,
3761-
state, takefocus, text, textvariable, underline, value, variable,
3762-
width, wraplength."""
3782+
background, bd, bg, bitmap, borderwidth, command, compound,
3783+
cursor, disabledforeground, fg, font, foreground, height,
3784+
highlightbackground, highlightcolor, highlightthickness,
3785+
image, indicatoron, justify, offrelief, overrelief, padx,
3786+
pady, relief, selectcolor, selectimage, state, takefocus,
3787+
text, textvariable, tristateimage, tristatevalue, underline,
3788+
value, variable, width, wraplength."""
37633789
Widget.__init__(self, master, 'radiobutton', cnf, kw)
37643790

37653791
def deselect(self):
@@ -3890,9 +3916,11 @@ def __init__(self, master=None, cnf={}, **kw):
38903916
38913917
WIDGET-SPECIFIC OPTIONS
38923918
3893-
autoseparators, height, maxundo,
3894-
spacing1, spacing2, spacing3,
3895-
state, tabs, undo, width, wrap,
3919+
autoseparators, blockcursor, endline,
3920+
height, inactiveselectbackground,
3921+
insertunfocussed, locale (Tk 9.1+), maxundo,
3922+
spacing1, spacing2, spacing3, startline,
3923+
state, tabs, tabstyle, undo, width, wrap,
38963924
38973925
"""
38983926
Widget.__init__(self, master, 'text', cnf, kw)
@@ -4750,9 +4778,12 @@ def __init__(self, master=None, cnf={}, **kw):
47504778
buttondownrelief, buttonuprelief,
47514779
command, disabledbackground,
47524780
disabledforeground, format, from,
4753-
invalidcommand, increment,
4781+
invalidcommand, invcmd, increment,
4782+
locale (Tk 9.1+),
4783+
placeholder (Tk 9.0+),
4784+
placeholderforeground (Tk 9.0+),
47544785
readonlybackground, state, to,
4755-
validate, validatecommand values,
4786+
validate, validatecommand, vcmd, values,
47564787
width, wrap,
47574788
"""
47584789
Widget.__init__(self, master, 'spinbox', cnf, kw)
@@ -4941,8 +4972,9 @@ def __init__(self, master=None, cnf={}, **kw):
49414972
WIDGET-SPECIFIC OPTIONS
49424973
49434974
handlepad, handlesize, opaqueresize,
4944-
sashcursor, sashpad, sashrelief,
4945-
sashwidth, showhandle,
4975+
proxybackground, proxyborderwidth,
4976+
proxyrelief, sashcursor, sashpad,
4977+
sashrelief, sashwidth, showhandle,
49464978
"""
49474979
Widget.__init__(self, master, 'panedwindow', cnf, kw)
49484980

0 commit comments

Comments
 (0)