Skip to content

gh-99749: Add closest choice if exists in Argparser if wrong choice picked #99773

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

Closed
wants to merge 24 commits into from

Conversation

abdulrafey38
Copy link

@abdulrafey38 abdulrafey38 commented Nov 25, 2022

Description

Add closet choice if exists in Argparser if wrong choice picked

@bedevere-bot
Copy link

Most changes to Python require a NEWS entry.

Please add it using the blurb_it web app or the blurb command-line tool.

@ghost
Copy link

ghost commented Nov 25, 2022

All commit authors signed the Contributor License Agreement.
CLA signed

@AlexWaygood AlexWaygood changed the title gh-99749: Add closet choice if exists in Argparser if wrong choice picked gh-99749: Add closest choice if exists in Argparser if wrong choice picked Nov 25, 2022
@abdulrafey38 abdulrafey38 marked this pull request as draft November 25, 2022 18:12
Copy link
Contributor

@noamcohen97 noamcohen97 left a comment

Choose a reason for hiding this comment

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

Great feature!
Maybe a way to opt out of this feature should be added?

Lib/argparse.py Outdated
Comment on lines 2551 to 2552
if closest_choice := closest_choice and closest_choice[0] or None:
args['closest'] = closest_choice
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
if closest_choice := closest_choice and closest_choice[0] or None:
args['closest'] = closest_choice
if closest_choice:
args['closest'] = closest_choice[0]

Copy link
Author

Choose a reason for hiding this comment

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

why not the above one liner?

Copy link
Contributor

Choose a reason for hiding this comment

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

the one liner is less readable - at least to me

Lib/argparse.py Outdated
args = {'value': value,
'choices': ', '.join(map(repr, action.choices))}
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
closest_choice = _difflib.get_close_matches(value, action.choices)
Copy link
Contributor

Choose a reason for hiding this comment

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

this might fail if action.choices are not strings

Copy link
Author

@abdulrafey38 abdulrafey38 Nov 26, 2022

Choose a reason for hiding this comment

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

argparser does not accept choices other than string, it will throw error even before reaching at this line
reference ==> https://stackoverflow.com/questions/49578928/typeerror-int-object-is-not-subscriptable-when-i-try-to-pass-three-arguments

Copy link
Contributor

Choose a reason for hiding this comment

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

The second example for the documentation (https://docs.python.org/3/library/argparse.html#choices)

parser = argparse.ArgumentParser(prog='doors.py')
parser.add_argument('door', type=int, choices=range(1, 4))
print(parser.parse_args(['3']))

Also - I would use

Suggested change
closest_choice = _difflib.get_close_matches(value, action.choices)
closest_choice = _difflib.get_close_matches(value, action.choices, 1)

Copy link

@NIKDISSV-Forever NIKDISSV-Forever left a comment

Choose a reason for hiding this comment

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

Please, instead of just this

closet_choice = _difflib.get_close_matches(value, action.choices)

Add

if isinstance(value, Iterable) and all(isinstance(option, Sized) for option in action.choices)):
    closet_choice = _difflib.get_close_matches(value, action.choices, 1)  # also n=1 (default 3)
else:
    closet_choice = []

or this (better in my opinion)

try:
    closet_choice = _difflib.get_close_matches(value, action.choices, 1)
except TypeError:
    closet_choice = []

@abdulrafey38 abdulrafey38 marked this pull request as ready for review November 26, 2022 13:25
@abdulrafey38 abdulrafey38 requested review from sobolevn and removed request for NIKDISSV-Forever November 26, 2022 13:25
@abdulrafey38
Copy link
Author

abdulrafey38 commented Nov 28, 2022

Closing this pull request & opening a new one as this PR contains unwanted commits...

@sobolevn
Copy link
Member

Please, don't create new PRs.
This PR contains history and reviews. New one is empty. It is better to force push commits in case you have some conflicts than creating a new PR.

@abdulrafey38
Copy link
Author

Ok @sobolevn i will open this PR again... and close the new one

@abdulrafey38 abdulrafey38 reopened this Nov 28, 2022
@abdulrafey38
Copy link
Author

@sobolevn reopened this PR again, please have a review

@sobolevn
Copy link
Member

Quoting myself:

Isn't invalid choice: %(value)r (choose from %(choices)s) enough?

@abdulrafey38
Copy link
Author

Quoting myself:

Isn't invalid choice: %(value)r (choose from %(choices)s) enough?

@sobolevn no this is not enough now, as we change argparser logic here ==> when we pass a choice which is not in choices array it now throws an exception with a different error message (this error message now includes closest suggestions too if available) & in the test case we are passing ['foo','bar'] in choices array and adding 'baz' in argparser. so instead of showing previous message which is (Isn't invalid choice: %(value)r (choose from %(choices)s)) it now throws msg including closest_choice which is (Isn't invalid choice: %(value)r, may be you meant %(closest_choice)r (choose from %(choices)s).

@abdulrafey38
Copy link
Author

@sobolevn do we have any update here?

@rhettinger rhettinger self-assigned this Dec 3, 2022
@rhettinger
Copy link
Contributor

I'm still evaluating whether this should be done or not. Here are a few thoughts:

  • We've tried hard to not add dependencies to argparse because startup time is important in command line applications. The import of difflib loads a slew of other modules.

  • The current choices error message is self-explanatory and we have no evidence that it is insufficient. AFAICT, no end-user has ever asked for this.

  • If this were added, it would need to be opt-in. There are thousands of deployed command line tools and the publishers of those tools may not want this new behavior.

@encukou
Copy link
Member

encukou commented Mar 19, 2024

How's the evaluation going?
Some counterpoints:

  • difflib could be imported on error only
  • We don't really have a way for end-users to ask; “did you mean” notes for things like AttributeError were generally received positively.
  • IMO, opt-out would be fine; I don't think the wording of this message is API. But that's a personal opinion.

@abdulrafey38
Copy link
Author

@encukou i can work on it further like importing on errors only, what u say?

@encukou
Copy link
Member

encukou commented Mar 20, 2024

That work might not be merged, so it's better to wait for the decision on whether this is a good idea.

Copy link
Member

@savannahostrowski savannahostrowski left a comment

Choose a reason for hiding this comment

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

Hey there - thanks for the PR! I'm in the process of triaging existing PRs for argparse and noticed this was still open.

A couple of comments but, this still works as intended.

(If you haven't got the bandwidth, let me know and I'd also be happy to carry the PR forward (with credit to you, of course!))

@@ -84,7 +84,7 @@
'ZERO_OR_MORE',
]


import difflib as _difflib
Copy link
Member

Choose a reason for hiding this comment

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

I think we'd probably want to move the import down into the case where the error is thrown, since this is probably not going to be used very often.

@@ -2193,9 +2193,9 @@ def test_wrong_argument_subparsers_no_destination_error(self):
subparsers.add_parser('bar')
with self.assertRaises(ArgumentParserError) as excinfo:
parser.parse_args(('baz',))
self.assertRegex(
self.assertIn(
Copy link
Member

Choose a reason for hiding this comment

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

We probably also want some additional test cases here.

@@ -2193,9 +2193,9 @@ def test_wrong_argument_subparsers_no_destination_error(self):
subparsers.add_parser('bar')
with self.assertRaises(ArgumentParserError) as excinfo:
parser.parse_args(('baz',))
self.assertRegex(
self.assertIn(
"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from 'foo', 'bar')",
Copy link
Member

Choose a reason for hiding this comment

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

I'd be interested in others' opinions around the verbiage here. It seems like we are using both "Maybe you meant" and "Did you mean" verbiage in the codebase. Not sure if we have any principle around this.

@abdulrafey38
Copy link
Author

Hi @savannahostrowski ,

Yes you can pick it and if you need any help do let me know.

Thanks

@savannahostrowski
Copy link
Member

Carrying this forward in #124456 with @abdulrafey38's blessing. Thank you!

savannahostrowski added a commit to savannahostrowski/cpython that referenced this pull request Sep 24, 2024
savannahostrowski added a commit to savannahostrowski/cpython that referenced this pull request Sep 24, 2024
savannahostrowski added a commit to savannahostrowski/cpython that referenced this pull request Oct 12, 2024
savannahostrowski added a commit to savannahostrowski/cpython that referenced this pull request Oct 12, 2024
serhiy-storchaka added a commit to savannahostrowski/cpython that referenced this pull request Oct 14, 2024
savannahostrowski added a commit to savannahostrowski/cpython that referenced this pull request Oct 17, 2024
savannahostrowski added a commit to savannahostrowski/cpython that referenced this pull request Oct 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants