fire - Python CLI Tool#

Python Fire is a library for automatically generating command line interfaces (CLIs) from absolutely any Python object.

Overview#

Fire 是一个由 Google 的团队维护, 但是并不作为 Google 的产品的 Python 开源项目. 该项目 2014 年就发布了. 和大部分命令行框架不同的是, fire 可以将 Python 中的函数, 方法, 类直接转化为一个命令行工具, 而无需重新编写 Wrapper. 该项目是我见过的 Python 社区中的命令行工具中最简单, 最易用的一个. 虽然它不适合作为非常复杂的 CLI 工具的底层框架, 但是它几乎适用于 90% 的情况. 并且它没有任何外部依赖, 安装交付非常方便.

How it Work#

任何 callable 的对象如果被 fire.Fire(callable_object) 包装, 那么这个对象就可以被当做命令行工具使用. 这个对象可以是函数, 类, 方法.

  • callable 对象的参数就是命令行参数.

  • callable 对象中的任何 mandatory 的参数会变成 CLI 的 mandatory 参数.
    • 你可以按照定义的顺序传入参数, 也可以下面四种方式中的任何一种传入参数 --arg_name value, --arg_name=value, --arg-name value, --arg-name=value. arg name 既可以用 underscore 也可以用 hyphen, 即时 Python 中的变量只能用 underscore. 你既可以用空格也可以用等号来分隔参数名和参数值.

  • callable 对象中的任何 optional 的参数会变成 CLI 的 optional 参数. 如果你不指定, 那么 callable 对象中的默认参数值将会被使用.

  • 如果你只指定了 --arg_name 但没有指定 value, 那么这个值将会被视为 True. 你可以在参数名前加 no 来传递一个 False, 例如 --noarg_name.

  • 传递某个 arg name 的时候不仅可以用 --arg_name, 还可以用首字母简写 -a (其中 a 这里只是个例子, 实际取决于你的参数名)

  • 如果你 fire 的对象是一个类, 那么类的方法将会被视为 sub command.

  • 如果你 fire 的对象是一个类, 并且它的属性里是一些其他类的实例, 那么其他类将会被视为 sub command, 而其他类的方法将会被视为 sub command 的 sub command.

  • fire 会自动根据参数和 doc string 生成 CLI 文档.
    • 根据参数是否可选以及它们的默认值会自动生成参数文档.

    • doc string 的第一行会被用作 command 的 name, 而剩下的部分则会被用作 command 的 description.

Examples#

Basic Usage#

 1# -*- coding: utf-8 -*-
 2
 3import typing as T
 4import fire
 5
 6
 7def main(
 8    name,
 9    is_kid: T.Optional[bool] = False,
10    age: T.Optional[int] = 18,
11):
12    """
13    this is main function
14
15    this is description
16
17    - sentence 1
18    - sentence 2
19    - sentence 3
20    """
21    print(f"name = {name}")
22    print(f"is_kid = {is_kid}")
23    print(f"age = {age}")
24
25
26if __name__ == "__main__":
27    fire.Fire(main)
$ python basic_usage.py -h
NAME
    basic_usage.py - this is main function

SYNOPSIS
    basic_usage.py NAME <flags>

DESCRIPTION
    this is description

    - sentence 1
    - sentence 2
    - sentence 3

POSITIONAL ARGUMENTS
    NAME

FLAGS
    -i, --is_kid=IS_KID
        Type: typing.Union[bool, NoneType]
        Default: False
    -a, --age=AGE
        Type: typing.Union[int, NoneType]
        Default: 18

NOTES
    You can also use flags syntax for POSITIONAL ARGUMENTS
$ python basic_usage.py --name alice
name = alice
is_kid = False
age = 18
$ python basic_usage.py --name alice --is_kid
name = alice
is_kid = True
age = 18
$ python basic_usage.py --name alice --age 30
name = alice
is_kid = False
age = 30

Subcommand example 1#

 1# -*- coding: utf-8 -*-
 2
 3"""
 4Simple sub command example.
 5
 6- Class = top level command
 7- Method = sub command
 8"""
 9
10import typing as T
11import fire
12
13
14class App:
15    """
16    Top level command document.
17    """
18    def __call__(self, version: T.Optional[bool] = False):
19        if version:
20            print("0.0.1")
21        else:
22            print("app")
23
24    def config(self):
25        """
26        "config" subcommand document
27        """
28        print("app config")
29
30    def run(self):
31        """
32        "run" subcommand document
33        """
34        print("app run")
35
36
37if __name__ == "__main__":
38    fire.Fire(App())
$ python subcommand_example1.py -h
NAME
    subcommand_example1.py - Top level command document.

SYNOPSIS
    subcommand_example1.py COMMAND | <flags>

DESCRIPTION
    Top level command document.

FLAGS
    -v, --version=VERSION
        Type: typing.Union[bool, NoneType]
        Default: False

COMMANDS
    COMMAND is one of the following:

     config
       "config" subcommand document

     run
       "run" subcommand document
$ python subcommand_example1.py -v
0.0.1
$ python subcommand_example1.py config
app config

Subcommand example 2#

 1# -*- coding: utf-8 -*-
 2
 3"""
 4Advanced sub command example.
 5
 6- Class = top level command
 7- Attribute, it is an instance of another class = sub command
 8- Method in other classes = sub command of sub command
 9"""
10
11import typing as T
12import fire
13
14
15class S3:
16    """
17    AWS S3 sub command
18    """
19
20    def ls(self):
21        """
22        List s3 buckets
23        """
24        print("run: aws s3 ls")
25
26
27class EC2:
28    """
29    AWS EC2 sub command
30    """
31
32    def list_instances(self):
33        """
34        List ec2 instances
35        """
36        print("run: aws ec2 list-instances")
37
38    def list_vpcs(self):
39        """
40        List ec2 vpc
41        """
42        print("run: aws ec2 list-vpcs")
43
44
45class AWS:
46    """
47    AWS CLI class doc string
48    """
49
50    def __init__(self):
51        self.s3 = S3()
52        self.ec2 = EC2()
53
54    def __call__(self, version: T.Optional[bool] = False):
55        """
56        AWS CLI
57        """
58        if version:
59            print("0.0.1")
60        else:
61            print("run: aws")
62
63
64if __name__ == "__main__":
65    fire.Fire(AWS())
$ python subcommand_example2.py -h
NAME
    subcommand_example2.py - AWS CLI class doc string

SYNOPSIS
    subcommand_example2.py GROUP | <flags>

DESCRIPTION
    AWS CLI class doc string

FLAGS
    -v, --version=VERSION
        Type: typing.Union[bool, NoneType]
        Default: False

GROUPS
    GROUP is one of the following:

     ec2
       AWS EC2 sub command

     s3
       AWS S3 sub command
$ python subcommand_example2.py -v
0.0.1
$ python subcommand_example2.py s3
NAME
    subcommand_example2.py s3 - AWS S3 sub command

SYNOPSIS
    subcommand_example2.py s3 COMMAND

DESCRIPTION
    AWS S3 sub command

COMMANDS
    COMMAND is one of the following:

     ls
       List s3 buckets
$ python subcommand_example2.py s3 ls
run: aws s3 ls