如何将我的 Click 命令(每个命令都有一组子命令)拆分为多个文件?
How can I split my Click commands, each with a set of sub-commands, into multiple files?
我开发了一个大型点击应用程序,但浏览不同的 commands/subcommands 变得越来越困难。如何将我的命令组织到单独的文件中?是否可以将命令及其子命令组织成单独的 类?
这是我想如何分离它的一个例子:
初始化
import click
@click.group()
@click.version_option()
def cli():
pass #Entry Point
command_cloudflare.py
@cli.group()
@click.pass_context
def cloudflare(ctx):
pass
@cloudflare.group('zone')
def cloudflare_zone():
pass
@cloudflare_zone.command('add')
@click.option('--jumpstart', '-j', default=True)
@click.option('--organization', '-o', default='')
@click.argument('url')
@click.pass_obj
@__cf_error_handler
def cloudflare_zone_add(ctx, url, jumpstart, organization):
pass
@cloudflare.group('record')
def cloudflare_record():
pass
@cloudflare_record.command('add')
@click.option('--ttl', '-t')
@click.argument('domain')
@click.argument('name')
@click.argument('type')
@click.argument('content')
@click.pass_obj
@__cf_error_handler
def cloudflare_record_add(ctx, domain, name, type, content, ttl):
pass
@cloudflare_record.command('edit')
@click.option('--ttl', '-t')
@click.argument('domain')
@click.argument('name')
@click.argument('type')
@click.argument('content')
@click.pass_obj
@__cf_error_handler
def cloudflare_record_edit(ctx, domain):
pass
command_uptimerobot.py
@cli.group()
@click.pass_context
def uptimerobot(ctx):
pass
@uptimerobot.command('add')
@click.option('--alert', '-a', default=True)
@click.argument('name')
@click.argument('url')
@click.pass_obj
def uptimerobot_add(ctx, name, url, alert):
pass
@uptimerobot.command('delete')
@click.argument('names', nargs=-1, required=True)
@click.pass_obj
def uptimerobot_delete(ctx, names):
pass
我不是点击专家,但只需将您的文件导入到主文件中,它就应该可以工作。我会将所有命令移动到单独的文件中,并让一个主文件导入其他文件。这样就更容易控制确切的顺序,以防它对你很重要。所以你的主文件看起来像:
import commands_main
import commands_cloudflare
import commands_uptimerobot
我现在正在寻找类似的东西,在你的情况下很简单,因为你在每个文件中都有组,你可以按照 documentation:[=13 中的解释解决这个问题=]
在 init.py
文件中:
import click
from command_cloudflare import cloudflare
from command_uptimerobot import uptimerobot
cli = click.CommandCollection(sources=[cloudflare, uptimerobot])
if __name__ == '__main__':
cli()
这个解决方案最好的部分是它完全符合 pep8 和其他 linters,因为你不需要导入你不会使用的东西,也不需要从任何地方导入 *。
为此使用 CommandCollection
的缺点是它会合并您的命令并且仅适用于命令组。恕我直言,更好的选择是使用 add_command
来获得相同的结果。
我有一个包含以下树的项目:
cli/
├── __init__.py
├── cli.py
├── group1
│ ├── __init__.py
│ ├── commands.py
└── group2
├── __init__.py
└── commands.py
每个子命令都有自己的模块,这使得使用更多帮助程序 类 和文件管理复杂的实现变得异常容易。在每个模块中,commands.py
文件包含 @click
注释。例子 group2/commands.py
:
import click
@click.command()
def version():
"""Display the current version."""
click.echo(_read_version())
如有必要,您可以轻松地在模块中创建更多 类 和 import
并在此处使用它们,从而为您的 CLI 提供 Python 的 类 和模块。
我的cli.py
是整个CLI的入口点:
import click
from .group1 import commands as group1
from .group2 import commands as group2
@click.group()
def entry_point():
pass
entry_point.add_command(group1.command_group)
entry_point.add_command(group2.version)
使用此设置,可以很容易地按关注点分离您的命令,并围绕它们构建它们可能需要的其他功能。到目前为止,它对我很有帮助...
假设您的项目具有以下结构:
project/
├── __init__.py
├── init.py
└── commands
├── __init__.py
└── cloudflare.py
组无非是多个命令,组可以嵌套。您可以将您的组分成模块并将它们导入到您的 init.py
文件中,然后使用 add_command.
将它们添加到 cli
组中
这是一个 init.py
示例:
import click
from .commands.cloudflare import cloudflare
@click.group()
def cli():
pass
cli.add_command(cloudflare)
您必须导入 cloudflare.py 文件中的 cloudflare 组。您的 commands/cloudflare.py
将如下所示:
import click
@click.group()
def cloudflare():
pass
@cloudflare.command()
def zone():
click.echo('This is the zone subcommand of the cloudflare command')
然后你可以运行这样的cloudflare命令:
$ python init.py cloudflare zone
此信息在文档中不是很明确,但如果您查看注释非常详细的源代码,您可以了解如何嵌套组。
我花了一段时间才弄明白
但我想我会把它放在这里是为了在我再次忘记该怎么做时提醒自己
我认为部分问题是 add_command 函数在 click 的 github 页面上提到,但在主要示例页面上没有提到
首先让我们创建一个名为 root.py
的初始 python 文件
import click
from cli_compile import cli_compile
from cli_tools import cli_tools
@click.group()
def main():
"""Demo"""
if __name__ == '__main__':
main.add_command(cli_tools)
main.add_command(cli_compile)
main()
接下来让我们将一些工具命令放在一个名为 cli_tools.py
的文件中
import click
# Command Group
@click.group(name='tools')
def cli_tools():
"""Tool related commands"""
pass
@cli_tools.command(name='install', help='test install')
@click.option('--test1', default='1', help='test option')
def install_cmd(test1):
click.echo('Hello world')
@cli_tools.command(name='search', help='test search')
@click.option('--test1', default='1', help='test option')
def search_cmd(test1):
click.echo('Hello world')
if __name__ == '__main__':
cli_tools()
接下来让我们将一些编译命令放在一个名为cli_compile.py
的文件中
import click
@click.group(name='compile')
def cli_compile():
"""Commands related to compiling"""
pass
@cli_compile.command(name='install2', help='test install')
def install2_cmd():
click.echo('Hello world')
@cli_compile.command(name='search2', help='test search')
def search2_cmd():
click.echo('Hello world')
if __name__ == '__main__':
cli_compile()
运行ning root.py 现在应该给我们
Usage: root.py [OPTIONS] COMMAND [ARGS]...
Demo
Options:
--help Show this message and exit.
Commands:
compile Commands related to compiling
tools Tool related commands
运行宁"root.py compile"应该给我们
Usage: root.py compile [OPTIONS] COMMAND [ARGS]...
Commands related to compiling
Options:
--help Show this message and exit.
Commands:
install2 test install
search2 test search
您还会注意到您可以直接 运行 cli_tools.py 或 cli_compile.py 以及我在其中包含的主语句
编辑: 刚刚意识到我的 answer/comment 只不过是对 Click 官方文档在“自定义多命令”部分中提供的内容的翻版:https://click.palletsprojects.com/en/7.x/commands/#custom-multi-commands
只是为了添加@jdno 接受的优秀答案,我想出了一个辅助函数 auto-imports 和 auto-adds 子命令模块,它大大减少了我 cli.py
:
我的项目结构是这样的:
projectroot/
__init__.py
console/
│
├── cli.py
└── subcommands
├── bar.py
├── foo.py
└── hello.py
每个子命令文件看起来像这样:
import click
@click.command()
def foo():
"""foo this is for foos!"""
click.secho("FOO", fg="red", bg="white")
(目前,我每个文件只有一个子命令)
在 cli.py
中,我编写了一个 add_subcommand()
函数,它循环遍历由“subcommands/*.py”组成的每个文件路径,然后执行导入和添加命令。
cli.py 脚本的主体被简化为:
import click
import importlib
from pathlib import Path
import re
@click.group()
def entry_point():
"""whats up, this is the main function"""
pass
def main():
add_subcommands()
entry_point()
if __name__ == '__main__':
main()
这就是 add_subcommands()
函数的样子:
SUBCOMMAND_DIR = Path("projectroot/console/subcommands")
def add_subcommands(maincommand=entry_point):
for modpath in SUBCOMMAND_DIR.glob('*.py'):
modname = re.sub(f'/', '.', str(modpath)).rpartition('.py')[0]
mod = importlib.import_module(modname)
# filter out any things that aren't a click Command
for attr in dir(mod):
foo = getattr(mod, attr)
if callable(foo) and type(foo) is click.core.Command:
maincommand.add_command(foo)
如果我要设计一个具有多层嵌套和上下文切换的命令,我不知道这有多稳健。但它现在似乎工作正常:)
当您希望您的用户 pip install "your_module",然后使用命令时,您可以将它们作为列表添加到 setup.py entry_points
中:
entry_points={
'console_scripts': [
'command_1 = src.cli:function_command_1',
'command_2 = src.cli:function_command_2',
]
每个命令都必须在 cli 文件中运行。
我开发了一个大型点击应用程序,但浏览不同的 commands/subcommands 变得越来越困难。如何将我的命令组织到单独的文件中?是否可以将命令及其子命令组织成单独的 类?
这是我想如何分离它的一个例子:
初始化
import click
@click.group()
@click.version_option()
def cli():
pass #Entry Point
command_cloudflare.py
@cli.group()
@click.pass_context
def cloudflare(ctx):
pass
@cloudflare.group('zone')
def cloudflare_zone():
pass
@cloudflare_zone.command('add')
@click.option('--jumpstart', '-j', default=True)
@click.option('--organization', '-o', default='')
@click.argument('url')
@click.pass_obj
@__cf_error_handler
def cloudflare_zone_add(ctx, url, jumpstart, organization):
pass
@cloudflare.group('record')
def cloudflare_record():
pass
@cloudflare_record.command('add')
@click.option('--ttl', '-t')
@click.argument('domain')
@click.argument('name')
@click.argument('type')
@click.argument('content')
@click.pass_obj
@__cf_error_handler
def cloudflare_record_add(ctx, domain, name, type, content, ttl):
pass
@cloudflare_record.command('edit')
@click.option('--ttl', '-t')
@click.argument('domain')
@click.argument('name')
@click.argument('type')
@click.argument('content')
@click.pass_obj
@__cf_error_handler
def cloudflare_record_edit(ctx, domain):
pass
command_uptimerobot.py
@cli.group()
@click.pass_context
def uptimerobot(ctx):
pass
@uptimerobot.command('add')
@click.option('--alert', '-a', default=True)
@click.argument('name')
@click.argument('url')
@click.pass_obj
def uptimerobot_add(ctx, name, url, alert):
pass
@uptimerobot.command('delete')
@click.argument('names', nargs=-1, required=True)
@click.pass_obj
def uptimerobot_delete(ctx, names):
pass
我不是点击专家,但只需将您的文件导入到主文件中,它就应该可以工作。我会将所有命令移动到单独的文件中,并让一个主文件导入其他文件。这样就更容易控制确切的顺序,以防它对你很重要。所以你的主文件看起来像:
import commands_main
import commands_cloudflare
import commands_uptimerobot
我现在正在寻找类似的东西,在你的情况下很简单,因为你在每个文件中都有组,你可以按照 documentation:[=13 中的解释解决这个问题=]
在 init.py
文件中:
import click
from command_cloudflare import cloudflare
from command_uptimerobot import uptimerobot
cli = click.CommandCollection(sources=[cloudflare, uptimerobot])
if __name__ == '__main__':
cli()
这个解决方案最好的部分是它完全符合 pep8 和其他 linters,因为你不需要导入你不会使用的东西,也不需要从任何地方导入 *。
为此使用 CommandCollection
的缺点是它会合并您的命令并且仅适用于命令组。恕我直言,更好的选择是使用 add_command
来获得相同的结果。
我有一个包含以下树的项目:
cli/
├── __init__.py
├── cli.py
├── group1
│ ├── __init__.py
│ ├── commands.py
└── group2
├── __init__.py
└── commands.py
每个子命令都有自己的模块,这使得使用更多帮助程序 类 和文件管理复杂的实现变得异常容易。在每个模块中,commands.py
文件包含 @click
注释。例子 group2/commands.py
:
import click
@click.command()
def version():
"""Display the current version."""
click.echo(_read_version())
如有必要,您可以轻松地在模块中创建更多 类 和 import
并在此处使用它们,从而为您的 CLI 提供 Python 的 类 和模块。
我的cli.py
是整个CLI的入口点:
import click
from .group1 import commands as group1
from .group2 import commands as group2
@click.group()
def entry_point():
pass
entry_point.add_command(group1.command_group)
entry_point.add_command(group2.version)
使用此设置,可以很容易地按关注点分离您的命令,并围绕它们构建它们可能需要的其他功能。到目前为止,它对我很有帮助...
假设您的项目具有以下结构:
project/
├── __init__.py
├── init.py
└── commands
├── __init__.py
└── cloudflare.py
组无非是多个命令,组可以嵌套。您可以将您的组分成模块并将它们导入到您的 init.py
文件中,然后使用 add_command.
cli
组中
这是一个 init.py
示例:
import click
from .commands.cloudflare import cloudflare
@click.group()
def cli():
pass
cli.add_command(cloudflare)
您必须导入 cloudflare.py 文件中的 cloudflare 组。您的 commands/cloudflare.py
将如下所示:
import click
@click.group()
def cloudflare():
pass
@cloudflare.command()
def zone():
click.echo('This is the zone subcommand of the cloudflare command')
然后你可以运行这样的cloudflare命令:
$ python init.py cloudflare zone
此信息在文档中不是很明确,但如果您查看注释非常详细的源代码,您可以了解如何嵌套组。
我花了一段时间才弄明白 但我想我会把它放在这里是为了在我再次忘记该怎么做时提醒自己 我认为部分问题是 add_command 函数在 click 的 github 页面上提到,但在主要示例页面上没有提到
首先让我们创建一个名为 root.py
的初始 python 文件import click
from cli_compile import cli_compile
from cli_tools import cli_tools
@click.group()
def main():
"""Demo"""
if __name__ == '__main__':
main.add_command(cli_tools)
main.add_command(cli_compile)
main()
接下来让我们将一些工具命令放在一个名为 cli_tools.py
的文件中import click
# Command Group
@click.group(name='tools')
def cli_tools():
"""Tool related commands"""
pass
@cli_tools.command(name='install', help='test install')
@click.option('--test1', default='1', help='test option')
def install_cmd(test1):
click.echo('Hello world')
@cli_tools.command(name='search', help='test search')
@click.option('--test1', default='1', help='test option')
def search_cmd(test1):
click.echo('Hello world')
if __name__ == '__main__':
cli_tools()
接下来让我们将一些编译命令放在一个名为cli_compile.py
的文件中import click
@click.group(name='compile')
def cli_compile():
"""Commands related to compiling"""
pass
@cli_compile.command(name='install2', help='test install')
def install2_cmd():
click.echo('Hello world')
@cli_compile.command(name='search2', help='test search')
def search2_cmd():
click.echo('Hello world')
if __name__ == '__main__':
cli_compile()
运行ning root.py 现在应该给我们
Usage: root.py [OPTIONS] COMMAND [ARGS]...
Demo
Options:
--help Show this message and exit.
Commands:
compile Commands related to compiling
tools Tool related commands
运行宁"root.py compile"应该给我们
Usage: root.py compile [OPTIONS] COMMAND [ARGS]...
Commands related to compiling
Options:
--help Show this message and exit.
Commands:
install2 test install
search2 test search
您还会注意到您可以直接 运行 cli_tools.py 或 cli_compile.py 以及我在其中包含的主语句
编辑: 刚刚意识到我的 answer/comment 只不过是对 Click 官方文档在“自定义多命令”部分中提供的内容的翻版:https://click.palletsprojects.com/en/7.x/commands/#custom-multi-commands
只是为了添加@jdno 接受的优秀答案,我想出了一个辅助函数 auto-imports 和 auto-adds 子命令模块,它大大减少了我 cli.py
:
我的项目结构是这样的:
projectroot/
__init__.py
console/
│
├── cli.py
└── subcommands
├── bar.py
├── foo.py
└── hello.py
每个子命令文件看起来像这样:
import click
@click.command()
def foo():
"""foo this is for foos!"""
click.secho("FOO", fg="red", bg="white")
(目前,我每个文件只有一个子命令)
在 cli.py
中,我编写了一个 add_subcommand()
函数,它循环遍历由“subcommands/*.py”组成的每个文件路径,然后执行导入和添加命令。
cli.py 脚本的主体被简化为:
import click
import importlib
from pathlib import Path
import re
@click.group()
def entry_point():
"""whats up, this is the main function"""
pass
def main():
add_subcommands()
entry_point()
if __name__ == '__main__':
main()
这就是 add_subcommands()
函数的样子:
SUBCOMMAND_DIR = Path("projectroot/console/subcommands")
def add_subcommands(maincommand=entry_point):
for modpath in SUBCOMMAND_DIR.glob('*.py'):
modname = re.sub(f'/', '.', str(modpath)).rpartition('.py')[0]
mod = importlib.import_module(modname)
# filter out any things that aren't a click Command
for attr in dir(mod):
foo = getattr(mod, attr)
if callable(foo) and type(foo) is click.core.Command:
maincommand.add_command(foo)
如果我要设计一个具有多层嵌套和上下文切换的命令,我不知道这有多稳健。但它现在似乎工作正常:)
当您希望您的用户 pip install "your_module",然后使用命令时,您可以将它们作为列表添加到 setup.py entry_points
中:
entry_points={
'console_scripts': [
'command_1 = src.cli:function_command_1',
'command_2 = src.cli:function_command_2',
]
每个命令都必须在 cli 文件中运行。