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