Her biri bir dizi alt komut içeren Click komutlarımı birden çok dosyaya nasıl bölebilirim?


87

Geliştirdiğim büyük bir tıklama uygulamam var, ancak farklı komutlar / alt komutlar arasında gezinmek zorlaşıyor. Komutlarımı ayrı dosyalar halinde nasıl düzenlerim? Komutları ve alt komutlarını ayrı sınıflar halinde düzenlemek mümkün müdür?

İşte onu nasıl ayırmak istediğime dair bir örnek:

içinde

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

Yanıtlar:


99

Bunun CommandCollectioniçin kullanmanın dezavantajı, komutlarınızı birleştirmesi ve yalnızca komut gruplarıyla çalışmasıdır. İmho daha iyi bir alternatif, add_commandaynı sonucu elde etmek için kullanmaktır .

Aşağıdaki ağaçla bir projem var:

cli/
├── __init__.py
├── cli.py
├── group1
│   ├── __init__.py
│   ├── commands.py
└── group2
    ├── __init__.py
    └── commands.py

Her alt komutun kendi modülü vardır, bu da çok daha fazla yardımcı sınıf ve dosya içeren karmaşık uygulamaları bile yönetmeyi inanılmaz derecede kolaylaştırır. Her modülde, commands.pydosya @clickek açıklamaları içerir . Örnek group2/commands.py:

import click


@click.command()
def version():
    """Display the current version."""
    click.echo(_read_version())

Gerekirse, modülde kolayca daha fazla sınıf oluşturabilir importve bunları burada kullanabilirsiniz, böylece CLI'nize Python'un sınıflarının ve modüllerinin tam gücünü verebilirsiniz.

My cli.py, tüm CLI için giriş noktasıdır:

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)

Bu kurulumla, komutlarınızı endişelere göre ayırmak ve ayrıca ihtiyaç duyabilecekleri ek işlevler etrafında oluşturmak çok kolaydır. Şimdiye kadar bana çok iyi hizmet etti ...

Referans: http://click.pocoo.org/6/quickstart/#nesting-commands


ayrı modüllerdeyse, bağlamı alt komuta nasıl geçirebilirim?
vishal

2
@vishal, belgelerin bu bölümüne bir göz atın: click.pocoo.org/6/commands/#nested-handling-and-contexts İçerik nesnesini dekoratör kullanarak herhangi bir komuta aktarabilirsiniz @click.pass_context. Alternatif olarak, Küresel Bağlam Erişimi adı verilen bir şey de vardır : click.pocoo.org/6/advanced/#global-context-access .
jdno

6
@Jdno yönergelerini kullanarak bir MWE derledim. Burada
Dror

Tüm grup komutlarını nasıl düzleştirebilirim? Demek istediğim, birinci seviyedeki tüm komutlar.
Mithril

3
@Mithril Kullanın a CommandCollection. Oscar'ın cevabının bir örneği var ve tıklamanın belgelerinde gerçekten güzel bir tane var: click.palletsprojects.com/en/7.x/commands/… .
jdno

36

Projenizin aşağıdaki yapıya sahip olduğunu varsayalım:

project/
├── __init__.py
├── init.py
└── commands
    ├── __init__.py
    └── cloudflare.py

Gruplar birden fazla komuttan başka bir şey değildir ve gruplar iç içe olabilir. Gruplarınızı modüllere ayırabilir ve onları init.pydosyanıza aktarabilir clive add_command'ı kullanarak gruba ekleyebilirsiniz .

İşte bir init.pyörnek:

import click
from .commands.cloudflare import cloudflare


@click.group()
def cli():
    pass


cli.add_command(cloudflare)

Cloudflare.py dosyasının içinde bulunan cloudflare grubunu içe aktarmanız gerekir. Sizin commands/cloudflare.pyşu şekilde görünecektir:

import click


@click.group()
def cloudflare():
    pass


@cloudflare.command()
def zone():
    click.echo('This is the zone subcommand of the cloudflare command')

Ardından cloudflare komutunu şu şekilde çalıştırabilirsiniz:

$ python init.py cloudflare zone

Bu bilgiler dokümantasyonda çok açık değildir, ancak çok iyi yorumlanmış olan kaynak koduna bakarsanız, grupların nasıl iç içe geçebileceğini görebilirsiniz.


5
Katılıyorum. O kadar minimum ki belgelerin bir parçası olmalıdır. Karmaşık araçlar oluşturmak için tam olarak aradığım şeydi! Teşekkürler 🙏!
Simon Kemper

Tabii büyük ama bir sorum var: senin örneğini, ben kaldırmalısınız düşünüldüğünde @cloudflare.command()gelen zoneişlevi eğer ben içe zonebaşka yerde mi?
Erdin Eray

Bu, aradığım mükemmel bir bilgi. Komut grupları arasında nasıl ayrım yapılacağına dair bir başka güzel örnek burada bulunabilir: github.com/dagster-io/dagster/tree/master/python_modules/…
Thomas Klinger

10

Şu anda buna benzer bir şey arıyorum, sizin durumunuzda basit çünkü dosyaların her birinde gruplarınız var, bu sorunu dokümantasyonda açıklandığı gibi çözebilirsiniz :

In init.pydosyası:

import click

from command_cloudflare import cloudflare
from command_uptimerobot import uptimerobot

cli = click.CommandCollection(sources=[cloudflare, uptimerobot])

if __name__ == '__main__':
    cli()

Bu çözümün en iyi yanı, pep8 ve diğer linterlerle tamamen uyumlu olmasıdır çünkü kullanmayacağınız bir şeyi içe aktarmanıza ve herhangi bir yerden * içeri aktarmanıza gerek yoktur.


Lütfen alt komut dosyalarına ne koyacağınızı söyler misiniz? cliMain'i init.py'den içe aktarmam gerekiyor, ancak bu döngüsel içe aktarmalara yol açıyor. Nasıl yapılacağını açıklar mısınız lütfen?
grundic

@grundic Henüz bir çözüm bulamadıysanız cevabıma bakın. Sizi doğru yola sokabilir.
jdno

1
@grundic Umarım zaten anlamışsınızdır, ancak alt komut dosyalarınızda sadece yeni bir tane yaratırsınız click.group, bu üst seviye CLI'de içe aktardığınızdır.
Oscar David Arbeláez

5

Bunu anlamam biraz zaman aldı, ancak bunu nasıl yapacağımı unuttuğumda kendime hatırlatmak için buraya koyacağımı düşündüm. örnekler sayfası

önce root.py adında bir ilk python dosyası oluşturalım

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()

Sonra, cli_tools.py adlı bir dosyaya bazı araç komutları koyalım.

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()

Sonra, cli_compile.py adlı bir dosyaya bazı derleme komutları koyalım.

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 çalıştıran şimdi bize

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" çalıştırmak bize

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

Ayrıca cli_tools.py veya cli_compile.py'yi doğrudan çalıştırabileceğinizi ve oraya bir ana ifade eklediğimi fark edeceksiniz.


0

Bir tıklama uzmanı değilim, ancak dosyalarınızı ana dosyaya aktararak çalışmalı. Tüm komutları ayrı dosyalarda taşır ve diğerlerini içe aktaran bir ana dosyam olur. Bu şekilde, sizin için önemli olması durumunda tam sırayı kontrol etmek daha kolaydır. Yani ana dosyanız şöyle görünecektir:

import commands_main
import commands_cloudflare
import commands_uptimerobot

0

düzenleme: cevabımın / yorumumun, Click'in resmi belgelerinin "Özel Çoklu Komutlar" bölümünde sunduklarının biraz daha fazlası olduğunu fark ettim: https://click.palletsprojects.com/en/7.x/commands/#custom -multi-komutlar

Sadece @jdno tarafından kabul edilen mükemmel yanıta eklemek için, alt komut modüllerini otomatik olarak içe aktaran ve otomatik olarak ekleyen yardımcı bir işlev buldum, bu da benim cli.py:

Proje yapım şu:

projectroot/
    __init__.py
    console/
    │
    ├── cli.py
    └── subcommands
       ├── bar.py
       ├── foo.py
       └── hello.py

Her bir alt komut dosyası şuna benzer:

import click

@click.command()
def foo():
    """foo this is for foos!"""
    click.secho("FOO", fg="red", bg="white")

(şimdilik, dosya başına sadece bir alt komutum var)

İçinde cli.py, add_subcommand()"subcommands / *. Py" tarafından globallenen her dosya yolunda döngü yapan ve sonra içe aktar ve ekle komutunu yapan bir işlev yazdım .

Cli.py betiğinin gövdesi şu şekilde basitleştirilmiştir:

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()

Ve add_subcommands()fonksiyon şu şekilde görünür:


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)

Birkaç seviyede iç içe geçme ve bağlam değiştirme içeren bir komut tasarlayacak olsaydım bunun ne kadar sağlam olacağını bilmiyorum. Ama şimdilik iyi çalışıyor gibi görünüyor :)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.