У меня есть одно приложение с большим щелчком, которое я разработал, но навигация по различным командам / подкомандам становится затруднительной. Как мне организовать свои команды в отдельные файлы? Можно ли организовать команды и их подкоманды в отдельные классы?
Вот пример того, как я хотел бы это разделить:
в этом
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
@click.pass_context
. В качестве альтернативы существует также так называемый доступ к глобальному контексту : click.pocoo.org/6/advanced/#global-context-access .CommandCollection
. В ответе Оскара есть пример, и в документации по щелчку есть действительно хороший пример : click.palletsprojects.com/en/7.x/commands/… .Предположим, ваш проект имеет следующую структуру:
Группы - это не что иное, как несколько команд, и группы могут быть вложенными. Вы можете разделить свои группы на модули и импортировать их в свой
init.py
файл и добавить их вcli
группу с помощью команды add_command.Вот
init.py
пример:import click from .commands.cloudflare import cloudflare @click.group() def cli(): pass cli.add_command(cloudflare)
Вам необходимо импортировать группу cloudflare, которая находится внутри файла cloudflare.py. Твой
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 следующим образом:
Эта информация не очень явна в документации, но если вы посмотрите исходный код, который очень хорошо прокомментирован, вы увидите, как группы могут быть вложены.
источник
@cloudflare.command()
изzone
функции, если я импортируюzone
откуда-то еще?На данный момент я ищу что-то подобное, в вашем случае это просто, потому что у вас есть группы в каждом из файлов, вы можете решить эту проблему, как описано в документации :
В
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 и другими линтерами, потому что вам не нужно импортировать то, что вы бы не использовали, и вам не нужно импортировать * откуда угодно.
источник
cli
из init.py, но это приводит к циклическому импорту. Не могли бы вы объяснить, как это сделать?click.group
которую вы импортируете в CLI верхнего уровня.Мне потребовалось время, чтобы понять это, но я решил, что поставлю это здесь, чтобы напомнить себе, когда я снова забуду, как это сделать. Я думаю, что отчасти проблема в том, что функция add_command упоминается на странице щелчка на github, но не в основном страница примеров
сначала давайте создадим начальный файл python с именем root.py
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()
запуск 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 напрямую, а также я включил туда основной оператор
источник
Я не эксперт по кликам, но он должен работать, просто импортируя ваши файлы в основной. Я бы переместил все команды в отдельные файлы, а один основной файл импортировал бы другие. Так будет проще контролировать точный порядок, если это важно для вас. Итак, ваш основной файл будет выглядеть так:
import commands_main import commands_cloudflare import commands_uptimerobot
источник
изменить: только что понял, что мой ответ / комментарий - это немного больше, чем переработка того, что предлагают официальные документы Click в разделе «Пользовательские мультикоманды»: https://click.palletsprojects.com/en/7.x/commands/#custom -мультикоманды
Просто чтобы добавить к превосходному принятому ответу @jdno, я придумал вспомогательную функцию, которая автоматически импортирует и автоматически добавляет модули подкоманд, что значительно сокращает шаблон в моем
cli.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)
Я не знаю, насколько это было бы надежно, если бы я проектировал команду с несколькими уровнями вложенности и переключения контекста. Но пока вроде работает нормально :)
источник