Just Use Argparse
Writing Python scripts is probably one of the most useful skills as a software engineer. As a project grows, we find ourselves dumping loads of scripts everywhere, making it hard to resist the urge to manage them somehow. One way to manage scripts better is by turning them into CLI applications. By letting them take arguments rather than hard-coding the configuration values in the scripts such as filepaths, we can re-use them anytime we need some jobs done.
For the sake of this, Python provides you a built-in which is
argparse. It lets us parse command-line arguments so that we can
easily convert the scripts for project-specific applications. While
argparse is built-in, other libraries like click aim to simplify the
process. And they are obviously easy to use. For example, to write a
simple greetings application, click provides a lot simpler way as
below:
parser = argparse.ArgumentParser() parser.add_argument('name', type=str, help="what's your name?") args = parser.parse_args() def hello(name): print(name) hello(args.name)
@click.command() @click.argument('name') def hello(name): click.echo(f"Hello {name}!")
With click, the command hello remains a self-contained function with
clear decorators, whereas in argparse, you have to manage the argument
parser separately. Hhow beautiful it is to keep a command hello as a
python function with self-explanatory decorators! It is quite easy to
think that managing command lines is way easier with click than with
argparse. However, from my experience for the past year, it seems that
the opposite is true for several reasons.
First, the most painful drawback of click is that it uses decorators
heavily. A decorator turns a python function into somewhat else. In
turn, you can't call the function directly. If you need call them, you
end up using the API ctx.invoke(hello, ...args). I found it annoying
when I want just a simple python script to get a job done. Even for
one-off scripts, you might be forced to use @click.pass_context,
adding extra complexity. As a result, you need to one more argument
(ctx) in the function. In addition, a decorator breaks the editor
support. I use VSCode, and its type analysis does not work for
decorator-wrapped functions. It makes me go back and forth to see which
arguments the function needs.
Second, it adds an extra dependency to the project. I get that, even
with the drawbacks I described earlier, using click can help
developing projects. However, at the point you want to publish your
work, you need to consider dependencies. For this, no other options can
beat argparse! Because argparse is built-in, users don't need to
install anything. Using click means potential setup issues—leading
to unexpected GitHub issues or extra emails to your inbox.
Furthurmore, here are some ways to manage Python scripts with argparse
more efficiently in a project:
lib: utility modulestool: you implementargparse-applications using modules inlibscripts: you manage your one-off scripts here. bash, python whatever.
This way, you can manage CLI applications just like using
@click.group():
tool/command.py [args] # instead of ./tool.py command [args]
And your python utils are just python functions which do not need an
extra ctx object and they come with decent editor supports!