点击

时间:2018-06-07 22:34:59

标签: python python-3.x command-line-interface python-click

我正试图在我的第一个Click CLI应用程序上进行一种递归调用。 要点是让子命令与第一个相关联,因此,我试图将它们全部分离到不同的文件/模块中,以提高它的可维护性。

我有当前的directory

root
|-commands
|-project
|---__init__
|---command1
|---command2
|-database
|---__init__
|---command1
|---command2

这是我的主要文件:

import click
from commands.project import project
from commands.database import database


@click.group(help="Main command")
def main():
    pass


main.add_command(project)
main.add_command(database)

我的项目__init__文件:

from commands.project.command1 import *
from commands.project.command2 import *
import click


@click.group(help="Projects")
def project():
    pass


project.add_command(command1)
project.add_command(command2)

我的commands.project.command1文件:

import click


@click.command()
def command1():
    """
    Execute all the steps required to update the project.
    """
    pass

这里的要点是,每次我想添加一个新的子命令时,我都需要:

  1. .py文件的所有代码添加到命令的相应子命令/子模块文件夹中(显然!)

  2. import文件

  3. 上添加__init__语句
  4. 将这个新命令关联到它的父项(在这种情况下是项目/数据库)

  5. 有没有办法进行循环/动态加载以避免第2步和第3步?

    修改

    在尝试了Stephen Rauch的方式之后,它成功地包含了所有提供的文件,但是没有一个命令仅与-一起使用函数名称(例如: update-project - > update_project)。

    root
    |-commands
    |-project
    |---update
    |---install_project
    |-database
    |---command_one
    |---command_two
    

    main.py

    # main command ----------------------------------------------------------- ###
    @click.group(help="CLI tool!", context_settings=dict(max_content_width=120))
    def main():
        pass
    
    
    # PROJECT command group -------------------------------------------------------- ###
    @main.group(cls=group_from_folder("commands/project"),
                short_help="Project installation and upgrade utils.",
                help="Project installation and upgrade.")
    def project():
        pass
    

    命令/项目/ install_project.py

    import click    
    
    @click.command(name="install-project",
                   help="This options allows you to easily install project",
                   short_help="Install a brand new project")
    @click.pass_context
    def install_project(ctx):
    

    CLI结果main project --help (请注意install_project代替install-project子命令)

    Usage: main project [OPTIONS] COMMAND [ARGS]...
    
      Project installation and upgrade.
    
    Options:
      --help  Show this message and exit.
    
    Commands:
      install_project                     Install a brand new project one
    

2 个答案:

答案 0 :(得分:6)

修改示例from here,您可以取消第2步和第3步。我建议通过闭包为每个文件夹创建一个自定义类。这完全消除了命令文件夹中__init__.py的需要。此外,无需导入文件夹(模块)或文件夹中的命令。

自定义组类创建者:

import click
import os

def group_from_folder(group_folder_name):

    folder = os.path.join(os.path.dirname(__file__), group_folder_name)

    class FolderCommands(click.MultiCommand):

        def list_commands(self, ctx):
            return sorted(
                f[:-3] for f in os.listdir(folder) if f.endswith('.py'))

        def get_command(self, ctx, name):
            namespace = {}
            command_file = os.path.join(folder, name + '.py')
            with open(command_file) as f:
                code = compile(f.read(), command_file, 'exec')
                eval(code, namespace, namespace)
            return namespace[name.replace('-', '_').lower()]

    return FolderCommands

使用自定义类:

要使用自定义类,首先将命令(在问题中结构化)放入文件夹中。然后使用cls参数装饰group命令,并传递一个初始化的自定义类,指向包含命令的文件夹。

@cli.group(cls=group_from_folder('project'))
def group():
    "command for grouping"

测试代码:

@click.group()
def cli():
    "My awesome script"


@cli.group(cls=group_from_folder('group'))
def group():
    "command for grouping"


if __name__ == "__main__":
    commands = (
        'group command-test',
        'group',
        'group --help',
        '',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for cmd in commands:
        try:
            time.sleep(0.1)
            print('-----------')
            print('> ' + cmd)
            time.sleep(0.1)
            cli(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

文件组/ command-test.py

import click


@click.command('command-test')
def command_test():
    """
    Execute all the steps required to update the project.
    """
    click.echo('Command Test')

结果:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> group command-test
Command Test
-----------
> group
Usage: test.py group [OPTIONS] COMMAND [ARGS]...

  command for grouping

Options:
  --help  Show this message and exit.

Commands:
  command-test  Execute all the steps required to update the...
-----------
> group --help
Usage: test.py group [OPTIONS] COMMAND [ARGS]...

  command for grouping

Options:
  --help  Show this message and exit.

Commands:
  command-test  Execute all the steps required to update the...
-----------
> 
Usage: test.py [OPTIONS] COMMAND [ARGS]...

  My awesome script

Options:
  --help  Show this message and exit.

Commands:
  group  command for grouping

答案 1 :(得分:1)

我建议您只从特定的Python包中读取命令,然后将其添加到您的输入组中。

假设我们有这样的结构:

|--app
   |--commands
      |--__init__.py
      |--group1
         |--__init__.py
         |--command1.py
      |--group2
         |--__init__.py
         |--command2.py
|--__init__.py
|--cli.py

然后您的命令文件需要单击一下。具有指定名称的命令和具有名称“ command”的函数:

import click

@click.command(name="your-first-command")
def command():
    pass

每个组中的初始化文件都需要包含doc字符串,以使click.Group具有正确的“帮助”值。

最有趣的cli.py:

import click
import importlib
import pkgutil
import os.path


def get_commands_from_pkg(pkg) -> dict:
    pkg_obj = importlib.import_module(pkg)

    pkg_path = os.path.dirname(pkg_obj.__file__)

    commands = {}
    for module in pkgutil.iter_modules([pkg_path]):
        module_obj = importlib.import_module(f"{pkg}.{module.name}")
        if not module.ispkg:
            commands[module_obj.command.name] = module_obj.command

        else:
            commands[module.name.replace('_', '-')] = click.Group(
                context_settings={'help_option_names': ['-h', '--help']},
                help=module_obj.__doc__,
                commands=get_commands_from_pkg(f"{pkg}.{module.name}")
            )

    return commands


@click.group(context_settings={'help_option_names': ['-h', '--help']}, help="Your CLI",
             commands=get_commands_from_pkg('app.commands'))
def cli():
    pass

如您所见,我们以递归方式创建点击组并将点击命令添加到特定的组。