如何创建多个 CLI 选项在 python 中标识我的包名称?

How to create multiple CLI options identified my package name in a python?

我想为我的应用程序构建一个具有嵌套功能的 cli 界面。示例:

├── ...
├── setup.py
└── package-name
    ├──__init__.py
    ├──command1.py
    └──command2.py

package-name command1 --arg .. ..
package-name command2 --arg ..

python -m package-name command1 --arg ..

这里要注意的是command1command2是独立的模块,接受不同的命令行参数。因此,将它们链接在一起 __main__.py 可能也是另一个挑战。

我遇到了类似的问题,这些问题在 setup.py 中使用 entry_points 来创建类似的 cli 功能,但它并不是我正在寻找的。我发现了这个类似的问题。

如果你想在一个输入命令中访问多个 sub-cli,你可以在 __main__.py 实现一个 sub-command 管理器,它可以从 [=] 解析 sub-command 44=] 然后分派到目标模块。

1️⃣首先推荐google fire,无需额外代码即可满足大部分场景

这里有一个例子,你可以使用from command1 import xx将add/multiply函数替换为你的sub-command函数,并使用入口点暴露主函数。

import fire

def add(x, y):
  return x + y

def multiply(x, y):
  return x * y


def main():
  fire.Fire({
      'add': add,
      'multiply': multiply,
  })

if __name__ == '__main__':
  main()

我们可以按照下面的方式进行调试:

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200

2️⃣其次,如果出于某种目的需要自己实现,例如使用argparse为每个命令定义选项。一个典型的做法是 Django command, the official demo: Writing custom django-admin commands

核心步骤是:

  1. 定义一个 BaseCommand
  2. 在子commands.py中实现了BaseCommand,并将其命名为Command
  3. __main__.py 实现查找命令并调用
# base_command.py
class BaseCommand(object):
    def create_parser(self, prog_name, subcommand, **kwargs):
        """
        Create and return the ``ArgumentParser`` which will be used to
        parse the arguments to this command.
        """
        # TODO: create your ArgumentParser
        return CommandParser(**kwargs) 
        
    def add_arguments(self, parser):
        """
        Entry point for subclassed commands to add custom arguments.
        """
        pass

    def run_from_argv(self, argv):
        """
        Entry point for commands to be run from the command line.
        """
        parser = self.create_parser(argv[0], argv[1])
        options = parser.parse_args(argv[2:])
        cmd_options = vars(options)
        args = cmd_options.pop('args', ())
        self.handle(*args, **cmd_options)
    
    def handle(self, *args, **options):
        """
        The actual logic of the command. Subclasses must implement
        this method.
        """
        raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
    
    
# command1.py     
class Command(BaseCommand):
    def handle(self, *args, **options):
        print("Hello, it is command1!")
        
   
# command2.py     
class Command(BaseCommand):
    def handle(self, *args, **options):
        print("Hello, it is command2!")
        
    
# __main__.py         
def main():
    # sub command name is the second argument
    command_name = sys.argv[1]
    # build sub command module name, and import
    # package-name is the module name you mentioned
    cmd_package_name = f'package-name.{command_name}'
    instance = importlib.import_module(cmd_package_name)
    # create instance of sub command, the Command must exist
    command = instance.Command()
    command.run_from_argv(sys.argv)

if __name__ == "__main__":
    main()