Python command line analysis library comparison (argparse, click, fire)

Comparison of Python command line analysis libraries

Introduction

This article is the 18th day of Python Advent Calender.

Recently, I've been using the command line a little more often. I've been using the default argparse, but I'm not sure about it so I'll look for other options and compare them. I will investigate and compare with click, fire which seems to be popular I will.

This article uses the following versions of the library:

pipenv --python 3.7.5
$ pipenv install click==7.0
$ pipenv install fire==0.2.1

Command line tool to create

python commands.py [command] [options] NAME

Basic

$ python commands.py hello World
Hello, World
$ python commands.py hellow World
Goodbye, World

option

$ python commands.py hello --greeting=Wazzup World
Whazzup, World
$ python commands.py goodbye --greeting=Later World
Later, World
$ python commands.py hello --caps World
HELLO, WORLD
$ python commands.py hello --greeting=Wazzup --caps World
WAZZUP, WORLD

In this article, we will compare the methods of each library to implement the following functions along the flow of the reference article.

  1. Command (hello, goodbye)
  2. Arguments
  3. Options / flags (--greeting = \ , --caps)

Additional features (will be written later)

  1. Version display
  2. Help message
  3. Error handling

command

I will do the basic writing of various libraries.

Argparse

import argparse

def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    hello_parser = subparsers.add_parser("hello")
    goodbye_parser = subparsers.add_parser("goodbye")
    parser.parse_args()

if __name__ == "__main__":
    main()

You now have two commands and a built-in help message. The help message changes when run as an option on the command hello.

$ python argparse/commands.py --helop
usage: commands.py [-h] {hello} ...

positional arguments:
    {hello}

optional arguments:
    -h, --help  show this help message and exit
$ python commands.py hello --help
usage: commands.py hello [-h]

optional arguments:
    -h, --help  show this help message and exit

Click

import click

@click.group()
def greet():
    pass

@greet.command()
def hello(**kwargs):
    pass

@greet.command()
def goodbye(**kwargs):
    pass

if __name__ == "__main__":
    greet()

You now have two commands and a built-in help message. The help message changes when run as an option on the command hello.

$ python click/commands.py  --help
Usage: commands.py [OPTIONS] COMMAND [ARGS]...

Options:
    --help  Show this message and exit.

Commands:
    goodbye
    hello

$ python click/commands.py hello --help
Usage: commands.py hello [OPTIONS]

Options:
    --help  Show this message and exit.

Fire

import fire

def hello():
    pass

def goodbye():
    pass

if __name__ == "__main__":
    fire.Fire({"hello": hello, "goodbye": goodbye})

You now have two commands and a built-in help message. The help message changes when run as an option on the command hello. In addition, fire provides help in man format. Fire can also be implemented in the following ways.

--How to arrange functions

import fire

def hello():
    pass

def goodbye():
    pass

if __name__ == "__main__":
    fire.Fire()

--How to pass in class

import fire

class Greet:
    def hello(self):
        pass

    def goodbye(self):
        pass

if __name__ == "__main__":
    greet = Greet()
    fire.Fire(greet)
$ python fire/commands.py --help

NAME
    commands.py

SYNOPSIS
    commands.py COMMAND

COMMANDS
    COMMAND is one of the following:
        goodbye
        hello

$ python fire/commands.py hello --help

NAME
    commands.py hello

SYNOPSIS
    commands.py hello

It's very interesting with a different approach for each library in the basic one. Next, we will add the NAME argument and the logic to output the result from each tool.

argument

Here we will add new logic to the code we wrote above. Add a comment to the new line to indicate your purpose. The argument (position specification) is a required input. This time, we'll add an argument with name so that the tool can greet a specific person.

Argparse

Use ʻadd_argumentto add arguments to the subcommand. Then set it withset_defautls to execute the function. Finally, after parsing the arguments, execute the function with ʻargs.func (args).

import argparse

def hello(args):
    print(f"Hello, {args.name}")

def goodbye(args):
    print(f"Goodbye, {args.name}")

def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    hello_parser = subparsers.add_parser("hello")
    hello_parser.add_argument("name")
    hello_parser.set_defaults(func=hello)

    goodbye_parser = subparsers.add_parser("goodbye")
    goodbye_parser.add_argument("name")
    goodbye_parser.set_defaults(func=goodbye)

    args = parser.parse_args()
    args.func(args)

if __name__ == "__main__":
    main()
$ python argparse/commands.py hello World
Hello, World

$ python argparse/commands.py hello --help
usage: commands.py hello [-h] name

positional arguments:
    name

optional arguments:
    -h, --help  show this help message and

Click

Use @ click.argument to add an argument to Click. In this case, you just pass the argument name, but there are various options.

$ python click/commands.py hello World
Hello, World

$ python click/commands.py hello --help
Usage: commands.py hello [OPTIONS] NAME

Options:
    --help  Show this message and exit.

Fire

Fire just adds arguments to the function. In Fire, it's basically just a function / class implementation, so it's pretty simple.

import fire

def hello(name):
    print(f"Hello, {name}")

def goodbye(name):
    print(f"Goodbye, {name}")

if __name__ == "__main__":
    fire.Fire({"hello": hello, "goodbye": goodbye})
$ python fire/commands.py hello World
Hello, World
(test-cli) ikura4@ikura1-ThinkPad:~/test/test-cli$ python fire/commands.py hello --help

NAME
    commands.py hello

SYNOPSIS
    commands.py hello NAME

POSITIONAL ARGUMENTS
    NAME

NOTES
    You can also use flags syntax for POSITIONAL ARGUMENTS
(END)

Options / flags

Here we'll add the new logic again to the code we wrote above. The option is a non-required input that can be specified. In this example, add --greeting = [greeting] and --caps. The default value for greeting is Hello or Goodbye, and the user can pass any value. For example, if you set --greeting = Wazzup, you will seeWazzup, [name]. If --caps is given, the display will be everything. For example, --caps will displayHELLO [NAME].

Argparse

import argparse

#Since I started to pass greetings
#Unified to greet function
def greet(args):
    output = f"{args.greeting}, {args.name}"
    if args.caps:
        output = output.upper()
    print(output)

def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    hello_parser = subparsers.add_parser("hello")
    #Add name to the argument
    hello_parser.add_argument("name")
    #Added greeting option and default
    hello_parser.add_argument("--greeting", default="Hello")
    #Added flag with default False
    hello_parser.add_argument("--caps", action="store_true")
    hello_parser.set_defaults(func=greet)

    goodbye_parser = subparsers.add_parser("goodbye")
    goodbye_parser.add_argument("name")
    goodbye_parser.add_argument("--greeting", default="Goodbye")
    goodbye_parser.add_argument("--caps", action="store_true")
    goodbye_parser.set_defaults(func=greet)

    args = parser.parse_args()
    args.func(args)

if __name__ == "__main__":
    main()
$ python argparse/commands.py hello --greeting=Wazzup World
Wazzup, World

$ python argparse/commands.py hello --caps World
HELLO, WORLD

$ python argparse/commands.py hello --greeting=Wazzup --caps World
WAZZUP, WORLD

$ python argparse/commands.py hello --help
usage: commands.py hello \[-h\] [--greeting GREETING] [--caps] name

positional arguments:
    name

optional arguments:
    -h, --help           show this help message and exit
    --greeting GREETING
    --caps

Click

Add greeting and caps with @ click.option. Since there is a default value, I made one function.

import click

def greeter(name, greeting, caps):
    output = f"{greeting}, {name}"
    if caps:
        output = output.upper()
    print(output)

@click.group()
def greet():
    pass

@greet.command()
@click.argument("name")
#Added greeting option and default
@click.option("--greeting", default="Hello")
#Added flag(is_flag=Can be flagged with True)
@click.option("--caps", is_flag=True)
def hello(name, greeting, caps):
    greeter(name, greeting, caps)

@greet.command()
@click.argument("name")
@click.option("--greeting", default="Goodbye")
@click.option("--caps", is_flag=True)
def goodbye(name, greeting, caps):
    greeter(name, greeting, caps)

if __name__ == "__main__":
    greet()
$ python click/commands.py hello --greeting=Wazzup World
Wazzup, World

$ python click/commands.py hello --caps World
HELLO, WORLD

$ python click/commands.py hello --greeting=Wazzup --caps World
WAZZUP, WORLD

$ python click/commands.py hello --helpUsage: commands.py hello [OPTIONS] NAME
Options:
    --greeting TEXT
    --caps
    --help           Show this message and exit.

Fire

Add greeting and caps by adding arguments to the function. Also, since there is common processing, I summarized it again in greet.

import fire

def greet(name, greeting, caps):
    output = f"{greeting}, {name}"
    if caps:
        output = output.upper()
    print(output)

def hello(name, greeting="Hello", caps=False):
    greet(name, greeting, caps)

def goodbye(name, greeting="Goodbye", caps=False):
    greet(name, greeting, caps)

if __name__ == "__main__":
    fire.Fire({"hello": hello, "goodbye": goodbye})

There is a caveat to fire when passing a bool. The token after passing --caps is passed to --caps. There are three ways to specify it:

--Add the bool option at the end

$ python fire/commands.py hello --greeting=Wazzup World
Wazzup, World

$ python fire/commands.py hello --caps=True World
HELLO, WORLD

$ python fire/commands.py hello --greeting=Wazzup --caps=True World
WAZZUP, WORLD

$ python fire/commands.py hello --help
NAME
    commands.py hello

SYNOPSIS
    commands.py hello NAME <flags>

POSITIONAL ARGUMENTS
    NAME

FLAGS
    --greeting=GREETING
    --caps=CAPS

NOTES
    You can also use flags syntax for POSITIONAL ARGUMENTS
(END)

Finally

Most of the things I make are basically small, so I will use Fire. I'm thinking about Click next.

Reference page

Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click

Recommended Posts

Python command line analysis library comparison (argparse, click, fire)
[Python] How to test command line parser click
Command line argument processing (Python docopt)
Decompose command arguments in one line in Python
Build a command line app in Python to understand setup.py, argparse, GitHub Actions
Template for creating command line applications in Python
Thorough comparison of three Python morphological analysis libraries
My thoughts on python2.6 command line app template
[Introduction to Udemy Python3 + Application] 67. Command line arguments