Skip to content

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

Merged
merged 19 commits into from
Jun 18, 2019
Merged

Conversation

csabella
Copy link
Contributor

@csabella csabella commented Jun 3, 2019

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

@brettcannon brettcannon added the type-feature A feature request or enhancement label Jun 3, 2019
cli_args = self.entry.get().strip()
if not cli_args:
self.showerror('no arguments specified.')
return None
Copy link
Contributor Author

@csabella csabella Jun 3, 2019

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.

Copy link
Contributor

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.

Copy link
Member

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.

return lex

def restart_ok(self):
return self.restartvar.get()
Copy link
Contributor Author

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.

Copy link
Member

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):
Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Member

@terryjreedy terryjreedy left a 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.

@bedevere-bot
Copy link

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

@taleinat
Copy link
Contributor

taleinat commented Jun 6, 2019

  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
Copy link
Contributor Author

csabella commented Jun 6, 2019

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
Copy link

Thanks for making the requested changes!

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

@csabella
Copy link
Contributor Author

csabella commented Jun 6, 2019

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
Copy link
Contributor

taleinat commented Jun 7, 2019

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..."?

Copy link
Contributor

@taleinat taleinat left a 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?

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

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.

Suggested change
self.assertIn('no arguments', dialog.entry_error['text'])
self.assertRegex(r'(?i)no arguments', dialog.entry_error['text'])

Copy link
Member

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()
Copy link
Contributor

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.

Copy link
Member

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 = {}
Copy link
Contributor

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.

Copy link
Member

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():
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
with self.subTest():
with self.subTest(restart=dialog.restart, cli_args=cli_args):

Copy link
Member

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.
Copy link
Contributor

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.

Copy link
Member

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))
Copy link
Contributor

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.

Copy link
Member

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()
Copy link
Contributor

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)

Copy link
Member

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
Copy link
Contributor

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?

Copy link
Member

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)
Copy link
Contributor

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?

Copy link
Member

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
Copy link
Member

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.

  • Config warning: fixed.

  • Menu entry: We could flip 'Run custom', but I prefer to keep to a verb form. ('Python Shell' might better be 'Open Shell'.) I think 'Run customized' or 'Run ... customized' as an abbreviation for 'Run module with customized settings' is better, and should be good enough for now. I will see how latter looks.

  • Restart (or not) button: now functional. Add to blurb.

  • Arguments, without restart: These extend sys.argv. 'Command line' arguments do not make sense without restart, but extending sys.argv does. So this is OK.

  • No arguments, with restart: I changed my mind and will remove the current error check. No args will be ok with other settings, and if someone opens box to see available customizations, I now think they should be able to run without changing anything.
    (Note: check button will be replaced with radio buttons when another mutually exclusive option is added, such as 'O Run module in system console' (no issue yet).)

  • Code comments, especially tests: I will make the changes I currently agree with.

  • Blurb: will expand.

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

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])
Copy link
Member

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)
Copy link
Member

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()
Copy link
Member

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():
Copy link
Member

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))
Copy link
Member

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
Copy link
Member

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()
Copy link
Member

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
Copy link
Member

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.
@terryjreedy
Copy link
Member

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 title bpo-5680: IDLE: Run modules with command line arguments bpo-5680: IDLE: Customize running a module. Jun 18, 2019
@terryjreedy terryjreedy merged commit 201bc2d into python:master Jun 18, 2019
@miss-islington
Copy link
Contributor

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

@miss-islington
Copy link
Contributor

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
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
Copy link

GH-14184 is a backport of this pull request to the 3.7 branch.

@bedevere-bot
Copy link

GH-14185 is a backport of this pull request to the 3.8 branch.

miss-islington added a commit that referenced this pull request Jun 18, 2019
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
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 csabella deleted the bpo5680 branch June 20, 2019 21:59
@csabella
Copy link
Contributor Author

@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
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
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-feature A feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants