Skip to content

Commit 74382a3

Browse files
csabellaserhiy-storchaka
authored andcommitted
bpo-32857: Raise error when tkinter after_cancel() is called with None. (GH-5701)
1 parent 7023644 commit 74382a3

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed

Lib/tkinter/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ def after(self, ms, func=None, *args):
739739
if not func:
740740
# I'd rather use time.sleep(ms*0.001)
741741
self.tk.call('after', ms)
742+
return None
742743
else:
743744
def callit():
744745
try:
@@ -762,11 +763,13 @@ def after_cancel(self, id):
762763
"""Cancel scheduling of function identified with ID.
763764
764765
Identifier returned by after or after_idle must be
765-
given as first parameter."""
766+
given as first parameter.
767+
"""
768+
if not id:
769+
raise ValueError('id must be a valid identifier returned from '
770+
'after or after_idle')
766771
try:
767772
data = self.tk.call('after', 'info', id)
768-
# In Tk 8.3, splitlist returns: (script, type)
769-
# In Tk 8.4, splitlist may return (script, type) or (script,)
770773
script = self.tk.splitlist(data)[0]
771774
self.deletecommand(script)
772775
except TclError:

Lib/tkinter/test/test_tkinter/test_misc.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,114 @@ def test_tk_setPalette(self):
4848
'^must specify a background color$',
4949
root.tk_setPalette, highlightColor='blue')
5050

51+
def test_after(self):
52+
root = self.root
53+
54+
def callback(start=0, step=1):
55+
nonlocal count
56+
count = start + step
57+
58+
# Without function, sleeps for ms.
59+
self.assertIsNone(root.after(1))
60+
61+
# Set up with callback with no args.
62+
count = 0
63+
timer1 = root.after(0, callback)
64+
self.assertIn(timer1, root.tk.call('after', 'info'))
65+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1))
66+
root.update() # Process all pending events.
67+
self.assertEqual(count, 1)
68+
with self.assertRaises(tkinter.TclError):
69+
root.tk.call(script)
70+
71+
# Set up with callback with args.
72+
count = 0
73+
timer1 = root.after(0, callback, 42, 11)
74+
root.update() # Process all pending events.
75+
self.assertEqual(count, 53)
76+
77+
# Cancel before called.
78+
timer1 = root.after(1000, callback)
79+
self.assertIn(timer1, root.tk.call('after', 'info'))
80+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1))
81+
root.after_cancel(timer1) # Cancel this event.
82+
self.assertEqual(count, 53)
83+
with self.assertRaises(tkinter.TclError):
84+
root.tk.call(script)
85+
86+
def test_after_idle(self):
87+
root = self.root
88+
89+
def callback(start=0, step=1):
90+
nonlocal count
91+
count = start + step
92+
93+
# Set up with callback with no args.
94+
count = 0
95+
idle1 = root.after_idle(callback)
96+
self.assertIn(idle1, root.tk.call('after', 'info'))
97+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1))
98+
root.update_idletasks() # Process all pending events.
99+
self.assertEqual(count, 1)
100+
with self.assertRaises(tkinter.TclError):
101+
root.tk.call(script)
102+
103+
# Set up with callback with args.
104+
count = 0
105+
idle1 = root.after_idle(callback, 42, 11)
106+
root.update_idletasks() # Process all pending events.
107+
self.assertEqual(count, 53)
108+
109+
# Cancel before called.
110+
idle1 = root.after_idle(callback)
111+
self.assertIn(idle1, root.tk.call('after', 'info'))
112+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1))
113+
root.after_cancel(idle1) # Cancel this event.
114+
self.assertEqual(count, 53)
115+
with self.assertRaises(tkinter.TclError):
116+
root.tk.call(script)
117+
118+
def test_after_cancel(self):
119+
root = self.root
120+
121+
def callback():
122+
nonlocal count
123+
count += 1
124+
125+
timer1 = root.after(5000, callback)
126+
idle1 = root.after_idle(callback)
127+
128+
# No value for id raises a ValueError.
129+
with self.assertRaises(ValueError):
130+
root.after_cancel(None)
131+
132+
# Cancel timer event.
133+
count = 0
134+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', timer1))
135+
root.tk.call(script)
136+
self.assertEqual(count, 1)
137+
root.after_cancel(timer1)
138+
with self.assertRaises(tkinter.TclError):
139+
root.tk.call(script)
140+
self.assertEqual(count, 1)
141+
with self.assertRaises(tkinter.TclError):
142+
root.tk.call('after', 'info', timer1)
143+
144+
# Cancel same event - nothing happens.
145+
root.after_cancel(timer1)
146+
147+
# Cancel idle event.
148+
count = 0
149+
(script, _) = root.tk.splitlist(root.tk.call('after', 'info', idle1))
150+
root.tk.call(script)
151+
self.assertEqual(count, 1)
152+
root.after_cancel(idle1)
153+
with self.assertRaises(tkinter.TclError):
154+
root.tk.call(script)
155+
self.assertEqual(count, 1)
156+
with self.assertRaises(tkinter.TclError):
157+
root.tk.call('after', 'info', idle1)
158+
51159

52160
tests_gui = (MiscTest, )
53161

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
In :mod:`tkinter`, ``after_cancel(None)`` now raises a :exc:`ValueError` instead of canceling the first scheduled function. Patch by Cheryl Sabella.

0 commit comments

Comments
 (0)