bpo-5680: IDLE: Customize running a module. by csabella · Pull Request #13763 · python/cpython (original) (raw)

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 andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Conversation53 Commits19 Checks0 Files changed

Conversation

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters

[ Show hidden characters]({{ revealButtonHref }})

csabella

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

@csabella

@blurb-it

@csabella

@csabella

csabella

csabella

csabella

cli_args = self.entry.get().strip()
if not cli_args:
self.showerror('no arguments specified.')
return None

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.

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.

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.

csabella

return lex
def restart_ok(self):
return self.restartvar.get()

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer possible.

csabella

_basename(_sys.argv[0]) != _basename(__file__)):
_sys.argv = [__file__]
_basename(_sys.argv[0]) != _basename(__file__) or
len(argv) > 1):

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.

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

terryjreedy

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.

@bedevere-bot

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@taleinat

  1. I find the name "Run Custom" unclear and confusing, and I image some users would too. Is "Run Module With Arguments" too long? Perhaps "Run With Arguments..."?
  2. It would be great to remember the last used arguments for each file, and pre-populate the field with them.

@csabella

@csabella

I have made the requested changes; please review again.

@terryjreedy, I added this pseudoevent to the former_extension_events set. I think that's what you wanted to fix -- to suppress the warning for the new binding and then to work on updating any custom keysets in another ticket. Maybe former_extension_events needs to be renamed?

@bedevere-bot

Thanks for making the requested changes!

@terryjreedy: please review the changes made to this pull request.

@csabella

Thanks for looking at this, @taleinat!

I find the name "Run Custom" unclear and confusing, and I image some users would too. Is "Run Module With Arguments" too long? Perhaps "Run With Arguments..."?

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'?

It would be great to remember the last used arguments for each file, and pre-populate the field with them.

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.

@taleinat

I find the name "Run Custom" unclear and confusing, and I image some users would too. Is "Run Module With Arguments" too long? Perhaps "Run With Arguments..."?

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'?

I see. Perhaps just "Run..."?

taleinat

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?

def test_blank_args(self):
dialog = self.Dummy_CustomRun(' ')
self.assertEqual(dialog.cli_args_ok(), None)
self.assertIn('no arguments', dialog.entry_error['text'])

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'])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert is gone.

class Dummy_CustomRun:
cli_args_ok = query.CustomRun.cli_args_ok # Function being tested.
entry = Var()

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.

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.

class Dummy_CustomRun:
cli_args_ok = query.CustomRun.cli_args_ok # Function being tested.
entry = Var()
entry_error = {}

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.

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.

for dialog.restart in {True, False}:
for cli_args, result in ((None, None),
('my arg', ('my arg', dialog.restart))):
with self.subTest():

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):

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.

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.

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))

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.

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()

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)

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.

dialog.entry.insert(0, 'okay')
dialog.button_ok.invoke()
self.assertEqual(dialog.result, (['okay'], True))
del dialog

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?

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.

self.showerror('no arguments specified.')
return None
try:
lex = shlex.split(cli_args, posix=True)

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?

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.

@terryjreedy

@terryjreedy

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.

@terryjreedy

Revise corresponding doc entry.

terryjreedy

cli_args = self.entry.get().strip()
if not cli_args:
self.showerror('no arguments specified.')
return None

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.

# User cancelled.
if not run_args:
return 'break'
return self._run_module_event(event, cli_args=run_args[0], restart=run_args[1])

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.

self.showerror('no arguments specified.')
return None
try:
lex = shlex.split(cli_args, posix=True)

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.

return lex
def restart_ok(self):
return self.restartvar.get()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer possible.

for dialog.restart in {True, False}:
for cli_args, result in ((None, None),
('my arg', ('my arg', dialog.restart))):
with self.subTest():

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))

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.entry.insert(0, 'okay')
dialog.button_ok.invoke()
self.assertEqual(dialog.result, (['okay'], True))
del dialog

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()

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.

@terryjreedy

@terryjreedy

buglet to fix: focus is not being shifted to shell correctly.

@terryjreedy

@terryjreedy

Don't ask for config if still may toss. Want focus to go to shell unless cancel.

@terryjreedy

@terryjreedy

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.

@terryjreedy terryjreedy changed the titlebpo-5680: IDLE: Run modules with command line arguments bpo-5680: IDLE: Customize running a module.

Jun 18, 2019

@miss-islington

Thanks @csabella for the PR, and @terryjreedy for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7.
🐍🍒⛏🤖

@miss-islington

Thanks @csabella for the PR, and @terryjreedy for merging it 🌮🎉.. I'm working now to backport this PR to: 3.8.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request

Jun 18, 2019

@csabella @miss-islington

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

@bedevere-bot

@bedevere-bot

miss-islington added a commit that referenced this pull request

Jun 18, 2019

@miss-islington @csabella

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

miss-islington added a commit that referenced this pull request

Jun 18, 2019

@miss-islington @csabella

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

@csabella

@taleinat, thank you for the review and @terryjreedy, thank you for the review and making the additional changes.

lisroach pushed a commit to lisroach/cpython that referenced this pull request

Sep 10, 2019

@csabella @lisroach

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.

DinoV pushed a commit to DinoV/cpython that referenced this pull request

Jan 14, 2020

@csabella @DinoV

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.