python - 如何创建多个 CLI 选项在 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 功能,但这并不是我想要的。我发现了这个类似的问题。 https://stackoverflow.com/questions/56534678/how-to-create-a-cli-in-python-that-can-be-installed-with-pip

回答1

如果你想在一个入口命令中访问多个子cli,你可以在__main__.py处实现一个子命令管理器,它可以从sys.argv解析子命令,然后分发到目标模块。

1️⃣首先,我推荐https://github.com/google/python-fire/blob/master/docs/guide.md,不需要额外的代码,大部分场景都能满足你。

这里是一个例子,你可以使用 from command1 import xx 替换你的子命令函数的加法/乘法函数,并使用入口点来暴露主函数。

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为每个命令定义选项。一个典型的做法是https://github.com/django/django/blob/main/django/core/management/base.py#L173,官方demo:https://docs.djangoproject.com/en/3.2/howto/custom-management-commands/

核心步骤是:

  1. 定义一个 BaseCommand
  2. 在子commands.py中实现BaseCommand,命名为Command
  3. __main__.py 实现 find 命令和调用
# 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()

相似文章

shopify - 提交问题包括堆栈跟踪

我使用命令->shopifythemeserve然后出现这个错误:X发生意外错误。提交问题包括堆栈跟踪。?向Shopify发送匿名错误报告?(您选择:不,不发送)C:/Ruby31-x64/lib/r...

随机推荐

最新文章