Mam jedną dużą aplikację do klikania, którą opracowałem, ale nawigacja po różnych poleceniach / podpoleceniach staje się trudna. Jak zorganizować moje polecenia w oddzielne pliki? Czy można organizować polecenia i ich podkomendy w oddzielne klasy?
Oto przykład, jak chciałbym to rozdzielić:
w tym
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
. Alternatywnie istnieje również coś, co nazywa się Globalny dostęp do kontekstu : click.pocoo.org/6/advanced/#global-context-access .CommandCollection
. Odpowiedź Oscara ma przykład, a w dokumentacji Click jest naprawdę ładny: click.palletsprojects.com/en/7.x/commands/… .Załóżmy, że Twój projekt ma następującą strukturę:
Grupy to nic innego jak wiele poleceń, a grupy można zagnieżdżać. Możesz podzielić swoje grupy na moduły i zaimportować je do
init.py
pliku i dodać docli
grupy za pomocą add_command.Oto
init.py
przykład:import click from .commands.cloudflare import cloudflare @click.group() def cli(): pass cli.add_command(cloudflare)
Musisz zaimportować grupę cloudflare, która znajduje się w pliku cloudflare.py. Twój
commands/cloudflare.py
wyglądałby tak:import click @click.group() def cloudflare(): pass @cloudflare.command() def zone(): click.echo('This is the zone subcommand of the cloudflare command')
Następnie możesz uruchomić polecenie cloudflare w następujący sposób:
Te informacje nie są zbyt wyraźne w dokumentacji, ale jeśli spojrzysz na kod źródłowy, który jest bardzo dobrze skomentowany, możesz zobaczyć, jak można zagnieżdżać grupy.
źródło
@cloudflare.command()
zzone
funkcji, jeśli importujęzone
z innego miejsca?Szukam czegoś takiego w tej chwili, w twoim przypadku jest to proste ponieważ masz grupy w każdym z plików, możesz rozwiązać ten problem jak wyjaśniono w dokumentacji :
W
init.py
pliku:import click from command_cloudflare import cloudflare from command_uptimerobot import uptimerobot cli = click.CommandCollection(sources=[cloudflare, uptimerobot]) if __name__ == '__main__': cli()
Najlepszą częścią tego rozwiązania jest to, że jest całkowicie zgodne z pep8 i innymi lintersami, ponieważ nie musisz importować czegoś, czego nie używałbyś i nie musisz importować * z dowolnego miejsca.
źródło
cli
z init.py, ale prowadzi to do importu cyklicznego. Czy mógłbyś wyjaśnić, jak to zrobić?click.group
, który importujesz w CLI najwyższego poziomu.Zajęło mi to trochę czasu, aby to rozgryźć, ale pomyślałem, że umieszczę to tutaj, aby przypomnieć sobie, gdy zapomnę, jak to zrobić, i myślę, że częścią problemu jest to, że funkcja add_command jest wymieniona na stronie github kliknięcia, ale nie na stronie głównej strona z przykładami
najpierw stwórzmy początkowy plik Pythona o nazwie 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()
Następnie umieśćmy polecenia narzędzi w pliku o nazwie 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()
Następnie umieśćmy kilka poleceń kompilujących w pliku o nazwie 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()
uruchomienie root.py powinno nam teraz dać
Usage: root.py [OPTIONS] COMMAND [ARGS]... Demo Options: --help Show this message and exit. Commands: compile Commands related to compiling tools Tool related commands
uruchomienie "root.py compile" powinno nam dać
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
Zauważysz również, że możesz uruchomić cli_tools.py lub cli_compile.py bezpośrednio, jak również umieściłem tam główną instrukcję
źródło
Nie jestem ekspertem od kliknięć, ale powinno to działać, po prostu importując pliki do głównego. Przeniósłbym wszystkie polecenia do oddzielnych plików, a jeden główny plik importuje pozostałe. W ten sposób łatwiej jest kontrolować dokładną kolejność, jeśli jest to dla Ciebie ważne. Więc twój główny plik wyglądałby tak:
import commands_main import commands_cloudflare import commands_uptimerobot
źródło
edycja: właśnie zdałem sobie sprawę, że moja odpowiedź / komentarz to niewiele więcej niż powtórzenie tego, co oferują oficjalne dokumenty firmy Click w sekcji „Niestandardowe polecenia wielofunkcyjne”: https://click.palletsprojects.com/en/7.x/commands/#custom -multi-polecenia
Aby dodać do doskonałej, zaakceptowanej odpowiedzi @jdno, wymyśliłem funkcję pomocniczą, która automatycznie importuje i automatycznie dodaje moduły podkomendy, co znacznie skraca standard w moim
cli.py
:Struktura mojego projektu jest następująca:
Każdy plik podpoleceń wygląda mniej więcej tak:
import click @click.command() def foo(): """foo this is for foos!""" click.secho("FOO", fg="red", bg="white")
(na razie mam tylko jedną komendę na plik)
W programie
cli.py
napisałemadd_subcommand()
funkcję, która zapętla każdą ścieżkę pliku globowaną przez "podpolecenia / *. Py", a następnie wykonuje polecenie importu i dodawania.Oto, do czego uproszczono treść skryptu 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()
I to jest właśnie to
add_subcommands()
wygląda funkcja: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)
Nie wiem, jak solidne jest to, gdybym zaprojektował polecenie, które ma kilka poziomów zagnieżdżania i przełączania kontekstów. Ale wydaje się, że na razie działa dobrze :)
źródło