Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upCannot use per-function docstring in help messages when argparse description is not set #522
Comments
|
@xNinjaKittyx Thank you for reporting this issue. It is certainly undesirable for one command to inadvertently pick up the docstring help from another command. @anselor @kmvanbrunt Would either of you want to take a look at this? It sounds up your alley. |
|
Since this Issue and #515 are somewhat related, it might be a good idea to work on a solution for them at the same time. |
|
This isn't really a cmd2 or argparse issue. The same parser object is being passed into both function wrappers. Even if you did change the description in between the function declarations, both functions would still print the new description because the same object is being altered. A deep copy of the parser needs to be made for each function you wish to use it with before the description gets set. I tried this in Python 3.5 on Debian 9 and didn't have much luck. I received lots of exceptions doing this with an ACArgumentParser object: |
|
I found a decent workaround if deepcopy doesn't work. Write a function to create and return the type of parser you want to reuse. Then call it for each function being wrapped. def make_parser():
new_parser = argparse.ArgumentParser()
new_parser.add_argument('-s', '--something', type=str, help='some argument')
return new_parser
class SomeClass(Cmd):
@with_argparser(make_parser())
def do_foo(self, opts):
"""This is foo docstring"""
pass
@with_argparser(make_parser())
def do_bar(self, opts):
"""This is bar docstring"""
pass |
|
Sure, that seems like a reasonable workaround. The way I originally was thinking of was something like this: def with_argparser(argparser: argparse.ArgumentParser) -> Callable:
"""A decorator to alter a cmd2 method to populate its ``args`` argument by parsing arguments
with the given instance of argparse.ArgumentParser.
:param argparser: argparse.ArgumentParser - given instance of ArgumentParser
:return: function that gets passed parsed args
"""
import functools
# noinspection PyProtectedMember
def arg_decorator(func: Callable):
if argparser.description is None:
argparser.init_with_description = False
@functools.wraps(func)
def cmd_wrapper(instance, cmdline):
lexed_arglist = parse_quoted_string(cmdline)
try:
# argparser defaults the program name to sys.argv[0]
# we want it to be the name of our command
argparser.prog = func.__name__[3:]
# If the description has not been set, then use the method docstring if one exists
if not argparser.init_with_description and func.__doc__:
argparser.description = func.__doc__
args = argparser.parse_args(lexed_arglist)
except SystemExit:
return
else:
return func(instance, args)
if func.__doc__:
setattr(cmd_wrapper, HELP_SUMMARY, func.__doc__)
cmd_wrapper.__doc__ = argparser.format_help()
# Mark this function as having an argparse ArgumentParser
setattr(cmd_wrapper, 'argparser', argparser)
return cmd_wrapper
return arg_decoratorWhere the function name and doc string were dynamically changed based on the command. Feel free to close based on your decision. |
|
I'm inclined to handle it inside with_argparser rather than creating a separate argparser per command. On phone right now so difficult to look at this specific implementation. I was pretty sure that this could be done without making multiple argparser objects. |
|
The solution proposed by @xNinjaKittyx does work. However, I'm not sure it sets a good precedent. Argparse has many parameters that a user could want overridden between commands (ex: |
|
@kmvanbrunt For whatever its worth, the inability to deepcopy an I would prefer for |
|
Then general consensus among the For now we will document that a separate instance of an argparser needs to be used for each command. We should make sure to also demonstrate best practices. Perhaps we could keep track of id's of all argparsers used so far and disallow one to be used again, but maybe that would be overkill? In the future if |
I have been noticing help messages of only a single docstring will apply to its argparser. So any commands that use a common parser will always have the same docstring, which is fairly inconvenient if someone wants to use the same parser for multiple commands.
Now using the Cmd
Looking at the code, it could be fixed by setting the argparser's description inside the function wrapper, rather than outside of it. I don't know how this will affect the rest of the code though.Nevermind, it's not that straightforward. There needs to be a way to know if argparse.description was not declared on init vs declaring it dynamically.
If people want all of their functions to have the same help message, they can always set the parser's description. However, if people want to have unique messages for each one, it would be nice to be setting that docstring based on what function is being used.