[Python-checkins] bpo-30853: IDLE: Factor a VarTrace class from configdialog.ConfigDialog. (#2872)
Terry Jan Reedy
webhook-mailer at python.org
Wed Jul 26 19:10:02 EDT 2017
https://github.com/python/cpython/commit/45bf723c6c591ec56a18dad8150ae89797450d8b
commit: 45bf723c6c591ec56a18dad8150ae89797450d8b
branch: master
author: csabella <cheryl.sabella at gmail.com>
committer: Terry Jan Reedy <tjreedy at udel.edu>
date: 2017-07-26T19:09:58-04:00
summary:
bpo-30853: IDLE: Factor a VarTrace class from configdialog.ConfigDialog. (#2872)
The new class manages pairs of tk Variables and trace callbacks.
It is completely covered by new tests.
files:
M Lib/idlelib/configdialog.py
M Lib/idlelib/idle_test/test_configdialog.py
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 1832e156dc6..f98af4600ee 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -1846,6 +1846,61 @@ def save_all_changed_extensions(self):
self.ext_userCfg.Save()
+class VarTrace:
+ """Maintain Tk variables trace state."""
+
+ def __init__(self):
+ """Store Tk variables and callbacks.
+
+ untraced: List of tuples (var, callback)
+ that do not have the callback attached
+ to the Tk var.
+ traced: List of tuples (var, callback) where
+ that callback has been attached to the var.
+ """
+ self.untraced = []
+ self.traced = []
+
+ def add(self, var, callback):
+ """Add (var, callback) tuple to untraced list.
+
+ Args:
+ var: Tk variable instance.
+ callback: Function to be used as a callback or
+ a tuple with IdleConf values for default
+ callback.
+
+ Return:
+ Tk variable instance.
+ """
+ if isinstance(callback, tuple):
+ callback = self.make_callback(var, callback)
+ self.untraced.append((var, callback))
+ return var
+
+ @staticmethod
+ def make_callback(var, config):
+ "Return default callback function to add values to changes instance."
+ def default_callback(*params):
+ "Add config values to changes instance."
+ changes.add_option(*config, var.get())
+ return default_callback
+
+ def attach(self):
+ "Attach callback to all vars that are not traced."
+ while self.untraced:
+ var, callback = self.untraced.pop()
+ var.trace_add('write', callback)
+ self.traced.append((var, callback))
+
+ def detach(self):
+ "Remove callback from traced vars."
+ while self.traced:
+ var, callback = self.traced.pop()
+ var.trace_remove('write', var.trace_info()[0][1])
+ self.untraced.append((var, callback))
+
+
help_common = '''\
When you click either the Apply or Ok buttons, settings in this
dialog that are different from IDLE's default are saved in
diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py
index 54b2d78d667..ce02ae4a8e6 100644
--- a/Lib/idlelib/idle_test/test_configdialog.py
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -3,11 +3,12 @@
Half the class creates dialog, half works with user customizations.
Coverage: 46% just by creating dialog, 60% with current tests.
"""
-from idlelib.configdialog import ConfigDialog, idleConf, changes
+from idlelib.configdialog import ConfigDialog, idleConf, changes, VarTrace
from test.support import requires
requires('gui')
-from tkinter import Tk
+from tkinter import Tk, IntVar, BooleanVar
import unittest
+from unittest import mock
import idlelib.config as config
from idlelib.idle_test.mock_idle import Func
@@ -248,5 +249,94 @@ def test_editor_size(self):
#def test_help_sources(self): pass # TODO
+class TestVarTrace(unittest.TestCase):
+
+ def setUp(self):
+ changes.clear()
+ self.v1 = IntVar(root)
+ self.v2 = BooleanVar(root)
+ self.called = 0
+ self.tracers = VarTrace()
+
+ def tearDown(self):
+ del self.v1, self.v2
+
+ def var_changed_increment(self, *params):
+ self.called += 13
+
+ def var_changed_boolean(self, *params):
+ pass
+
+ def test_init(self):
+ self.assertEqual(self.tracers.untraced, [])
+ self.assertEqual(self.tracers.traced, [])
+
+ def test_add(self):
+ tr = self.tracers
+ func = Func()
+ cb = tr.make_callback = mock.Mock(return_value=func)
+
+ v1 = tr.add(self.v1, self.var_changed_increment)
+ self.assertIsInstance(v1, IntVar)
+ v2 = tr.add(self.v2, self.var_changed_boolean)
+ self.assertIsInstance(v2, BooleanVar)
+
+ v3 = IntVar(root)
+ v3 = tr.add(v3, ('main', 'section', 'option'))
+ cb.assert_called_once()
+ cb.assert_called_with(v3, ('main', 'section', 'option'))
+
+ expected = [(v1, self.var_changed_increment),
+ (v2, self.var_changed_boolean),
+ (v3, func)]
+ self.assertEqual(tr.traced, [])
+ self.assertEqual(tr.untraced, expected)
+
+ del tr.make_callback
+
+ def test_make_callback(self):
+ tr = self.tracers
+ cb = tr.make_callback(self.v1, ('main', 'section', 'option'))
+ self.assertTrue(callable(cb))
+ self.v1.set(42)
+ # Not attached, so set didn't invoke the callback.
+ self.assertNotIn('section', changes['main'])
+ # Invoke callback manually.
+ cb()
+ self.assertIn('section', changes['main'])
+ self.assertEqual(changes['main']['section']['option'], '42')
+
+ def test_attach_detach(self):
+ tr = self.tracers
+ v1 = tr.add(self.v1, self.var_changed_increment)
+ v2 = tr.add(self.v2, self.var_changed_boolean)
+ expected = [(v1, self.var_changed_increment),
+ (v2, self.var_changed_boolean)]
+
+ # Attach callbacks and test call increment.
+ tr.attach()
+ self.assertEqual(tr.untraced, [])
+ self.assertCountEqual(tr.traced, expected)
+ v1.set(1)
+ self.assertEqual(v1.get(), 1)
+ self.assertEqual(self.called, 13)
+
+ # Check that only one callback is attached to a variable.
+ # If more than one callback were attached, then var_changed_increment
+ # would be called twice and the counter would be 2.
+ self.called = 0
+ tr.attach()
+ v1.set(1)
+ self.assertEqual(self.called, 13)
+
+ # Detach callbacks.
+ self.called = 0
+ tr.detach()
+ self.assertEqual(tr.traced, [])
+ self.assertCountEqual(tr.untraced, expected)
+ v1.set(1)
+ self.assertEqual(self.called, 0)
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
More information about the Python-checkins
mailing list