Ich habe eine große Klickanwendung, die ich entwickelt habe, aber das Navigieren durch die verschiedenen Befehle / Unterbefehle wird schwierig. Wie organisiere ich meine Befehle in separaten Dateien? Ist es möglich, Befehle und ihre Unterbefehle in separate Klassen zu organisieren?
Hier ist ein Beispiel, wie ich es trennen möchte:
drin
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
. Alternativ gibt es auch einen so genannten globalen Kontextzugriff : click.pocoo.org/6/advanced/#global-context-access .CommandCollection
. Oskars Antwort hat ein Beispiel, und es gibt ein wirklich schönes in der Dokumentation von click: click.palletsprojects.com/en/7.x/commands/… .Angenommen, Ihr Projekt hat die folgende Struktur:
Gruppen sind nichts anderes als mehrere Befehle und Gruppen können verschachtelt werden. Sie können Ihre Gruppen in Module aufteilen und in Ihre
init.py
Datei importieren undcli
mit dem Befehl add_command zur Gruppe hinzufügen.Hier ist ein
init.py
Beispiel:import click from .commands.cloudflare import cloudflare @click.group() def cli(): pass cli.add_command(cloudflare)
Sie müssen die Cloudflare-Gruppe importieren, die sich in der Datei cloudflare.py befindet. Ihr
commands/cloudflare.py
würde so aussehen:import click @click.group() def cloudflare(): pass @cloudflare.command() def zone(): click.echo('This is the zone subcommand of the cloudflare command')
Anschließend können Sie den Befehl cloudflare folgendermaßen ausführen:
Diese Informationen sind in der Dokumentation nicht sehr explizit. Wenn Sie sich jedoch den Quellcode ansehen, der sehr gut kommentiert ist, können Sie sehen, wie Gruppen verschachtelt werden können.
quelle
@cloudflare.command()
vonzone
Funktion , wenn ich importierenzone
von woanders?Ich suche im Moment nach so etwas. In Ihrem Fall ist es einfach, weil Sie Gruppen in jeder der Dateien haben. Sie können dieses Problem lösen, wie in der Dokumentation erläutert :
In der
init.py
Datei:import click from command_cloudflare import cloudflare from command_uptimerobot import uptimerobot cli = click.CommandCollection(sources=[cloudflare, uptimerobot]) if __name__ == '__main__': cli()
Das Beste an dieser Lösung ist, dass sie vollständig mit pep8 und anderen Lintern kompatibel ist, da Sie nichts importieren müssen, was Sie nicht verwenden würden, und nicht * von irgendwoher importieren müssen.
quelle
cli
aus init.py importieren, aber dies führt zu zirkulären Importen. Könnten Sie bitte erklären, wie es geht?click.group
, die Sie in die CLI der obersten Ebene importieren.Ich habe eine Weile gebraucht, um das herauszufinden, aber ich dachte, ich würde das hier einfügen, um mich daran zu erinnern, wenn ich vergesse, wie ich es wieder mache. Ich denke, ein Teil des Problems ist, dass die Funktion add_command auf der Github-Seite von click erwähnt wird, aber nicht auf der Hauptseite Beispielseite
Zuerst erstellen wir eine erste Python-Datei namens 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()
Als nächstes fügen wir einige Tools-Befehle in eine Datei mit dem Namen cli_tools.py ein
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()
Als nächstes fügen wir einige Kompilierungsbefehle in eine Datei mit dem Namen cli_compile.py ein
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()
Das Ausführen von root.py sollte uns jetzt geben
Usage: root.py [OPTIONS] COMMAND [ARGS]... Demo Options: --help Show this message and exit. Commands: compile Commands related to compiling tools Tool related commands
Das Ausführen von "root.py compile" sollte uns geben
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
Sie werden auch feststellen, dass Sie cli_tools.py oder cli_compile.py direkt ausführen können, und ich habe dort eine Hauptanweisung eingefügt
quelle
Ich bin kein Klick-Experte, aber es sollte funktionieren, indem Sie einfach Ihre Dateien in die Hauptdatei importieren. Ich würde alle Befehle in separate Dateien verschieben und eine Hauptdatei die anderen importieren lassen. Auf diese Weise ist es einfacher, die genaue Reihenfolge zu kontrollieren, falls dies für Sie wichtig ist. Ihre Hauptdatei würde also einfach so aussehen:
import commands_main import commands_cloudflare import commands_uptimerobot
quelle
Bearbeiten: Ich habe gerade festgestellt, dass meine Antwort / mein Kommentar kaum mehr als eine Wiederholung dessen ist, was die offiziellen Dokumente von Click im Abschnitt "Benutzerdefinierte Multi-Befehle" bieten: https://click.palletsprojects.com/en/7.x/commands/#custom -multi-Befehle
Um die ausgezeichnete, akzeptierte Antwort von @jdno zu ergänzen, habe ich eine Hilfsfunktion entwickelt, die Unterbefehlsmodule automatisch importiert und automatisch hinzufügt, wodurch die Boilerplate in meinem
cli.py
:Meine Projektstruktur ist folgende:
Jede Unterbefehlsdatei sieht ungefähr so aus:
import click @click.command() def foo(): """foo this is for foos!""" click.secho("FOO", fg="red", bg="white")
(Im Moment habe ich nur einen Unterbefehl pro Datei)
In
cli.py
habe ich eineadd_subcommand()
Funktion geschrieben, die jeden Dateipfad durchläuft, der durch "Unterbefehle / *. Py" gekennzeichnet ist, und dann den Befehl import und add ausführt.Der Text des cli.py-Skripts wird folgendermaßen vereinfacht:
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()
Und so
add_subcommands()
sieht die Funktion aus: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)
Ich weiß nicht, wie robust dies ist, wenn ich einen Befehl mit mehreren Ebenen der Verschachtelung und Kontextumschaltung entwerfen würde. Aber es scheint jetzt in Ordnung zu funktionieren :)
quelle