-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
bpo-5680: IDLE: Customize running a module. #13763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Lib/idlelib/query.py
Outdated
cli_args = self.entry.get().strip() | ||
if not cli_args: | ||
self.showerror('no arguments specified.') | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about this edit, but it makes everything a little nicer right now. The reason I'm not sure about it is due to the behavior of the current Run Module
. Should Run Module
use the last settings from Custom Run
or should it work exactly as it works now (while in a subprocess)? Run Module
without a subprocess uses the Custom Run
settings from the last run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO "Run Module" should always ignore the latest "Custom Run" settings, regardless of whether or not a subprocess is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe Run Module is currently unchanged and agree that this should continue.
Lib/idlelib/query.py
Outdated
return lex | ||
|
||
def restart_ok(self): | ||
return self.restartvar.get() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could add edits here for when restart
cannot be False.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer possible.
_basename(_sys.argv[0]) != _basename(__file__)): | ||
_sys.argv = [__file__] | ||
_basename(_sys.argv[0]) != _basename(__file__) or | ||
len(argv) > 1): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking the length so that, if there are additional args, they will replace what was already there. However, existing args won't be replaced by no args. Currently, F5 still runs with no args and restarts the shell, but that's something to keep in mind if F5 changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that this can only matter if one used the deprecated -n option. I have no intention of changing F5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test of behavior without looking much at code yet. Will respond to other comments later. Immediate issue: I have a custom keyset without new pseudoevent binding and on startup see
Warning: config.py - IdleConf.GetCoreKeys -
problem retrieving key binding for event '<>'
from key set 'test'.
returning default value: ['']
The fix is simple and I know how to do it if you don't beat me.
Separate issue if not done yet: write back keyset with new bindings.
Otherwise, works great.
When you're done making the requested changes, leave the comment: |
|
I have made the requested changes; please review again. @terryjreedy, I added this pseudoevent to the |
Thanks for making the requested changes! @terryjreedy: please review the changes made to this pull request. |
Thanks for looking at this, @taleinat!
Terry wants to have more things on that dialog to be able to change the run environment, so using 'arguments' on the menu isn't inclusive enough. If 'Run Custom' is awkward, maybe 'Customize Run'?
Yes, there was a few things that I listed on the bpo issue that I didn't do in this first round, including saving the values. Terry requested creating a minimal version to start with. Saving the items wouldn't (shouldn't?) complicate the code, but I wanted to try to keep it simple and then iterate over it in the next round. He had also mentioned printing the run options in the shell window so that multiple runs would be annotated. Both features would be helpful. |
I see. Perhaps just "Run..."? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general note, I'm not sure about the usefulness of the non-GUI tests for the RunCustom dialog (CustomRunCLIargsokTest
, CustomRunEntryokTest
) . They rely rather heavily on the internal implementation. Since we have GUI tests run by the CI now, I prefer to have simpler tests actually using Tk with less mocking. Would it be possible to simply move the tests from these classes into CustomRunGuiTest
?
Lib/idlelib/idle_test/test_query.py
Outdated
def test_blank_args(self): | ||
dialog = self.Dummy_CustomRun(' ') | ||
self.assertEqual(dialog.cli_args_ok(), None) | ||
self.assertIn('no arguments', dialog.entry_error['text']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use assertRegex
to do a case-insensitive check.
self.assertIn('no arguments', dialog.entry_error['text']) | |
self.assertRegex(r'(?i)no arguments', dialog.entry_error['text']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assert is gone.
Lib/idlelib/idle_test/test_query.py
Outdated
|
||
class Dummy_CustomRun: | ||
cli_args_ok = query.CustomRun.cli_args_ok # Function being tested. | ||
entry = Var() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a class attribute? Besides being unclear, this also doesn't reflect the actual CustomRun
class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote the query tests, and Cheryl followed the pattern I set. For testing with one instance at a time, or ever, class and instance attributes work the same. For attributes never rebound, I attached them to the class, especially if there was no other need for an init method. Very clear to me. However, I moved Var attributes to init when there is one as it saves a line.
The cli_args_ok method being tested accesses self.query. tk Vars and Entries both have a get method that does the same thing, so a mock Var replaces an Entry quite nicely.
Lib/idlelib/idle_test/test_query.py
Outdated
class Dummy_CustomRun: | ||
cli_args_ok = query.CustomRun.cli_args_ok # Function being tested. | ||
entry = Var() | ||
entry_error = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be simpler to make Dummy_CustomRun
a Mock
instance and then check calls to showerror()
, rather than the entry_error['text']
stuff which is more implementation-specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used this in all the other functional method tests. The dummy classes have and do exactly what is needed to test the method. A unittest.mock would be much more complicated and difficult and would run much slower. Test speed is important.
Lib/idlelib/idle_test/test_query.py
Outdated
for dialog.restart in {True, False}: | ||
for cli_args, result in ((None, None), | ||
('my arg', ('my arg', dialog.restart))): | ||
with self.subTest(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with self.subTest(): | |
with self.subTest(restart=dialog.restart, cli_args=cli_args): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -0,0 +1 @@ | |||
Run modules from the editor using command line arguments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be a bit more explicit, mentioning that a new menu option is added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expanded.
dialog = query.CustomRun(root, 'Title', _utest=True) | ||
dialog.entry.insert(0, 'okay') | ||
dialog.button_ok.invoke() | ||
self.assertEqual(dialog.result, (['okay'], True)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The should probably be a call to root.update()
after the .invoke()
call, to ensure that Tk has a chance to do all of its processing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe invoke blocks until done. I have run this test at least 20 times without issue. event_generate is different (and flakey). There are 55 invokes in idle tests and my spot check of nearly 10 showed none followed by update. And I know of no resulting failures.
dialog.button_ok.invoke() | ||
self.assertEqual(dialog.result, (['okay'], True)) | ||
del dialog | ||
root.destroy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest cleaning up using self.addCleanup()
immediately after creating the Tk()
instance, e.g.:
root = Tk()
def cleanup():
root.update()
root.destroy()
self.addCleanup(cleanup)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I far prefer explicit calls. Update is not generally needed. update_idletasks is more often needed. I have not seen any of the tclerror messages that so indicate.
Lib/idlelib/idle_test/test_query.py
Outdated
dialog.entry.insert(0, 'okay') | ||
dialog.button_ok.invoke() | ||
self.assertEqual(dialog.result, (['okay'], True)) | ||
del dialog |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are this del
call, and the one below, actually needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. Gone. Added class attributes need deletion, but function locals already go on return, and there is no evident issue here with destroying first.
Lib/idlelib/query.py
Outdated
self.showerror('no arguments specified.') | ||
return None | ||
try: | ||
lex = shlex.split(cli_args, posix=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does lex
stand for? For me, this is confusing both here and in entry_ok()
. Perhaps args_list
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I switched to cli_string and cli_args. cli_args_ok should return cli_args.
I am reviewing and editing with the intention of getting this into 3.7.4. While the user UI can be changed after that, it is my primary focus. Summary responses to comments above.
|
Revise corresponding doc entry.
Lib/idlelib/query.py
Outdated
cli_args = self.entry.get().strip() | ||
if not cli_args: | ||
self.showerror('no arguments specified.') | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe Run Module is currently unchanged and agree that this should continue.
Lib/idlelib/runscript.py
Outdated
# User cancelled. | ||
if not run_args: | ||
return 'break' | ||
return self._run_module_event(event, cli_args=run_args[0], restart=run_args[1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to pass run_args and unpack in _run_module.
Lib/idlelib/query.py
Outdated
self.showerror('no arguments specified.') | ||
return None | ||
try: | ||
lex = shlex.split(cli_args, posix=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I switched to cli_string and cli_args. cli_args_ok should return cli_args.
Lib/idlelib/query.py
Outdated
return lex | ||
|
||
def restart_ok(self): | ||
return self.restartvar.get() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer possible.
Lib/idlelib/idle_test/test_query.py
Outdated
for dialog.restart in {True, False}: | ||
for cli_args, result in ((None, None), | ||
('my arg', ('my arg', dialog.restart))): | ||
with self.subTest(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
dialog = query.CustomRun(root, 'Title', _utest=True) | ||
dialog.entry.insert(0, 'okay') | ||
dialog.button_ok.invoke() | ||
self.assertEqual(dialog.result, (['okay'], True)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe invoke blocks until done. I have run this test at least 20 times without issue. event_generate is different (and flakey). There are 55 invokes in idle tests and my spot check of nearly 10 showed none followed by update. And I know of no resulting failures.
Lib/idlelib/idle_test/test_query.py
Outdated
dialog.entry.insert(0, 'okay') | ||
dialog.button_ok.invoke() | ||
self.assertEqual(dialog.result, (['okay'], True)) | ||
del dialog |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. Gone. Added class attributes need deletion, but function locals already go on return, and there is no evident issue here with destroying first.
dialog.button_ok.invoke() | ||
self.assertEqual(dialog.result, (['okay'], True)) | ||
del dialog | ||
root.destroy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I far prefer explicit calls. Update is not generally needed. update_idletasks is more often needed. I have not seen any of the tclerror messages that so indicate.
buglet to fix: focus is not being shifted to shell correctly. |
Don't ask for config if still may toss. Want focus to go to shell unless cancel.
Changes: 1) Check code before bother to customize. 2) Add module title to title bar. 3) Make shell.text the parent, so focus goes to shell. Good enough to merge. See issue for 2 remaining issues. |
Thanks @csabella for the PR, and @terryjreedy for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7. |
Thanks @csabella for the PR, and @terryjreedy for merging it 🌮🎉.. I'm working now to backport this PR to: 3.8. |
The initialize options are 1) add command line options, which are appended to sys.argv as if passed on a real command line, and 2) skip the shell restart. The customization dialog is accessed by a new entry on the Run menu. (cherry picked from commit 201bc2d) Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
GH-14184 is a backport of this pull request to the 3.7 branch. |
GH-14185 is a backport of this pull request to the 3.8 branch. |
The initialize options are 1) add command line options, which are appended to sys.argv as if passed on a real command line, and 2) skip the shell restart. The customization dialog is accessed by a new entry on the Run menu. (cherry picked from commit 201bc2d) Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
The initialize options are 1) add command line options, which are appended to sys.argv as if passed on a real command line, and 2) skip the shell restart. The customization dialog is accessed by a new entry on the Run menu. (cherry picked from commit 201bc2d) Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
@taleinat, thank you for the review and @terryjreedy, thank you for the review and making the additional changes. |
The initialize options are 1) add command line options, which are appended to sys.argv as if passed on a real command line, and 2) skip the shell restart. The customization dialog is accessed by a new entry on the Run menu.
The initialize options are 1) add command line options, which are appended to sys.argv as if passed on a real command line, and 2) skip the shell restart. The customization dialog is accessed by a new entry on the Run menu.
The initialize options are 1) add command line options, which are appended to sys.argv as if passed on a real command line, and 2) skip the shell restart. The customization dialog is accessed by a new item on the Run menu.
https://bugs.python.org/issue5680