我想为我的应用程序构建一个具有嵌套功能的 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 ..
这里要注意的是 command1
和 command2
是接受不同命令行参数的独立模块。因此,在 __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/
核心步骤是:
- 定义一个 BaseCommand
- 在子commands.py中实现BaseCommand,命名为
Command
。 __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()