import argparse
parser = argparse.ArgumentParser(prog="demo", description="Run something")
parser.add_argument("--arg", type=int, help="some docs", default=1)
def main(arg: int = 1) -> None:
"""
Run something.
Args:
arg: Some docs
"""
if __name__ == "__main__":
args: argparse.Namespace = parser.parse_args()
main(**vars(args))
We see there is lots of duplication between the main signature/docstring and the parser. We have to re-type the type, default, help, double dash arg name, and description.
Idea
It would be cool if we could use argparse.ArgumentParser like a decorator on a function, to automatically infer all of this stuff. It would be a big improvement for DRY code, and quick minimum-viable-product scripts using argparse as an entrypoint.
That’s, you propose to make a decorator that, based on the function signature, will create an argument parser and pass them to the function. The idea is good, if I understand correctly, it will work?
I thought I had seen something like this before. It is not exactly click, fire, or typer, but maybe a mix of those, although typer might be the closest.
If I’ve understood correctly, I’ve made something similar myself: epmanager. The decorator also adds a callable .invoke attribute to the function which wraps up the actual argparse logic. You can still use the function normally, but if you call the func.invoke and pass a list of strings, it will be equivalent to using .parse_args on that list, dispatching the resulting args to the appropriate places, and calling the function.
Finally, there’s a command-line tool that scans for uses of the decorator and updates pyproject.toml so that those .invoke callables become entry points to the program.
The short version is, you can write (assuming I still remember how it works! It’s been almost 3 years…):
from epmanager import entrypoint
@entrypoint(name="demo", description="Run something", arg="some docs")
def main(arg: int = 1) -> None:
pass
and you don’t even need a if __name__ == '__main__': block, because the packaging system will create a wrapper script to call main.invoke for you.
Drat… that looks very much like what I wanted when I started writing epmanager, and has been around for much longer. I just… had no idea what to search for x.x
In and of itself, neither. There are a few other considerations that can affect such a decision though:
Is it very easy to implement just-plain-wrong, and hard to get right? (Particularly with edge cases.) Increased likelihood.
Are all the variations slightly different in what they can do, such that having all the functionality in one library would make a monster? Decreased likelihood.
Is it something that would be of value to the stdlib itself? For example, the current regular expression module is used by quite a few other stdlib modules; those modules can’t depend on a third-party module, so if they want an uprated regex module, it’d need to be added to the stdlib itself.
Is it a trivially simple part of a larger library? Decreased likelihood - not every two-liner needs to be in the stdlib.
And a few others that I haven’t thought of, too.
As the maintainer of Click, I would strongly recommend not trying to implement a CLI framework in the standard library. optparse is a good base, argparse already adds complexity/ambiguity. I don’t think either are accepting new features, and adding a third one wouldn’t help the situation.