Llama a otro comando de clic desde un comando de clic

Quiero usar algunas funciones útiles como comandos. Para eso estoy probando la biblioteca de click . click.command mis tres funciones originales y luego las click.command como 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 esta manera puedo generar los tres comandos add_name , add_surname y add_name_and_surname usando un archivo setup.py y pip install --editable . Entonces soy capaz de canalizar:

 $ echo "original content" | add_name | add_surname original content added name added surname 

Sin embargo, hay un pequeño problema que debo resolver al componer con diferentes comandos de clic como funciones:

 $echo "original content" | add_name_and_surname Usage: add_name_and_surname [OPTIONS] [CONTENT] Error: Got unexpected extra arguments (riginalcontent ) 

No tengo ni idea de por qué no funciona, necesito este comando add_name_and_surname para llamar a add_name y add_surname no como comando sino como funciones, de lo contrario, add_surname mi propósito original de usar funciones como funciones y comandos de la biblioteca.

Debido a los decoradores de clic, las funciones ya no se pueden llamar simplemente especificando los argumentos. La clase de contexto es tu amigo aquí, específicamente:

  1. Context.invoke (): invoca otro comando con los argumentos que proporciona
  2. Context.forward (): rellena los argumentos del comando actual

Entonces, su código para add_name_and_surname debería verse así:

 @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 

Referencia: http://click.pocoo.org/6/advanced/#invoking-other-commands

Cuando llama a add_name() y a add_surname() directamente desde otra función, en realidad llama a las versiones decoradas de ellas, por lo que es posible que los argumentos esperados no sean como los definió (consulte las respuestas a Cómo eliminar a los decoradores de una función en python detalles sobre por qué).

Le sugeriría que modifique su implementación para mantener las funciones originales sin decorar y crear envoltorios específicos para cada clic, por ejemplo:

 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) 

Luego puede llamar directamente a estas funciones o invocarlas a través de un script de envoltorio de CLI creado por setup.py.

Esto puede parecer redundante, pero en realidad es probablemente la forma correcta de hacerlo: una función representa su lógica de negocios, la otra (el comando de clic) es un “controlador” que expone esta lógica a través de la línea de comandos (podría haber, por el bien de ejemplo, también una función que expone la misma lógica a través de un servicio web, por ejemplo).

De hecho, incluso recomendaría colocarlos en módulos de Python separados: su lógica “central” y una implementación específica de clic que podría reemplazarse por cualquier otra interfaz si fuera necesario.