मैं अपनी क्लिक कमांड को कैसे विभाजित कर सकता हूं, प्रत्येक को उप-कमांड के एक सेट के साथ, कई फाइलों में?


87

मेरे पास एक बड़ा क्लिक एप्लिकेशन है जिसे मैंने विकसित किया है, लेकिन विभिन्न कमांडों / उप-कमलों के माध्यम से नेविगेट करना कठिन हो रहा है। मैं अपने आदेशों को अलग-अलग फ़ाइलों में कैसे व्यवस्थित करूं? क्या अलग-अलग वर्गों में कमांड और उनके उप-क्षेत्र को व्यवस्थित करना संभव है?

यहाँ एक उदाहरण दिया गया है कि मैं इसे कैसे अलग करना चाहूंगा:

इस में

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

जवाबों:


99

इसके CommandCollectionलिए उपयोग करने का नकारात्मक पक्ष यह है कि यह आपकी कमांड्स को मर्ज करता है और केवल कमांड समूहों के साथ काम करता है। add_commandइसी परिणाम को प्राप्त करने के लिए imho बेहतर विकल्प का उपयोग करना है।

मेरे पास निम्नलिखित पेड़ के साथ एक परियोजना है:

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

इस सेटअप के साथ, अपने आदेशों को चिंताओं से अलग करना बहुत आसान है, और उनके आस-पास अतिरिक्त कार्यक्षमता का निर्माण भी करते हैं जिनकी उन्हें आवश्यकता हो सकती है। इसने मुझे अब तक बहुत अच्छी सेवा दी है ...

संदर्भ: http://click.pocoo.org/6/quickstart/#nesting-commands


यदि वे अलग-अलग मॉड्यूल में हैं, तो सबकमांड के संदर्भ में कैसे पास करें?
विशाल

2
@vishal, दस्तावेज़ीकरण के इस भाग पर एक नज़र डालें: click.pocoo.org/6/commands/#nested-handling-and-contexts आप डेकोरेटर का उपयोग करके किसी भी कमांड के संदर्भ ऑब्जेक्ट को पास कर सकते हैं @click.pass_context। वैकल्पिक रूप से, ग्लोबल कॉनटेक्स्ट एक्सेस नाम की भी कोई चीज़ है : click.pocoo.org/6/advanced/#global-context-access
jdno

6
मैंने @jdno दिशानिर्देशों का उपयोग करते हुए एक MWE को संकलित किया। आप इसे यहां
Dror

मैं सभी समूह कमांड को कैसे फ्लैट कर सकता हूं? मेरा मतलब है, पहले स्तर पर सभी कमांड।
मिथिल

3
@ मिथिल यूज ए CommandCollection। ऑस्कर के जवाब का एक उदाहरण है, और क्लिक के प्रलेखन में एक बहुत अच्छा है: click.palletsprojects.com/en/7.x/commands/…
jdno

36

मान लीजिए कि आपकी परियोजना में निम्नलिखित संरचना है:

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

समूह कई कमांडों से अधिक कुछ नहीं हैं और समूहों को नेस्टेड किया जा सकता है। आप अपने समूहों को मॉड्यूल में अलग कर सकते हैं और उन्हें आप init.pyफ़ाइल पर आयात कर सकते हैं और उन्हें cliadd_command का उपयोग करके समूह में जोड़ सकते हैं ।

यहाँ एक init.pyउदाहरण है:

import click
from .commands.cloudflare import cloudflare


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


cli.add_command(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')

फिर आप इस तरह से क्लाउडफ्लेयर कमांड चला सकते हैं:

$ python init.py cloudflare zone

यह जानकारी दस्तावेज़ीकरण पर बहुत स्पष्ट नहीं है, लेकिन यदि आप स्रोत कोड को देखते हैं, जो बहुत अच्छी तरह से टिप्पणी करता है, तो आप देख सकते हैं कि समूहों को कैसे नेस्ट किया जा सकता है।


5
इस बात से सहमत। इतना न्यूनतम कि यह प्रलेखन का हिस्सा होना चाहिए। वास्तव में क्या मैं जटिल उपकरण बनाने के लिए देख रहा था! धन्यवाद 🙏
साइमन केपर

यह निश्चित रूप से बहुत अच्छा है लेकिन एक सवाल है: आपके उदाहरण को ध्यान में रखते हुए, अगर मुझे कहीं और से आयात करना है तो मुझे फ़ंक्शन @cloudflare.command()से हटा देना चाहिए ? zonezone
एरडिन एर

यह एक उत्कृष्ट जानकारी है जिसकी मुझे तलाश थी। कमांड समूहों के बीच अंतर करने के तरीके पर एक और अच्छा उदाहरण यहां पाया जा सकता है: github.com/dagster-io/dagster/tree/master/python_modules/…
थॉमस क्लिंगर

10

मैं इस समय ऐसा कुछ देख रहा हूं, आपके मामले में यह सरल है क्योंकि आपके पास प्रत्येक फाइल में समूह हैं, आप इस समस्या को हल कर सकते हैं जैसा कि प्रलेखन में बताया गया है :

में 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 और अन्य लिंटर के साथ पूरी तरह से अनुपालन है क्योंकि आपको कुछ ऐसा आयात करने की आवश्यकता नहीं है जिसका आप उपयोग नहीं करेंगे और आपको कहीं से भी * आयात करने की आवश्यकता नहीं है।


क्या आप कृपया बता सकते हैं कि उप-कमांड फाइलों में क्या डाला जाए? मुझे cliinit.py से मुख्य आयात करना है , लेकिन इससे परिपत्र आयात होता है। क्या आप बता सकते हैं कि यह कैसे करना है?
प्रचंड

@grundic यदि आपने अभी तक कोई समाधान नहीं निकाला है, तो मेरे उत्तर को देखें। यह आपको सही रास्ते पर ला सकता है।
12

1
@grundic मुझे उम्मीद है कि आप पहले से ही समझ गए होंगे, लेकिन आपकी उप कमांड फाइलों में आप सिर्फ एक नया निर्माण करते हैं, click.groupवह है जो आप शीर्ष सीएलआई में आयात करते हैं।
ऑस्कर डेविड अर्बेलेज़

5

मुझे यह पता लगाने में थोड़ा समय लगा, लेकिन मुझे लगा कि जब मैं भूल जाऊंगा कि मैं दोबारा कैसे करूं तो मुझे लगता है कि मुझे लगता है कि समस्या का एक हिस्सा यह है कि add_command फ़ंक्शन क्लिक के जीथब पृष्ठ पर उल्लिखित है, लेकिन मुख्य नहीं उदाहरण पृष्ठ

पहले root प्रारंभिक नामक एक प्रारंभिक अजगर फ़ाइल बनाने देता है

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 चला सकते हैं और साथ ही मैंने इसमें एक मुख्य वक्तव्य भी शामिल किया है


0

मैं एक क्लिक विशेषज्ञ नहीं हूं, लेकिन यह आपकी फ़ाइलों को मुख्य में आयात करके काम करना चाहिए। मैं सभी आदेशों को अलग-अलग फाइलों में ले जाऊंगा और एक मुख्य फाइल दूसरे लोगों को आयात करना होगा। इस तरह यह सटीक क्रम को नियंत्रित करना आसान है, अगर यह आपके लिए महत्वपूर्ण है। तो आपकी मुख्य फ़ाइल कुछ इस तरह दिखाई देगी:

import commands_main
import commands_cloudflare
import commands_uptimerobot

0

संपादित करें: बस महसूस किया कि मेरा उत्तर / टिप्पणी "कस्टम मल्टी कमांड्स" अनुभाग में क्लिक के आधिकारिक डॉक्स की पेशकश की तुलना में थोड़ा अधिक है: https://click.palletsprojects.com/en/7.x/commands/#custom -मल्टी-आदेशों

सिर्फ @jdno द्वारा उत्कृष्ट, स्वीकृत जवाब में जोड़ने के लिए, मैं एक सहायक फ़ंक्शन के साथ आया था जो ऑटो-आयात और ऑटो-जोड़ उप-मॉडल मॉड्यूल, जो मेरे में बॉयलरप्लेट पर बहुत कटौती करते हैं 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()फंक्शन लिखा है जो "सबकमांड / *। 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)

मुझे नहीं पता कि यह कितना मजबूत है अगर मैं एक कमांड डिजाइन करता हूं जिसमें घोंसले के शिकार और संदर्भ स्विचिंग के कई स्तर थे। लेकिन यह अभी के लिए ठीक काम करने लगता है :)

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.