web-dev-qa-db-fra.com

Appeler une autre commande de clic à partir d'une commande de clic

Je veux utiliser des fonctions utiles comme commandes. Pour cela, je teste la bibliothèque click. J'ai défini mes trois fonctions originales puis décoré comme click.command:

import click
import os, sys

@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=True)
def add_name(content, to_stdout=False):
    if not content:
        content = ''.join(sys.stdin.readlines())
    result = content + "\n\tadded name"
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result


@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=True)
def add_surname(content, to_stdout=False):
    if not content:
        content = ''.join(sys.stdin.readlines())
    result = content + "\n\tadded surname"
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result

@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=False)
def add_name_and_surname(content, to_stdout=False):
    result = add_surname(add_name(content))
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result

De cette façon, je suis capable de générer les trois commandes add_name, add_surname et add_name_and_surname utilisant un setup.py fichier et pip install --editable . Alors je suis capable de pipe:

$ echo "original content" | add_name | add_surname 
original content

    added name
    added surname

Cependant, il y a un léger problème que je dois résoudre, lors de la composition avec différentes commandes de clic en tant que fonctions:

$echo "original content" | add_name_and_surname 
Usage: add_name_and_surname [OPTIONS] [CONTENT]

Error: Got unexpected extra arguments (r i g i n a l   c o n t e n t 
)

Je ne sais pas pourquoi cela ne fonctionne pas, j'ai besoin de ce add_name_and_surname commande pour appeler add_name et add_surname non pas comme une commande mais comme des fonctions, sinon cela va à l'encontre de mon objectif initial d'utiliser les fonctions à la fois comme fonctions et commandes de bibliothèque.

16
kaligne

En raison des décorateurs de clic, les fonctions ne peuvent plus être appelées simplement en spécifiant les arguments. La classe Context est votre amie ici, en particulier:

  1. Context.invoke () - invoque une autre commande avec les arguments que vous fournissez
  2. Context.forward () - remplit les arguments de la commande courante

Ainsi, votre code pour add_name_and_surname devrait ressembler à:

@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=False)
@click.pass_context
def add_name_and_surname(ctx, content, to_stdout=False):
    result = ctx.invoke(add_surname, content=ctx.forward(add_name))
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result

Référence: http://click.pocoo.org/6/advanced/#invoking-other-commands

18
Vikas Tikoo

Lorsque vous appelez add_name() et add_surname() directement à partir d'une autre fonction, vous en appelez en fait les versions décorées afin que les arguments attendus ne soient pas tels que vous les avez définis (voir les réponses à Comment retirer les décorateurs d'une fonction en python pour plus de détails sur pourquoi).

Je suggérerais de modifier votre implémentation afin de ne pas décorer les fonctions d'origine et de créer des wrappers spécifiques aux clics, par exemple:

def add_name(content, to_stdout=False):
    if not content:
        content = ''.join(sys.stdin.readlines())
    result = content + "\n\tadded name"
    if to_stdout is True:
        sys.stdout.writelines(result)
    return result

@click.command()
@click.argument('content', required=False)
@click.option('--to_stdout', default=True)
def add_name_command(content, to_stdout=False):
    return add_name(content, to_stdout)

Vous pouvez ensuite appeler ces fonctions directement ou les appeler via un script wrapper CLI créé par setup.py.

Cela peut sembler redondant, mais en fait, c'est probablement la bonne façon de le faire: une fonction représente votre logique métier, l'autre (la commande click) est un "contrôleur" exposant cette logique via la ligne de commande (il pourrait y avoir, pour le plaisir de exemple, également une fonction exposant la même logique via un service Web par exemple).

En fait, je conseillerais même de les mettre dans des modules séparés Python - Votre logique "de base" et une implémentation spécifique au clic qui pourrait être remplacée pour toute autre interface si nécessaire.

24
shevron