New submission from Karthikeyan Singaravelan <tir.kar...@gmail.com>:

I came across this idea while working on error messages for click at 
https://github.com/pallets/click/issues/1446. Currently for unknown arguments 
which could in some case be typos argparse throws an error but doesn't make any 
suggestions. It could do some heuristic to suggest matches. The unrecognized 
argument error prints all unrecognized arguments so in that case it will be 
less useful to mix match suggestions. It can be helpful for single argument 
usages. argparse is performance sensitive since it's used in cli environments 
so I feel the tradeoff to do simple match to make suggestions as a good user 
experience.

# ssl_helper.py

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--include-ssl', action='store_true')
namespace = parser.parse_args()

No suggestions are included currently

$ python3.8 ssl_helper.py --include-ssll
usage: ssl_helper.py [-h] [--include-ssl]
ssl_helper.py: error: unrecognized arguments: --include-ssll

Include suggestions based when one of the option starts with the argument 
supplied similar to click

$ ./python.exe ssl_helper.py --include-ssll
usage: ssl_helper.py [-h] [--include-ssl]
ssl_helper.py: error: unrecognized argument: --include-ssll . Did you mean 
--include-ssl?

difflib.get_close_matches could also provide better suggestions in some cases 
but comes at import cost and could be imported only during error messages as 
proposed in the click issue

./python.exe ssl_helper.py --exclude-ssl
usage: ssl_helper.py [-h] [--include-ssl]
ssl_helper.py: error: unrecognized argument: --exclude-ssl . Did you mean 
--include-ssl?


Attached is a simple patch of the implementation with startswith which is more 
simple and difflib.get_close_matches

diff --git Lib/argparse.py Lib/argparse.py
index 5d3ce2ad70..e10a4f0c9b 100644
--- Lib/argparse.py
+++ Lib/argparse.py
@@ -1818,8 +1818,29 @@ class ArgumentParser(_AttributeHolder, 
_ActionsContainer):
     def parse_args(self, args=None, namespace=None):
         args, argv = self.parse_known_args(args, namespace)
         if argv:
-            msg = _('unrecognized arguments: %s')
-            self.error(msg % ' '.join(argv))
+            suggestion = None
+            if len(argv) == 1:
+                argument = argv[0]
+
+                # simple startswith
+                for option in self._option_string_actions:
+                    if argument.startswith(option):
+                        suggestion = option
+                        break
+
+                # difflib impl
+                import difflib
+                try:
+                    suggestion = difflib.get_close_matches(argv[0], 
self._option_string_actions, n=1)[0]
+                except IndexError:
+                    pass
+
+            if suggestion:
+                msg = _('unrecognized argument: %s . Did you mean %s?')
+                self.error(msg % (' '.join(argv), suggestion))
+            else:
+                msg = _('unrecognized arguments: %s')
+                self.error(msg % ' '.join(argv))
         return args
 
     def parse_known_args(self, args=None, namespace=None):

----------
components: Library (Lib)
messages: 358699
nosy: paul.j3, rhettinger, xtreak
priority: normal
severity: normal
status: open
title: Add suggestions to argparse error message output for unrecognized 
arguments
type: enhancement
versions: Python 3.9

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue39106>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to