3 puntos por GN⁺ 2023-12-10 | 1 comentarios | Compartir por WhatsApp
  • Gooey convierte aplicaciones de consola en Python 3 en aplicaciones GUI fáciles de usar para usuarios finales, y funciona agregando una sola línea de decorador a una función que contiene declaraciones de argparse
  • La instalación básica se puede hacer con pip install Gooey; al usar el decorador @Gooey junto con ArgumentParser, Gooey lee los argumentos y construye una UI basada en componentes WX
  • Cuando la selección predeterminada de widgets no alcanza, se puede usar GooeyParser como reemplazo de ArgumentParser y especificar el argumento widget para elegir widgets personalizados como FileChooser, DateChooser, PasswordField o Slider
  • El público objetivo no son tanto las herramientas para desarrolladores que manejan directamente la CLI, sino scripts de oficina de tipo “ejecutar y listo” o herramientas para no programadores; admite modos de ejecución avanzado/básico/sin configuración, menús, visualización de progreso, internacionalización, cambio de íconos y empaquetado
  • La validación dinámica y los eventos de ciclo de vida están marcados como funciones experimentales; actualmente no admite optparse y asume código basado en ArgumentParser con suscripción opcional a eventos

Cómo convertir un programa de consola en GUI

  • Gooey convierte aplicaciones de consola en aplicaciones GUI fáciles de manejar para usuarios finales
  • El uso principal consiste en agregar el decorador @Gooey a una función que contiene declaraciones de argparse
from gooey import Gooey

@Gooey
def main():
  parser = ArgumentParser(...)

# rest of code
  • El estilo y el comportamiento se pueden ajustar mediante argumentos del decorador
    • advanced: si se muestra la pantalla de configuración avanzada
    • language: selección de traducción basada en JSON
    • auto_start: omite la pantalla de configuración y ejecuta de inmediato
    • target: especifica el comando que se ejecutará como subproceso
    • program_name, program_description: nombre y descripción que se muestran en la GUI
    • default_size: tamaño inicial de la GUI
    • dump_build_config, load_build_config: guardar/cargar la configuración de build de Gooey en JSON
    • monospace_display: usar una fuente monoespaciada en la pantalla de salida

Instalación y ejemplos

  • La forma más sencilla de instalarlo es con pip
pip install Gooey
  • También se puede clonar el repositorio y ejecutar setup.py para instalarlo
git clone https://github.com/chriskiehl/Gooey.git
python setup.py install
  • Los scripts de ejemplo preparados se pueden obtener en el repositorio Gooey Examples
  • El repositorio de ejemplos se ofrece para recorrer distintos layouts, widgets y funciones de Gooey

Por qué hace falta

  • Gooey fue creado para reducir el problema de mostrar un prompt de comandos a usuarios que no están familiarizados con él
  • Para que un programa haga una o más tareas, necesita recibir opciones; antes había que crear una GUI manualmente o explicar cómo pasar argumentos a una aplicación de consola
  • Gooey permite crear programas configurables de una manera familiar para los desarrolladores, pero ofreciendo la visualización y la interacción mediante una GUI

Usuarios adecuados y casos no adecuados

  • Gooey puede no ser adecuado para utilidades destinadas a uno mismo o a otros programadores, ni para herramientas que generan resultados para canalizarlos hacia otras aplicaciones de consola
  • Es adecuado para scripts de ejecución de oficina, herramientas que mueven datos del punto A al B y programas dirigidos a no programadores
  • El objetivo es crear aplicaciones complejas obteniendo la parte de GUI sin costo adicional

Funcionamiento interno y mapeo de argparse

  • Gooey parsea en tiempo de ejecución la referencia a ArgumentParser dentro del script de Python
  • Actualmente no admite optparse
  • A los argumentos extraídos se les asigna un component type según la action proporcionada, y se usan para construir la GUI
  • ArgumentParser._actions se mapea de forma predeterminada a los siguientes componentes WX
    • store: TextCtrl
    • store_const, store_true, store_False, version: CheckBox
    • append: TextCtrl
    • count: DropDown
    • Mutually Exclusive Group: RadioGroup
    • choice: DropDown

GooeyParser y widgets personalizados

  • Si la selección predeterminada de widgets no es suficiente, se puede usar GooeyParser, un reemplazo directo de ArgumentParser
  • GooeyParser ofrece el argumento de palabra clave adicional widget, con el que se puede especificar el nombre del componente que se mostrará
from gooey import Gooey, GooeyParser

@Gooey
def main():
  parser = GooeyParser(description="My Cool GUI Program!")
  parser.add_argument('Filename', widget="FileChooser")
  parser.add_argument('Date', widget="DateChooser")
  • Entre los widgets personalizados admitidos se incluyen:
    • DirChooser, FileChooser, MultiFileChooser
    • FileSaver, MultiFileSaver
    • DateChooser, TimeChooser
    • PasswordField
    • Listbox
    • BlockCheckbox
    • ColourChooser
    • FilterableDropdown
    • IntegerField, DecimalField
    • Slider
  • Los valores que DateChooser y TimeChooser pasan a la aplicación siempre están en ISO format, aunque en partes de la GUI puedan verse valores localizados según la configuración del usuario final
  • BlockCheckbox mueve el bloque de texto a una posición normal cuando hay textos de ayuda largos, para que se vea mejor que un checkbox inline predeterminado, y muestra un block_label corto junto al control

Internacionalización

  • Gooey selecciona el idioma de visualización mediante el argumento language del decorador
@Gooey(language='russian')
def main():
    ...
  • Todos los textos del programa se almacenan en archivos JSON externos
  • Para agregar soporte para un idioma nuevo, se añaden pares clave/valor en el directorio gooey/languages/
  • Actualmente incluye más de 18 traducciones de forma predeterminada

Configuración global y construcción de la UI

  • La apariencia y el comportamiento generales de Gooey se ajustan con argumentos del decorador
  • Entre las principales opciones se incluyen:
    • encoding: codificación de caracteres para mostrar; el valor predeterminado es utf-8
    • advanced: elegir entre pantalla de configuración completa o simplificada
    • auto_start: ejecutar de inmediato sin configuración
    • target: especifica cómo Gooey debe volver a ejecutarse a sí mismo
    • suppress_gooey_flag: evita inyectar parámetros CLI adicionales al usar un target personalizado
    • fullscreen: iniciar en pantalla completa
    • image_dir: directorio de imágenes/íconos personalizados
    • language_dir: directorio de archivos de idioma personalizados
    • disable_stop_button, show_stop_warning: control del botón de detener y la advertencia durante la ejecución
    • show_success_modal, show_failure_modal: si se muestran modales de resumen de éxito/fallo
    • run_validators: si se ejecuta la validación antes de invocar el programa
    • progress_regex, progress_expr: parseo y transformación del texto de progreso
    • navigation: TABBED o SIDEBAR
    • body_bg_color, header_bg_color, footer_bg_color, sidebar_bg_color: configuración de colores
    • terminal_font_family, terminal_font_size, terminal_font_color: configuración de fuente para la visualización de terminal
    • menus: grupos y elementos de menú personalizados
    • clear_before_run: eliminar la salida anterior al volver a ejecutar

Layouts y modos de ejecución

  • Gooey divide las entradas de forma predeterminada en dos grupos: positional y optional
  • Si se usa add_argument_group() de argparse, se pueden agrupar las entradas de forma lógica y ajustar el layout de cada grupo
parser = ArgumentParser()
search_group = parser.add_argument_group(
    "Search Options",
    "Customize the search options"
)
search_group.add_argument(
    '--query',
    help='Base search string'
)
  • Las opciones de layout completas admitidas incluyen show_sidebar=True, show_sidebar=False, navigation='TABBED' y tabbed_groups=True
  • Advanced

    • La pantalla predeterminada es la de configuración full/advanced
    • Para la mayoría de las aplicaciones, resulta adecuado un layout plano que coincida con la estructura de la CLI
    • Para CLIs compuestas por varias rutas o una combinación de pequeñas herramientas, es adecuado un layout en columnas
    • El layout en columnas muestra las rutas principales en la columna izquierda y sus argumentos correspondientes a la derecha
    • Actualmente no se puede especificar explícitamente el layout mediante un parámetro; se construye según si el código contiene subparsers
  • Basic

    • Al configurar advanced=False, se cambia a la vista básica
    • Es adecuada cuando el usuario está familiarizado con aplicaciones de consola, pero quiere una pantalla más pulida que una terminal simple
    @gooey(advanced=False)
    def main():
    
    # rest of code
    
  • No Config

    • Al configurar auto_start=True, se salta la pantalla de configuración y se pasa directamente al área de visualización para ejecutar el programa host
    • Se usa para mejorar la apariencia de scripts pequeños de un solo uso
    @Gooey(auto_start=True)
    def main():
        ...
    

Función de menú

  • Desde Gooey 1.0.2 se puede agregar una Menu Bar en la parte superior
  • Los menús se especifican como una lista de mapas con la forma @Gooey(menu=[{}, {}, ...])
  • Cada grupo de menú tiene name e items
  • Cada elemento de menú siempre incluye type y menuTitle
  • Los tipos de elementos de menú admitidos son:
    • AboutDialog: muestra información del programa, como nombre, versión y licencia
    • MessageDialog: muestra al usuario un mensaje informativo
    • Link: abre la URL indicada en el navegador predeterminado
    • HtmlDialog: muestra un diálogo usando un subconjunto limitado de HTML

Validación dinámica

  • Dynamic Validation es una función experimental y su API puede cambiar o eliminarse
  • Gooey puede realizar opcionalmente una validación previa antes de pasar la entrada del usuario al programa
  • Esta función aprovecha el parámetro type, disponible en la mayoría de los tipos de argumentos de Argparse
def must_be_exactly_ten(value):
    number = int(value)
    if number == 10:
        return number
    else:
        raise TypeError("Hey! you need to provide exactly the number 10!")
  • La validación no se ejecuta de forma predeterminada
  • Para activarla, hay que suscribirse al evento VALIDATE_FORM
from gooey import Gooey, Events

@Gooey(use_events=[Events.VALIDATE_FORM])
def main():
    ...
  • Actualmente esta función es opt-in porque internamente usa Monkey Patching intensivo

Eventos de ciclo de vida y control de UI

  • Desde Gooey 1.2.0, se exponen hooks de ciclo de vida al programa
  • Esta función también es experimental, y su API puede cambiar o eliminarse
  • Actualmente hay dos hooks principales:
    • on_success
    • on_error
  • Ambos hooks se ejecutan después de que el proceso finaliza
  • El handler recibe el objeto Argparse parseado y el estado actual de la UI de Gooey; si devuelve un estado actualizado, este puede reflejarse en la UI
  • Los handlers se conectan al crear GooeyParser
parser = GooeyParser(
    on_success=my_success_handler,
    on_failure=my_failure_handler)
  • Para usarlo, hay que suscribirse explícitamente a los eventos
from gooey import Gooey, Events

@Gooey(use_events=[Events.ON_SUCCESS, Events.ON_ERROR])
def main():
    ...

Progreso y visualización de tiempo

  • Gooey puede leer salidas de texto de progreso existentes y con ellas controlar una Progress Bar
  • En casos simples, convierte en progreso strings como Progress 83% haciendo matching con una expresión regular
@Gooey(progress_regex=r"^progress: (\d+)%$")
  • Para salidas más complejas, se puede transformar el resultado del matching de la expresión regular con progress_expr
@Gooey(progress_regex=r"^progress: (?P<current>\d+)/(?P<total>\d+)$",
       progress_expr="current / total * 100")
  • Al especificar hide_progress_msg=True, se puede ocultar en la consola el texto de salida que coincide con el progreso
  • Con timing_options, se pueden mostrar el tiempo transcurrido y el tiempo restante
  • La visualización de tiempo solo funciona cuando se usan progress_regex y progress_expr

Íconos y empaquetado

  • Gooey ofrece 6 íconos predeterminados; se pueden sobrescribir indicando un directorio de imágenes personalizado con el argumento image_dir
  • Busca las imágenes por nombre de archivo, y los nombres de archivo que se pueden sobrescribir son:
    • program_icon.png
    • success_icon.png
    • running_icon.png
    • loading_icon.gif
    • config_icon.png
    • error_icon.png
  • Para empaquetar ejecutables, ofrece un ejemplo con PyInstaller
  • El procedimiento resumido consiste en colocar build.spec en la raíz de la aplicación, modificar los valores APPPNAME, name y pathex según el proyecto, y luego ejecutarlo
pyinstaller -F --windowed build.spec

1 comentarios

 
GN⁺ 2023-12-10
Comentarios en Hacker News
  • Eh, este es mi proyecto ^_^ ¿por qué está hasta arriba de HN?
    Para responder brevemente al comentario sobre argparse, Gooey ya es un proyecto bastante viejo y cuando empezó argparse era una opción sólida.
    Hoy en día Gooey en sí funciona con JSON y está desacoplado de argparse, pero casi nadie ha hecho otras interfaces, así que argparse sigue siendo la interfaz “oficial” principal.
    Algo interesante es que puede invocar no solo Python sino también cualquier ejecutable, así que es bastante útil: https://chriskiehl.com/article/gooey-as-a-universal-frontend
    Sobre lo de que el último commit fue hace 2 años: cuando uno se hace mayor y cambian las prioridades, se vuelve difícil encontrar una justificación para seguir manteniendo gratis software de nicho :( No sé si sirva de consuelo, pero siempre me siento culpable.
    Curiosamente, por donde vivo hay grafitis que dicen “GOOEY” por todos lados, así que parecen un recordatorio permanente de ese issue tracker abandonado haha

    • argparse sigue siendo una opción bastante buena. Se usa muchísimo, está bien documentado y es relativamente fácil.
      Typer y Click también tienen buena usabilidad, pero siento que la documentación tipo “tutorial” de Typer hace bastante difícil buscar respuestas como qué argumentos recibe Typer.App().
      El programa en el que estoy trabajando ahora está estructurado para que el usuario empiece a manejar el parseo de argumentos directamente, así que argparse encaja bien. Me gustaría ofrecer a los usuarios una UI basada en texto como alternativa, pero creo que Textualize requería Click, y aunque intenté integrar Textual directamente en el código para hacer una UI, le metí varias horas y ni siquiera logré arrancar.
    • Es una idea realmente genial. Me encantan las interfaces separadas de la API.
      Hoy en día me frustra muchísimo que no se puedan controlar las aplicaciones por programación. Honestamente, ojalá hubiera una ley que obligara a que toda función de una aplicación tuviera API.
    • ¡Qué buen proyecto! Tengo curiosidad: ¿por qué hacen dump de sys.argv en un archivo local del directorio de trabajo actual? https://github.com/chriskiehl/Gooey/blob/be4b11b8f27f500e732...
      tmp.txt está lejos de tener un nombre único, así que me pregunto si se me está escapando algo.
    • Está padre la parte de “hoy en día Gooey en sí funciona con JSON y está desacoplado de argparse”.
      Antes de hacer clic, al ver solo el título pensé “seguro depende fuertemente de argparse”, así que fue una sorpresa agradable.
      Yo soy un fanático de getopt, porque considero que la comodidad del usuario importa mucho más que la comodidad del desarrollador. El usuario no debería tener que preocuparse por qué lenguaje de programación o librería para parsear opciones eligió el desarrollador.
      argparse, Click, el estilo flag de Go/X11 y muchas otras librerías rompen convenciones que llevan medio siglo grabadas en la memoria muscular colectiva. Pero si hay una capa intermedia, parece que sí se puede matar dos pájaros de un tiro.
    • Gooey es muy viejo porque lo dejaron abandonado. No lo digo como crítica, solo como una realidad.
      Lo peor es que tampoco ha salido una alternativa viable que lo reemplace, y ninguno de los dos forks ha logrado ganar tracción.
  • ¿No habría que acotarlo a “basado en argparse”?
    argparse sirve para casos simples, pero también hay muchos CLI basados en Click, y una buena parte de las librerías populares de CLI están construidas sobre Click. También hay herramientas CLI de Python que no usan ni argparse ni Click, pero probablemente esas dos sean las más populares.
    ¿Se ha verificado que funcione también con Click? Por ahora solo veo cosas relacionadas con argparse. Si la respuesta es no, entonces la expresión “casi todos” es claramente falsa, y un título más preciso sería “convertir casi cualquier programa de línea de comandos de Python basado en argparse en una aplicación GUI completa”.
    También está el hecho de que el último commit significativo fue hace más de 2 años. Eso en sí no es malo, pero viendo la cantidad de issues abiertos no inspira mucha confianza en el proyecto.
    Aun así, es un proyecto genial. Me gustaría ver más cosas así. Como dice el README, es una herramienta multiplicadora de fuerza, así que alguien puede crear automatizaciones con CLI y compartirlas fácilmente con gente de oficina no técnica. Es parecido a lo que Access hizo por las bases de datos.
    Igual que Access, al crecer en escala o complejidad va a topar con límites, pero para usos pequeños de oficina puede ser lo suficientemente “bueno”.

    • Me da curiosidad qué carencias le ves a argparse. Yo tiendo a minimizar las dependencias externas.
    • En mi trabajo anterior usé exactamente esto para ese propósito. Automatizaba muchas tareas y hacía herramientas para mi equipo, pero la mayoría no eran programadores de Python, así que con Gooey les poníamos una GUI sencilla encima a los scripts. Funcionó muy bien.
    • ¿Qué es exactamente lo que no se puede hacer con argparse?
  • Publicaciones relacionadas:
    Gooey: Turn almost any Python command line program into a GUI application - https://news.ycombinator.com/item?id=27490291 - junio de 2021, 115 comentarios
    Gooey: Turn command line programs into GUI applications - https://news.ycombinator.com/item?id=8218785 - agosto de 2014, 74 comentarios

  • Estaría bien que el sistema operativo y el shell pudieran entender mejor cómo se ejecuta un programa sin depender de una librería de parsing específica
    Sería ideal que los programas se comunicaran en un formato JSON/proto tipado, y que solo con inspeccionar los tipos de entrada y salida esperados se pudiera obtener estructuración y autocompletado de comandos en el shell, o incluso una GUI completa
    Por ahora, parece que lo mejor que se puede hacer es una solución armada con cuidado pero algo improvisada. Cada programa tiene que exportar varios archivos de autocompletado para distintos shells, los estilos de flags varían mucho entre programas y librerías de parsing, y por supuesto no hay GUI

    • Ahora existe PowerShell. Y, respondiendo más en serio, probablemente el único sistema operativo que realmente intentó algo así fue Plan 9
    • ¿Te refieres a algo como una API universal de intercambio gRPC/CLI con soporte nativo del sistema operativo? Eso podría ser bastante genial
      Aunque el principal problema de gRPC probablemente sea que muchos desarrolladores, incluyéndome, sienten que es algo más engorroso de manejar que una solución que se use de forma natural dentro del lenguaje
    • Vale la pena probar PowerShell. Básicamente es el ecosistema .NET de Microsoft convertido en una línea de comandos interactiva
      No estoy seguro de si PowerShell puede aprovechar por completo los tipos estáticos que son el núcleo del sistema, pero su capacidad para pasar objetos por la línea de comandos es casi única
      En Linux se puede encadenar la línea de comandos combinando jc(https://github.com/kellyjonbrazil/jc) y jq(https://jqlang.github.io/jq/), pero aun así son como dos pasos extra frente a lo que PowerShell ya trae integrado
  • A veces una GUI se siente como comida reconfortante. Puedes simplemente explorar una interfaz gráfica sin la carga cognitiva que puede generar una CLI que usas muy rara vez
    Cambiando un poco de tema, hace unos meses aquí conocí el editor CUDATEXT, que te permite usar elementos GUI arbitrarios como MENU, INPUTS, etc., mediante una API de Python de un solo archivo. Son los mismos elementos que usa el propio editor
    Ahora mismo estoy usando esos elementos GUI simples para configurar cosas y generar un blog directamente dentro del editor

  • También vale la pena ver esto:
    “Textual: framework ligero de aplicaciones para Python. Crea interfaces de usuario sofisticadas con una API simple de Python y ejecuta apps en la terminal y en el navegador web” https://github.com/textualize/textual/

  • Solo quería decir gracias por crear este excelente software. Me gusta Gooey y lo he usado bastante en varios programas
    Para mí encaja perfecto justo en ese espacio donde un script de Python hace algo importante y ahora hay que lograr que personas no programadoras también lo usen

  • Esto me recuerda a naked objects. La idea era que, si definías unas cuantas anotaciones y un tema para una clase Java, se generaba toda una GUI, o incluso una aplicación frontend web
    Era una idea muy interesante, pero hasta donde sé, al final no terminó de funcionar bien

    • Django también tiene una filosofía parecida. Defines un modelo, se refleja de inmediato en una tabla de base de datos, y a partir de ese modelo se pueden generar formularios automáticamente
      Creo que el problema es que se acoplan entre sí preocupaciones verticales como presentación, dominio e infraestructura. Está bien cuando la representación en pantalla y en base de datos del modelo de dominio es simple
      Pero si quieres crear una interfaz compleja con muchas comodidades, esos requisitos terminan filtrándose hacia la capa de dominio. Y al revés, si quieres construir un modelo de dominio complejo, se vuelve poco realista hacer cumplir invariantes a nivel del lenguaje, porque cualquier código con una referencia a Model puede modificarlo arbitrariamente
  • También estaría bien algo que hiciera lo contrario

    • Poder configurar un barrido de parámetros en una GUI a partir de la información obtenida desde argparse, y luego convertir eso de vuelta en un script de shell que ejecute y guarde los resultados como corresponde
    • ¿Te refieres a un editor WYSIWYG que escupa una CLI? Suena bastante genial, aunque seguramente tendría ciertas limitaciones inevitables. Me pregunto si alguien ha intentado ese enfoque, pero no se me ocurre ningún ejemplo
  • En el antiguo Macintosh Programmer's Workshop, o MPW, el shell basado en texto para Mac OS de los 80 y 90, había una función parecida llamada Commando para casi todos los comandos
    La información de Commando se guardaba en recursos cmdo. Si el soporte para atributos extendidos no hubiera sido tan irregular, hoy todavía se podría hacer algo parecido
    Revisando, veo que A/UX también tenía Commando. Si hacías doble clic en un comando de terminal desde Finder, aparecía un cuadro de Commando para elegir opciones de CLI, y luego se ejecutaba en una ventana de shell. Hoy, si ejecutas un comando de terminal desde Finder, simplemente se lanza en una ventana de shell sin argumentos
    https://cohost.org/boredzo/post/804893-i-still-want-a-moder
    http://toastytech.com/guis/aux3.html