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
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]"
)
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()
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()
Este es un spinner sencillo pero la herramienta tiene una gran cantidad como se aprecia en la Figura 5
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()
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
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()
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
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)
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
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