fis*_*lin 11 python command-line-interface python-3.x python-click
我试图在我的第一个Click CLI应用程序上进行一种递归调用.要点是让子命令与第一个相关联,因此,我试图将它们全部分离到不同的文件/模块中以提高它的可维护性.
我有现在directory:
root
|-commands
|-project
|---__init__
|---command1
|---command2
|-database
|---__init__
|---command1
|---command2
Run Code Online (Sandbox Code Playgroud)
这是我的主要文件:
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)
Run Code Online (Sandbox Code Playgroud)
我的项目__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)
Run Code Online (Sandbox Code Playgroud)
我的commands.project.command1档案:
import click
@click.command()
def command1():
"""
Execute all the steps required to update the project.
"""
pass
Run Code Online (Sandbox Code Playgroud)
这里的要点是,每次我想添加一个新的子命令时,我需要:
.py在相应的子命令/子模块文件夹中添加包含所有代码的文件(显然!)
import在它的__init__文件上添加它的声明
将此新命令与其父项(项目/数据库,在本例中)相关联
有没有办法进行循环/动态加载以避免第2步和第3步?
编辑
在尝试过Stephen Rauch之后,它成功地包含了所有提供的文件,但是没有一个命令-仅与函数名称一起使用(例如:- > update-projectupdate_project).
root
|-commands
|-project
|---update
|---install_project
|-database
|---command_one
|---command_two
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
命令/项目/ 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):
Run Code Online (Sandbox Code Playgroud)
CLI结果main project --help (注意install_project而不是install-projectsub子命令)
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
Run Code Online (Sandbox Code Playgroud)
从这里修改示例,您可以消除第二步和第三步.我建议通过闭包为每个文件夹创建一个自定义类.这完全消除__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
Run Code Online (Sandbox Code Playgroud)
要使用自定义类,首先将命令(在问题中结构化)放入文件夹中.然后使用cls参数装饰group命令,并传递一个初始化的自定义类,指向包含命令的文件夹.
@cli.group(cls=group_from_folder('project'))
def group():
"command for grouping"
Run Code Online (Sandbox Code Playgroud)
@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
Run Code Online (Sandbox Code Playgroud)
import click
@click.command('command-test')
def command_test():
"""
Execute all the steps required to update the project.
"""
click.echo('Command Test')
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我建议您只从特定的 Python 包中读取命令,然后添加到您的条目组中。
假设我们有这样的结构:
|--app
|--commands
|--__init__.py
|--group1
|--__init__.py
|--command1.py
|--group2
|--__init__.py
|--command2.py
|--__init__.py
|--cli.py
Run Code Online (Sandbox Code Playgroud)
那么你的命令文件需要包含一个具有指定名称的单击命令和一个名为“command”的函数:
import click
@click.command(name="your-first-command")
def command():
pass
Run Code Online (Sandbox Code Playgroud)
每个组中的初始化文件都需要包含文档字符串,以便为您的 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
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我们递归地创建单击组并将单击命令添加到特定组。