Python no es un solo un lenguaje muy utilizado para trabajar en ciencia de datos, machine learning y desarrollo web también es utilizado para hacer scripts que automatizan algunas tareas de hecho según el survey de python 2020 es la cuarta razón por la que se usa el lenguaje (recuerdo en una charla llamarlo un lenguaje multi-genial ) por ello en este post nos centraremos en hacer que estos scripts se vean geniales en consola, es de aclarar que estas automatizaciones terminan tomando más tiempo de lo que deberían ver Figura 1 y haciendo que la tarea original se atrase, pero bueno todo sea por el aprendizaje

Figura 1: Tiempo que toma automatizar una tarea Tomado de https://xkcd.com/1319/

Hablaremos de tres herramientas/librerías y mostraremos ejemplos de cada una ellas, donde cada una nos aporta un componente visual diferente para que no solo la consola luzca genial si no que además facilite la interacción con estos script sin más empecemos

Rich

Rich es indispensable a la hora de trabajar con scripts que interactúan por consola, nos permite hacer textos no solo coloridos si no también renderizar tablas, emojis, barras de progreso y muchas opciones mas lo mas genial es que es compatible tanto con windows, linux y macOS, pero vayamos al código, primero necesitamos instalarla, la instalación es muy fácil usando poetry:

poetry add rich

vamos a mostrar algunos ejemplos no me enfocare en explicar linea por linea del código, esto podría hacerlo en otro post en donde se hable de cada herramienta por aparte, ya que cada herramienta tiene una gran cantidad de cualidades

Ejemplo # 1

Una salida por consola donde mostramos como podemos imprimir emojis, imprimir textos de colores pero lo más genial podemos renderizar markdown

from rich.console import Console
from rich.markdown import Markdown

console = Console()


def print_text_basic():
    with open("README.md") as readme:
        markdown = Markdown(readme.read())

    console.print(markdown)
    console.print(":smiley:")
    console.print(
        "This a [b dark_red]text example [/b dark_red][b dark_goldenrod]with console  [/b dark_goldenrod][i][u light_sea_green] output[/u light_sea_green][/i]"
    )
Figura 2 : Resultado la salida por consola de rich
# Welcome to the console output example
## with Rich you can
- Render markdown
- print texts with beautiful formats
- print emojis
Archivo README.md

Ejemplo # 2

Esta opción es bastante genial y yo en las personalizaciones que he realizado la uso bastante, consiste en renderizar una tabla la cual permite con código muy sencillo pintar en consola los datos contenidos en ella

from rich.table import Table
from rich.console import Console

console = Console()



users = [
    {"name": "andres", "age": 24},
    {"name": "diego", "age": 50},
    {"name": "daniela", "age": 34},
    {"name": "andrea", "age": 19},
]


def print_average_age_user():
    console.print(Markdown("# Average users"))
    table = Table(show_header=True, header_style="bold steel_blue1")
    table.add_column("# Users", style="dim", width=12, justify="center")
    table.add_column("Average Age", style="dim", width=12, justify="center")
    ages = 0
    for user in users:
        ages += user["age"]
    table.add_row(f"[bold] {len(users)} [/bold]", f" [bold] {ages/len(users)} [/bold]")

    console.print(table)

    console.print(table)
print_average_age_user()
Figura 3 : Resultado salida por consola del ejemplo de imprimir tabla

Ejemplo # 3

Este es un ejemplo de un spinner de carga se utiliza cuando tienes un proceso que puede tardar bastante tiempo, por ejemplo en el caso de hacer scraping yo lo utilizo para cuando se trabaja con bastantes urls un ejemplo algo sencillo seria el siguiente

import requests
from rich.table import Table
from rich.console import Console
from rich.markdown import Markdown

console = Console()

def print_status_example():
    sites_scraping = [
        "https://jairoandres.com",
        "https://pypi.org/",
        "https://www.python.org/",
        "https://google.com",
    ]
    with console.status("[bold green]Working on scraping...") as status:
        for site_scraping in sites_scraping:
            response = requests.get(site_scraping)
            if response.status_code == 200:
                console.print(f" {site_scraping} [green]complete[/green]")



print_status_example()
Figura 4: Resultado de spinner mientras termina el proceso

Este es un spinner sencillo pero la herramienta tiene una gran cantidad como se aprecia en la Figura 5

Figura 5: Spinners disponibles para utilizarlo en consola

Esta herramienta es super interesante como vieron además se puede agregar colores a los texto, tiene una gama disponible  se utilizan f string sin problema

Click

Click es una herramienta enfocada a la interacción del usuario con el script, es un kit completísimo que además con poco código y muy  fácil de entender permite capturar banderas, variables y comandos por medio de la consola, para los que desarrollan estos tipos de scripts sabrán el valor de los mensajes de help, click vuelve esto algo muy sencillo para instalar la librería es muy fácil utilizando poetry

poetry add click

Ejemplo #1

Una de las opciones que más me gusta de click es la carga automática de archivos gestionando esta parte por mi y abriendo un file a partir de una ubicación

import json
import click


@click.command()
@click.option(
    "-f", "--file", type=click.File("rb"), required=True, help="json file with users"
)
@click.option("--position", default=1, help="position of user ouput")
def main(file, position: int):
    users = json.load(file)
    if position < len(users):
        print(json.dumps(users[position], indent=3))


if __name__ == "__main__":
    main()
[
  { "name": "andres", "age": 24 },
  { "name": "diego", "age": 50 },
  { "name": "daniela", "age": 34 },
  { "name": "andrea", "age": 19 }
]
Archivo users.json

en la Figura 6 se puede apreciar cómo genera documentación automática sobre los parámetros cuales son obligatorios y cuáles opcionales además algunos parámetros tienen varias formas de pasar por ejemplo --f --file

Figura 6 : Documentación automática de los parámetros que recibe el script utilizando el comando --help
poetry run python example.py -f users.json --position 1
Ejecución del comando pasando los parámetros
{
   "name": "diego",
   "age": 50
}
Salida en consola

Ejemplo # 2

Una validación genial que tiene click es con relación a los folders, cuando necesitamos pasar directorios o ubicaciones existen algunas validaciones que pueden ser tediosas, en el siguiente ejemplo veremos cómo de estas validaciones se encarga click

import json
from pathlib import Path
import click
from rich.console import Console

console = Console()
@click.command()
@click.option(
    "-m",
    "--module",
    type=click.Path(
        exists=True,
        file_okay=False,
    ),
    help="path to search for information",
    multiple=True,
)
def search_files_in_folder(module):
    for path in module:
        files_dir = Path(path)
        for file_dir in files_dir.iterdir():
            if file_dir.is_file():
                console.print(f" is file :smiley:  [bold green] {file_dir}")


if __name__ == "__main__":
    search_files_in_folder()
poetry run python example.py -m data/ -m colors/
Ejecución del comando del script
Figura 7: Resultado de ejecutar el script satisfactoriamente

si pasamos parámetros de folder que no existen la herramienta tiene la capacidad automáticamente de validar estos casos, podemos verlo revisando el codigo a continuacion

@click.option(
    "-m",
    "--module",
    type=click.Path(
        exists=True,
        file_okay=False,
    ),
    help="path to search for information",
    multiple=True,
)

en los parámetros de click.Path() estamos garantizando exists a true eso quiere decir que el folder debe existir y además file_okay a false por lo cual si se pasa un parámetro de un archivo y no un folder genera error, así que en las siguientes figuras veremos unos intentos de pasar un folder que no existe y pasar un archivo como parámetro

Figura 8: Validación de folder que no existe automáticamente
Figura 9: Validación de parámetro que no es detectado como folder

Esta herramienta permite realizar una cantidad de validaciones de entrada por consola es una opción al módulo nativo argparse  y si pudieron apreciar además lo he conectado con rich librería explicada anteriormente, con estas dos herramientas ya tendríamos la interacción relacionada con la entrada de datos y la interacción relacionada con una salida de resultados "hermosa" tal como se dice en la documentación de rich

TyPer

Es una librería super completa para la creación de apps en CLI, además está basada en type hints lo que agrega una característica no solo de autocompletado en desarrollo si no también en interacción con el usuario como ya mostraremos en un ejemplo, esta librería es creada por el mismo autor de FastAPI  

Para instalarlo es muy sencillo, eso si al usar type hints requiere python 3.6 o superior usando poetry:

poetry add typer

la mayor ventaja es lo poco invasivo de la herramienta ya que no tiene esa cantidad de decorators que tienen las herramientas mostradas anteriormente, en el siguiente ejemplo podemos verlo

from pathlib import Path
from typing import List
import typer
from rich.console import Console


console = Console()


def search_files_in_folder(
    module: List[Path] = typer.Option(
        ..., exists=True, file_okay=False, dir_okay=True, resolve_path=True
    )
):
    for path in module:
        files_dir = Path(path)
        for file_dir in files_dir.iterdir():
            if file_dir.is_file():
                console.print(f" is file :smiley:  [bold green] {file_dir}")


if __name__ == "__main__":
    typer.run(search_files_in_folder)

Figura 10 : Resultado de ejecutar el script con Typer satisfactoriamente

Este código hace exactamente lo mismo que el ejemplo de la herramienta click anterior pero el código es mucho más limpio (internamente typer usa click) las validaciones las realiza en el código  que se explica a continuación

module: List[Path] = typer.Option(
        ..., exists=True, file_okay=False, dir_okay=True, resolve_path=True
    )

en este caso en esta porcion de codigo se le dice a typer que recibe una lista de parámetros module que son obligatorios (mínimo uno)  debe ser un path folder no un archivo y que además debe existir, al ejecutar un folder que no existe me genera la excepción de la Figura 11

Figura 11 : Excepción generada al pasar como parámetro un folder que no existe

TyperCLI

Es una librería complementaria de typer que facilita la ejecución de estos scripts para agregar una funcionalidad adicional, autocompletado en consola para instalarlo pueden ir a la documentación oficial  al instalarlo se puede ejecutar el script del ejemplo anterior

typer example_typer.py run

al presionar tab automáticamente completa el parámetro option que es --module esto permite crear un script que tiene una gran experiencia de usuario y además permite entender cómo funciona el script mucho más fácil, typer es una herramienta con un entorno gigantesco que daría para un post individual en este quize hacer una muestra sencilla

Conclusión

Cuando se desarrollan scripts que inicialmente automatizan cosas aburridas en muchas ocasiones estos scripts se publican en algún repositorio y son usados por diferentes personas, es conveniente por ello agregar una mayor usabilidad para que la interacción sea bastante sencilla, las librerías mostradas anteriormente permiten lograr tener estas consolas intuitivas sin entrar en grandes complejidades ya que crean capas superiores en donde el programador interactúa con clases, métodos o decoradores esto permite con gran facilidad mejorar el aspecto visual de los scripts dando un valor agregado