Generación de interfaces de línea de comandos (CLI) con Fire en Python

En este tutorial, veremos cómo crear una aplicación de interfaz de línea de comandos usando Python Fire.

Introducción

Una interfaz de línea de comandos (CLI) es una forma de interactuar con las computadoras mediante comandos de texto.

Muchas herramientas que no requieren GUI están escritas como herramientas/utilidades CLI. Aunque Python tiene el módulo argparse integrado, existen otras bibliotecas con una funcionalidad similar.

Estas bibliotecas pueden ayudarnos a escribir secuencias de comandos CLI, brindando servicios como opciones de análisis e indicadores para una funcionalidad CLI mucho más avanzada.

Este artículo analiza la biblioteca python fuego, escrita por Google Inc., una herramienta útil para crear CLI con un código mínimo.

Formulario general de aplicaciones CLI

Antes de comenzar con la biblioteca Fire, intentemos comprender los conceptos básicos de los programas de interfaz de línea de comandos en general. Según el programa y el comando, el patrón general de una CLI se puede resumir de la siguiente manera:

1
prompt command parameter1 parameter2 ... parameterN
  • prompt es una secuencia de caracteres que solicita al usuario que ingrese un comando
  • comando es el nombre del programa que el usuario está ejecutando (por ejemplo, ls)
  • parámetros son tokens opcionales que aumentan o modifican la salida del comando

Un programa CLI se ejecuta escribiendo el nombre del programa después de que aparezca el indicador, en este caso el símbolo $.

Aquí estamos usando el comando ls que devuelve una lista de nombres de archivo en un directorio, siendo el directorio actual el predeterminado:

1
2
3
$ ls
README.md
python

Puede modificar el comportamiento o la salida de un programa de línea de comandos al proporcionarle una lista de tokens o parámetros más conocidos como banderas. Probemos una bandera del comando ls:

1
2
3
$ ls -l
-rwxrwxrwx 1 pandeytapan pandeytapan  10 Sep 23 18:29 README.md
drwxrwxrwx 1 pandeytapan pandeytapan 512 Sep 23 18:29 python

Como puede ver, después de pasar el indicador -l, obtenemos información adicional para cada entrada, como el propietario, el grupo y el tamaño del archivo.

Las banderas que tienen un solo guión (-) se denominan opciones cortas, mientras que las que tienen dos guiones (--) se denominan opciones largas. Ambos tipos se pueden usar juntos en un solo comando, como en el siguiente ejemplo:

1
2
3
$ ls -l --time-style=full-iso
-rwxrwxrwx 1 pandeytapan pandeytapan  10 2020-09-23 18:29:25.501149000 +0530 README.md
drwxrwxrwx 1 pandeytapan pandeytapan 512 2020-09-23 18:29:25.506148600 +0530 python

La diferencia entre opciones cortas y largas:

  1. Las opciones cortas se pueden encadenar juntas
  • Si queremos usar las opciones cortas -l y -a simplemente escribimos -al
  1. Las opciones cortas se indican con un solo carácter, mientras que las opciones largas tienen un nombre completo separado por guiones y no se pueden encadenar.

El indicador --time-style funciona con el indicador -l y controla el formato de tiempo de visualización para una lista de directorios.

Una CLI proporciona una manera fácil para que el usuario configure y ejecute una aplicación desde la línea de comandos. La biblioteca Python Fire de Google facilita agregar un componente de procesamiento CLI a cualquier secuencia de comandos de Python existente.

Veamos cómo hacer una aplicación de línea de comandos usando Python Fire.

Instalación

Avancemos e instalemos la biblioteca usando pip:

1
$ pip install fire

Python Fire funciona en cualquier objeto de Python, es decir, funciones, clases, diccionarios, listas, etc. Tratemos de comprender el uso de la biblioteca Python Fire a través de algunos ejemplos.

Generación de una aplicación CLI con Python Fire

Hagamos un script, digamos, fire_cli.py y pongamos una función dentro de él:

1
2
3
def greet_mankind():
    """Greets you with Hello World"""
    return 'Hello World'

Al ejecutar este programa en el shell de Python, el resultado es:

1
2
3
4
>>> from fire_cli import greet_mankind
>>> greet_mankind()
'Hello World'
>>>

Podemos convertir fácilmente este script en una aplicación CLI usando Python Fire:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import fire

def greet_mankind():
    """
    Returns a textual message
    """
    return 'Hello World'

if __name__ == '__main__':
    fire.Fire()

Las llamadas fire.Fire() convierten el módulo, es decir, fire_cli.py en una aplicación Fire CLI. Además, ha expuesto la función greet_mankind() como comando, automáticamente.

Ahora podemos guardar y ejecutar el script anterior como CLI de la siguiente manera:

1
2
$ python fire_greet_mk_cli.py greet_mankind
Hello World

Como repaso, analicemos la llamada:

  • $ es el indicador
  • python es el intérprete de comandos
  • fire_cli.py es el módulo que contiene el comando CLI
  • saludar_humanidad es el comando

Pasar argumentos a un comando {#pasar argumentos a un comando}

Hagamos otra aplicación CLI que tome un nombre como parámetro y muestre un mensaje de saludo personalizado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import fire

def greetings(name):
    '''
    Returns a greeting message
    Parameters
    ----------
    name : string
        String that represents the addresses name 

    Returns
    -------
    string
        greeting message concatenated with name
    '''
    return 'Hello %s' % name

if __name__ == '__main__':
    fire.Fire()

Aquí, ahora tenemos una función que acepta una cadena - nombre. Python Fire recoge esto automáticamente y si proporcionamos un argumento después de la llamada saludos, vinculará esa entrada al parámetro nombre. También hemos agregado un comentario como una especie de documentación para el comando --help.

Así es como podemos ejecutar este comando desde la línea de comandos:

1
2
$ python fire_greet_cli.py greetings Robin
Hello Robin

Una aplicación Fire CLI puede usar indicadores --help para verificar la descripción del comando generado a partir de los documentos de Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
python fire_greet_cli.py greetings --help
NAME
    fire_greet_cli.py greetings - Returns a greeting message

SYNOPSIS
    fire_greet_cli.py greetings NAME

DESCRIPTION
    Returns a greetings message

POSITIONAL ARGUMENTS
    NAME
        String that represents the addresses name

NOTES
    You can also use flags syntax for POSITIONAL ARGUMENTS

Establecer una función como punto de entrada {#establecer una función como punto de entrada}

Con una ligera modificación, podemos controlar la exposición de la función saludos() a la línea de comandos y configurarla como el punto de entrada predeterminado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import fire

def greetings(name):
    '''
    Returns a greeting message 
    :param name: string argument
    :return: greeting message appended with name
    '''
    return 'Hello %s' % name

if __name__ == '__main__':
    fire.Fire(greetings)

Así es como ejecutaremos el comando ahora:

1
2
$ python fire_greet_cli.py Robin
Hello Robin

Así que esta vez ya no necesitamos llamar al comando ya que hemos definido saludos implícitamente como un punto de entrada usando Fuego(). Una cosa a tener en cuenta aquí es que con esta versión, solo podemos pasar un solo argumento:

1
2
3
4
5
$ python fire_greet_cli.py Robin Hood
ERROR: Could not consume arg: Hood
...
$ python fire_greet_cli.py Robin
Hello Robin

Análisis de argumentos

La biblioteca Fire también funciona con clases. Definamos una clase CustomSequence que genera y devuelve una lista de números entre start y end:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import fire

class CustomSequence:
    '''Class that generates a sequence of numbers'''
    def __init__(self, offset=1):
        '''
         Parameters
        ----------
        offset : int, optional
            Number controlling the difference between two generated values
        '''
        self.offset = offset

    def generate(self, start, stop):
        '''
        Generates the sequence of numbers

        Parameters
        ----------
        start : int
            Number that represents the elements lower bound
        stop : int
            Number that represents the elements upper bound

        Returns
        -------
        string
            a string that represents the generated sequence
        '''
        return ' '.join(str(item) for item in range(start, stop, self.offset))

if __name__ == '__main__':
    fire.Fire(CustomSequence)

Así es como generamos una secuencia usando esta utilidad de línea de comandos:

1
2
$ python fire_gen_cli.py generate 1 10
1 2 3 4 5 6 7 8 9

Usamos una clase en lugar de una función porque, a diferencia de las funciones, si queremos pasar un argumento al constructor, siempre debe representarse como un indicador de línea de comando con guiones dobles (por ejemplo, --offset=2).

Por lo tanto, nuestra aplicación CLI admite un argumento opcional --offset que se pasará al constructor de la clase. Esto modifica la salida controlando la diferencia entre dos valores generados consecutivos:

Aquí está la salida con un valor de compensación de 2:

1
2
$ python fire_gen_cli.py generate 1 10 --offset=2
1 3 5 7 9

Los argumentos del constructor siempre se pasan usando la sintaxis de bandera, mientras que los argumentos a otros métodos o funciones se pasan posicionalmente o por nombre:

1
2
3
4
5
6
$ python fire_gen_cli.py generate --start=10 --stop=20
10 11 12 13 14 15 16 17 18 19
$ python fire_gen_cli.py generate 10 20
10 11 12 13 14 15 16 17 18 19
$ python fire_gen_cli.py generate --start=10 --stop=20 --offset=2
10 12 14 16 18

Podemos verificar el uso del comando generar usando el indicador --help. Esto le dará la información de uso para la CLI:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ python fire_gen_cli.py generate --help
INFO: Showing help with the command 'fire_gen_cli.py generate -- --help'.

NAME
    fire_gen_cli.py generate - Generates the sequence of numbers

SYNOPSIS
    fire_gen_cli.py generate START STOP

DESCRIPTION
    Generates the sequence of numbers

POSITIONAL ARGUMENTS
    START
        Number that represents the first value for the sequence
    STOP
        Number that represents the ending value for the sequence

NOTES
    You can also use flags syntax for POSITIONAL ARGUMENTS

Usar --help con el módulo nos da su información de uso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ python fire_gen_cli.py  --help
INFO: Showing help with the command 'fire_gen_cli.py -- --help'.

NAME
    fire_gen_cli.py - Class that generates a sequence of numbers

SYNOPSIS
    fire_gen_cli.py <flags>

DESCRIPTION
    Class that generates a sequence of numbers

FLAGS
    --offset=OFFSET

Fuego Banderas

Fire CLI viene con muchas banderas integradas. Ya hemos visto --help, sin embargo, otra bandera útil es --interactive. El uso de esta bandera nos pone en modo REPL de Python, con el módulo ya definido.

Esto es bastante útil para probar comandos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ python fire_greet_cli.py -- --interactive
Fire is starting a Python REPL with the following objects:
Modules: fire
Objects: component, fire_greet_cli.py, greetings, result, trace

Python 3.7.8 (tags/v3.7.8:4b47a5b6ba, Jun 28 2020, 08:53:46) [MSC v.1916 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.16.1 -- An enhanced Interactive Python. Type '?' for help.


In [1]: greetings("Robin")
Out[1]: 'Hello Robin'

Tenga en cuenta que las banderas de incendios deben separarse de otras opciones con dos guiones (--), por lo que si queremos usar las banderas --help y --interactive en un comando, se vería así :

1
$ python fire_greet_cli.py -- --help --interactive

Conclusión

La biblioteca Python Fire de Google es una forma rápida y fácil de generar interfaces de línea de comandos (CLI) para casi cualquier objeto de Python.

En este artículo, hemos repasado cómo instalar Python Fire, así como generar interfaces de línea de comandos simples.