[Python-checkins] cpython: Issue 27437: Add query.ModuleName and use it for file => Load Module.

terry.reedy python-checkins at python.org
Sun Jul 3 19:11:30 EDT 2016


https://hg.python.org/cpython/rev/78a3d3700233
changeset:   102253:78a3d3700233
user:        Terry Jan Reedy <tjreedy at udel.edu>
date:        Sun Jul 03 19:11:13 2016 -0400
summary:
  Issue 27437: Add query.ModuleName and use it for file => Load Module.
Users can now edit bad entries instead of starting over.

files:
  Lib/idlelib/README.txt              |    6 +-
  Lib/idlelib/editor.py               |   58 ++-----
  Lib/idlelib/idle_test/htest.py      |    5 +-
  Lib/idlelib/idle_test/test_query.py |  110 +++++++++++++--
  Lib/idlelib/query.py                |   83 +++++++++--
  5 files changed, 185 insertions(+), 77 deletions(-)


diff --git a/Lib/idlelib/README.txt b/Lib/idlelib/README.txt
--- a/Lib/idlelib/README.txt
+++ b/Lib/idlelib/README.txt
@@ -41,7 +41,6 @@
 configdialog.py   # Display user configuration dialogs.
 config_help.py    # Specify help source in configdialog.
 config_key.py     # Change keybindings.
-config_sec.py     # Spefify user config section name
 dynoption.py      # Define mutable OptionMenu widget (nim).
 debugobj.py       # Define class used in stackviewer.
 debugobj_r.py     # Communicate objects between processes with rpc (nim).
@@ -66,6 +65,7 @@
 percolator.py     # Manage delegator stack (nim).
 pyparse.py        # Give information on code indentation
 pyshell.py        # Start IDLE, manage shell, complete editor window
+query.py          # Query user for informtion
 redirector.py     # Intercept widget subcommands (for percolator) (nim).
 replace.py        # Search and replace pattern in text.
 rpc.py            # Commuicate between idle and user processes (nim).
@@ -192,8 +192,8 @@
   Configure IDLE   # eEW.config_dialog, configdialog
     (tabs in the dialog)
     Font tab       # config-main.def
-    Highlight tab  # config_sec, config-highlight.def
-    Keys tab       # config_key, configconfig_secg-keus.def
+    Highlight tab  # query, config-highlight.def
+    Keys tab       # query, config_key, config_keys.def
     General tab    # config_help, config-main.def
     Extensions tab # config-extensions.def, corresponding .py
   ---
diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py
--- a/Lib/idlelib/editor.py
+++ b/Lib/idlelib/editor.py
@@ -14,6 +14,7 @@
 import webbrowser
 
 from idlelib.multicall import MultiCallCreator
+from idlelib import query
 from idlelib import windows
 from idlelib import search
 from idlelib import grep
@@ -573,46 +574,27 @@
         text.see("insert")
 
     def open_module(self, event=None):
-        # XXX Shouldn't this be in IOBinding?
+        """Get module name from user and open it.
+
+        Return module path or None for calls by open_class_browser
+        when latter is not invoked in named editor window.
+        """
+        # XXX This, open_class_browser, and open_path_browser
+        # would fit better in iomenu.IOBinding.
         try:
-            name = self.text.get("sel.first", "sel.last")
+            name = self.text.get("sel.first", "sel.last").strip()
         except TclError:
-            name = ""
-        else:
-            name = name.strip()
-        name = tkSimpleDialog.askstring("Module",
-                 "Enter the name of a Python module\n"
-                 "to search on sys.path and open:",
-                 parent=self.text, initialvalue=name)
-        if name:
-            name = name.strip()
-        if not name:
-            return
-        # XXX Ought to insert current file's directory in front of path
-        try:
-            spec = importlib.util.find_spec(name)
-        except (ValueError, ImportError) as msg:
-            tkMessageBox.showerror("Import error", str(msg), parent=self.text)
-            return
-        if spec is None:
-            tkMessageBox.showerror("Import error", "module not found",
-                                   parent=self.text)
-            return
-        if not isinstance(spec.loader, importlib.abc.SourceLoader):
-            tkMessageBox.showerror("Import error", "not a source-based module",
-                                   parent=self.text)
-            return
-        try:
-            file_path = spec.loader.get_filename(name)
-        except AttributeError:
-            tkMessageBox.showerror("Import error",
-                                   "loader does not support get_filename",
-                                   parent=self.text)
-            return
-        if self.flist:
-            self.flist.open(file_path)
-        else:
-            self.io.loadfile(file_path)
+            name = ''
+        file_path = query.ModuleName(
+                self.text, "Open Module",
+                "Enter the name of a Python module\n"
+                "to search on sys.path and open:",
+                name).result
+        if file_path is not None:
+            if self.flist:
+                self.flist.open(file_path)
+            else:
+                self.io.loadfile(file_path)
         return file_path
 
     def open_class_browser(self, event=None):
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -235,8 +235,9 @@
 
 Query_spec = {
     'file': 'query',
-    'kwds': {'title':'Query',
-             'message':'Enter something',
+    'kwds': {'title': 'Query',
+             'message': 'Enter something',
+             'text0': 'Go',
              '_htest': True},
     'msg': "Enter with <Return> or [Ok].  Print valid entry to Shell\n"
            "Blank line, after stripping, is ignored\n"
diff --git a/Lib/idlelib/idle_test/test_query.py b/Lib/idlelib/idle_test/test_query.py
--- a/Lib/idlelib/idle_test/test_query.py
+++ b/Lib/idlelib/idle_test/test_query.py
@@ -8,8 +8,8 @@
 from unittest import mock
 from idlelib.idle_test.mock_tk import Var, Mbox_func
 from idlelib import query
-Query, SectionName = query.Query, query.SectionName
 
+Query = query.Query
 class Dummy_Query:
     # Mock for testing the following methods Query
     entry_ok = Query.entry_ok
@@ -23,7 +23,7 @@
         self.destroyed = True
 
 # entry_ok calls modal messagebox.showerror if entry is not ok.
-# Mock showerrer returns, so don't need to click to continue.
+# Mock showerrer so don't need to click to continue.
 orig_showerror = query.showerror
 showerror = Mbox_func()  # Instance has __call__ method.
 
@@ -46,7 +46,7 @@
         dialog = self.dialog
         Equal = self.assertEqual
         dialog.entry.set(' ')
-        Equal(dialog.entry_ok(), '')
+        Equal(dialog.entry_ok(), None)
         Equal((dialog.result, dialog.destroyed), (None, False))
         Equal(showerror.title, 'Entry Error')
         self.assertIn('Blank', showerror.message)
@@ -74,44 +74,41 @@
 
 
 class Dummy_SectionName:
-    # Mock for testing the following method of Section_Name
-    entry_ok = SectionName.entry_ok
-    # Attributes, constant or variable, needed for tests
+    entry_ok = query.SectionName.entry_ok  # Test override.
     used_names = ['used']
     entry = Var()
 
 class SectionNameTest(unittest.TestCase):
     dialog = Dummy_SectionName()
 
-
     def setUp(self):
         showerror.title = None
 
-    def test_blank_name(self):
+    def test_blank_section_name(self):
         dialog = self.dialog
         Equal = self.assertEqual
         dialog.entry.set(' ')
-        Equal(dialog.entry_ok(), '')
+        Equal(dialog.entry_ok(), None)
         Equal(showerror.title, 'Name Error')
         self.assertIn('No', showerror.message)
 
-    def test_used_name(self):
+    def test_used_section_name(self):
         dialog = self.dialog
         Equal = self.assertEqual
         dialog.entry.set('used')
-        Equal(self.dialog.entry_ok(), '')
+        Equal(self.dialog.entry_ok(), None)
         Equal(showerror.title, 'Name Error')
         self.assertIn('use', showerror.message)
 
-    def test_long_name(self):
+    def test_long_section_name(self):
         dialog = self.dialog
         Equal = self.assertEqual
         dialog.entry.set('good'*8)
-        Equal(self.dialog.entry_ok(), '')
+        Equal(self.dialog.entry_ok(), None)
         Equal(showerror.title, 'Name Error')
         self.assertIn('too long', showerror.message)
 
-    def test_good_entry(self):
+    def test_good_section_name(self):
         dialog = self.dialog
         Equal = self.assertEqual
         dialog.entry.set('  good ')
@@ -119,13 +116,56 @@
         Equal(showerror.title, None)
 
 
+class Dummy_ModuleName:
+    entry_ok = query.ModuleName.entry_ok  # Test override
+    text0 = ''
+    entry = Var()
+
+class ModuleNameTest(unittest.TestCase):
+    dialog = Dummy_ModuleName()
+
+    def setUp(self):
+        showerror.title = None
+
+    def test_blank_module_name(self):
+        dialog = self.dialog
+        Equal = self.assertEqual
+        dialog.entry.set(' ')
+        Equal(dialog.entry_ok(), None)
+        Equal(showerror.title, 'Name Error')
+        self.assertIn('No', showerror.message)
+
+    def test_bogus_module_name(self):
+        dialog = self.dialog
+        Equal = self.assertEqual
+        dialog.entry.set('__name_xyz123_should_not_exist__')
+        Equal(self.dialog.entry_ok(), None)
+        Equal(showerror.title, 'Import Error')
+        self.assertIn('not found', showerror.message)
+
+    def test_c_source_name(self):
+        dialog = self.dialog
+        Equal = self.assertEqual
+        dialog.entry.set('itertools')
+        Equal(self.dialog.entry_ok(), None)
+        Equal(showerror.title, 'Import Error')
+        self.assertIn('source-based', showerror.message)
+
+    def test_good_module_name(self):
+        dialog = self.dialog
+        Equal = self.assertEqual
+        dialog.entry.set('idlelib')
+        self.assertTrue(dialog.entry_ok().endswith('__init__.py'))
+        Equal(showerror.title, None)
+
+
 class QueryGuiTest(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
         requires('gui')
-        cls.root = Tk()
-        cls.dialog = Query(cls.root, 'TEST', 'test', _utest=True)
+        cls.root = root = Tk()
+        cls.dialog = Query(root, 'TEST', 'test', _utest=True)
         cls.dialog.destroy = mock.Mock()
 
     @classmethod
@@ -160,5 +200,43 @@
         self.assertTrue(dialog.destroy.called)
 
 
+class SectionnameGuiTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        requires('gui')
+
+    def test_click_section_name(self):
+        root = Tk()
+        dialog =  query.SectionName(root, 'T', 't', {'abc'}, _utest=True)
+        Equal = self.assertEqual
+        Equal(dialog.used_names, {'abc'})
+        dialog.entry.insert(0, 'okay')
+        dialog.button_ok.invoke()
+        Equal(dialog.result, 'okay')
+        del dialog
+        root.destroy()
+        del root
+
+
+class ModulenameGuiTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        requires('gui')
+
+    def test_click_module_name(self):
+        root = Tk()
+        dialog =  query.ModuleName(root, 'T', 't', 'idlelib', _utest=True)
+        Equal = self.assertEqual
+        Equal(dialog.text0, 'idlelib')
+        Equal(dialog.entry.get(), 'idlelib')
+        dialog.button_ok.invoke()
+        self.assertTrue(dialog.result.endswith('__init__.py'))
+        del dialog
+        root.destroy()
+        del root
+
+
 if __name__ == '__main__':
     unittest.main(verbosity=2, exit=False)
diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py
--- a/Lib/idlelib/query.py
+++ b/Lib/idlelib/query.py
@@ -15,7 +15,8 @@
 # of configSectionNameDialog.py (temporarily config_sec.py) into
 # generic and specific parts.
 
-from tkinter import FALSE, TRUE, Toplevel
+import importlib
+from tkinter import Toplevel, StringVar
 from tkinter.messagebox import showerror
 from tkinter.ttk import Frame, Button, Entry, Label
 
@@ -24,20 +25,22 @@
 
     For this base class, accept any non-blank string.
     """
-    def __init__(self, parent, title, message,
-                 *, _htest=False, _utest=False):  # Call from override.
+    def __init__(self, parent, title, message, text0='',
+                 *, _htest=False, _utest=False):
         """Create popup, do not return until tk widget destroyed.
 
-        Additional subclass init must be done before calling this.
+        Additional subclass init must be done before calling this
+        unless  _utest=True is passed to suppress wait_window().
 
         title - string, title of popup dialog
         message - string, informational message to display
+        text0 - initial value for entry
         _htest - bool, change box location when running htest
         _utest - bool, leave window hidden and not modal
         """
         Toplevel.__init__(self, parent)
         self.configure(borderwidth=5)
-        self.resizable(height=FALSE, width=FALSE)
+        self.resizable(height=False, width=False)
         self.title(title)
         self.transient(parent)
         self.grab_set()
@@ -45,6 +48,7 @@
         self.protocol("WM_DELETE_WINDOW", self.cancel)
         self.parent = parent
         self.message = message
+        self.text0 = text0
         self.create_widgets()
         self.update_idletasks()
         #needs to be done here so that the winfo_reqwidth is valid
@@ -62,31 +66,34 @@
             self.wait_window()
 
     def create_widgets(self):  # Call from override, if any.
+        # Bind widgets needed for entry_ok or unittest to self.
         frame = Frame(self, borderwidth=2, relief='sunken', )
         label = Label(frame, anchor='w', justify='left',
                     text=self.message)
-        self.entry = Entry(frame, width=30)  # Bind name for entry_ok.
+        self.entryvar = StringVar(self, self.text0)
+        self.entry = Entry(frame, width=30, textvariable=self.entryvar)
         self.entry.focus_set()
 
-        buttons = Frame(self)  # Bind buttons for invoke in unittest.
+        buttons = Frame(self)
         self.button_ok = Button(buttons, text='Ok',
                 width=8, command=self.ok)
         self.button_cancel = Button(buttons, text='Cancel',
                 width=8, command=self.cancel)
 
-        frame.pack(side='top', expand=TRUE, fill='both')
+        frame.pack(side='top', expand=True, fill='both')
         label.pack(padx=5, pady=5)
         self.entry.pack(padx=5, pady=5)
         buttons.pack(side='bottom')
         self.button_ok.pack(side='left', padx=5)
         self.button_cancel.pack(side='right', padx=5)
 
-    def entry_ok(self):  # Usually replace.
-        "Check that entry not blank."
+    def entry_ok(self):  # Example: usually replace.
+        "Return non-blank entry or None."
         entry = self.entry.get().strip()
         if not entry:
             showerror(title='Entry Error',
                     message='Blank line.', parent=self)
+            return
         return entry
 
     def ok(self, event=None):  # Do not replace.
@@ -95,7 +102,7 @@
         Otherwise leave dialog open for user to correct entry or cancel.
         '''
         entry = self.entry_ok()
-        if entry:
+        if entry is not None:
             self.result = entry
             self.destroy()
         else:
@@ -114,32 +121,72 @@
     def __init__(self, parent, title, message, used_names,
                  *, _htest=False, _utest=False):
         "used_names - collection of strings already in use"
-
         self.used_names = used_names
         Query.__init__(self, parent, title, message,
                  _htest=_htest, _utest=_utest)
-        # This call does ot return until tk widget is destroyed.
 
     def entry_ok(self):
-        '''Stripping entered name, check that it is a  sensible
-        ConfigParser file section name. Return it if it is, '' if not.
-        '''
+        "Return sensible ConfigParser section name or None."
         name = self.entry.get().strip()
         if not name:
             showerror(title='Name Error',
                     message='No name specified.', parent=self)
+            return
         elif len(name)>30:
             showerror(title='Name Error',
                     message='Name too long. It should be no more than '+
                     '30 characters.', parent=self)
-            name = ''
+            return
         elif name in self.used_names:
             showerror(title='Name Error',
                     message='This name is already in use.', parent=self)
-            name = ''
+            return
         return name
 
 
+class ModuleName(Query):
+    "Get a module name for Open Module menu entry."
+    # Used in open_module (editor.EditorWindow until move to iobinding).
+
+    def __init__(self, parent, title, message, text0='',
+                 *, _htest=False, _utest=False):
+        """text0 - name selected in text before Open Module invoked"
+        """
+        Query.__init__(self, parent, title, message, text0=text0,
+                 _htest=_htest, _utest=_utest)
+
+    def entry_ok(self):
+        "Return entered module name as file path or None."
+        # Moved here from Editor_Window.load_module 2016 July.
+        name = self.entry.get().strip()
+        if not name:
+            showerror(title='Name Error',
+                    message='No name specified.', parent=self)
+            return
+        # XXX Ought to insert current file's directory in front of path
+        try:
+            spec = importlib.util.find_spec(name)
+        except (ValueError, ImportError) as msg:
+            showerror("Import Error", str(msg), parent=self)
+            return
+        if spec is None:
+            showerror("Import Error", "module not found",
+                      parent=self)
+            return
+        if not isinstance(spec.loader, importlib.abc.SourceLoader):
+            showerror("Import Error", "not a source-based module",
+                      parent=self)
+            return
+        try:
+            file_path = spec.loader.get_filename(name)
+        except AttributeError:
+            showerror("Import Error",
+                      "loader does not support get_filename",
+                      parent=self)
+            return
+        return file_path
+
+
 if __name__ == '__main__':
     import unittest
     unittest.main('idlelib.idle_test.test_query', verbosity=2, exit=False)

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list