Refactoriza un proyecto con Python y Poetry para Twitch

Refactoriza un proyecto con Python y Poetry para Twitch

En el artículo anterior mostré cómo inicié un proyecto de chatbot para la plataforma Twitch. A medida que adquirí más conocimientos, con ayuda de la comunidad, tanto sobre proyectos como sobre el lenguaje Python, logré refactorizar el proyecto manteniendo la funcionalidad inicial pero con más organización.

Recordemos que el tutorial fue escrito usando el sistema operativo Windows, por lo que puede haber diferencias en los comandos para otros sistemas.


Identificación del problema

El motivo principal de esta decisión fue que en un solo archivo se mezclaban muchas responsabilidades, por ejemplo, teníamos conexión al IRC de Twitch, información sensible del token, apertura y escritura de archivos, funciones para enviar mensajes al chat, etc. todo esto en el archivo principal.

Además, no teníamos información sobre qué bibliotecas externas y sus versiones eran necesarias para el proyecto.

Selección de las herramientas


Para solucionar la falta de especificación de la biblioteca, tenemos las principales opciones:

  • Uso de un archivo requirements.txt o setup.py, indicando cuáles librerías deben ser instaladas, lo cual se hace mediante un comando simple en la terminal:

‘pip install -r requirements’

  • Uso de un manejador de dependencias Poetry, en el que podemos declarar qué bibliotecas y versiones se instalarán para el proyecto y también gestionar el entorno virtual.

Entre ellos elegimos Poetry porque facilita la gestión del entorno, así como la construcción del proyecto.

Instalación y configuración de Poetry

Para instalar Poetry usamos el comando conforme a la documentación, a través de la terminal PowerShell.

(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -

Si todo salió bien, aparecerá el siguiente mensaje:


Para iniciar un proyecto Poetry, todavía en powershell, utilizamos el comando poetry init -n en la raíz del proyecto. El archivo de configuración pyproject.toml se generará automáticamente con algunas especificaciones patrón. Ese archivo es compatible con otros manejadores de Python, no solo con Poetry.


En el primer bloque [tool.poetry] podemos especificar algunos parámetros básicos como nombre del proyecto, versión, descripción y autores.

En el segundo bloque [tool.poetry.dependencies] especificamos las dependencias utilizadas en el proyecto. Aquí agregaremos en una nueva línea la dependencia llamada  twitchio = "~1.2.3". El símbolo ~ al inicio de la versión indica que se fijará en los dos primeros dígitos, dejando el último, referente al bugfix, el más actual posible.

Debajo del bloque de dependencias del proyecto, agregamos un nuevo bloque, llamado de la siguiente manera: [tool.poetry.dev-dependencies]. En él tenemos las dependencias de desarrollo del proyecto, que no son necesarias para ejecutar el chatbot, sino que solo facilitan el desarrollo. La dependencia que especificamos aquí es una librería que nos ayuda a formatear el código Python generando alertas sobre sangría, bibliotecas no utilizadas, etc. Debajo del bloque, agregamos la especificación flake8 = "^3.9".

En el próximo bloque [build-system] determinamos que usamos Poetry para hacer el build del programa.

Finalmente, añadimos un bloque más [tool.poetry.scripts] donde insertaremos la especificación chatbot = "chatbot.index:run" ,que será el comando responsable de la inicialización del chatbot.

Para el funcionamiento correcto de start precisamos agregar dos nuevos archivos: __init__.py vacío y __main__.py con el siguiente contenido:

En el archivo principal index.py, creamos la función de inicialización para luego ir con los imports:


Luego de completar la configuración del archivo, agregando todas las dependencias necesarias, volvemos a la terminal de PowerShell para instalarlo con el siguiente comando:

poetry install.

Cuando ejecutamos este comando, podemos ver en la terminal que Poetry crea un entorno virtual para instalar las bibliotecas.

Creating virtualenv chatbot-jMkYLH2J-py3.11 in C:\Users\...Cache\virtualenvs

De esta manera, cualquier cosa que eventualmente salga mal en el entorno se puede eliminar fácilmente, sin necesidad de desinstalar las bibliotecas.

Al finalizar esta instalación, se genera el archivo poetry.lock, enumera todas las dependencias con sus respectivas versiones instaladas. Si se instala en una máquina diferente, este archivo puede presentar algunos problemas y se puede eliminar y crear uno nuevo según las especificaciones del pyproject.toml.

Otro archivo de configuración que tendremos es .editorconfig, donde definiremos los siguientes parámetros:


Estas definiciones son importantes para estructurar la escritura de código, siguiendo un patrón de caracteres, sangría, ocultamiento, espaciado, entre otros. De esta manera evitamos conflictos de Git, por ejemplo, los saltos de línea en diferentes sistemas operativos aparecen de manera diferente, como \n o \r\n.

Organizar el proyecto

El patrón de proyecto Python utiliza un directorio con el mismo nombre que el proyecto para colocar los archivos de desarrollo, dejando los archivos de configuración y datos en la raíz del proyecto.

Del proyecto anterior, haremos algunos cambios. Inicialmente, eliminaremos el directorio venv, porque quien gestione el entorno virtual ahora será Poetry. También podemos borrar el directorio __pycache__, que almacena archivos bytecode que facilitan el inicio del chatbot, pero se crearán nuevos archivos en función de la organización que hagamos.

Vamos a organizar los archivos existentes index.py y musica.py dentro de un nuevo directorio creado en la raíz del proyecto llamado chatbot, con lo que el archivo first.txt puede excluirse.

En esta etapa tendremos la siguiente estructura de directorios y archivos:

chatbot(raiz)

│   .editorconfig

│   poetry.lock

│   pyproject.toml

└───chatbot

│   __init__.py

│   __main__.py

│   index.py

│   musica.py


Separar dados sensíveis

En el archivo principal tenemos datos sensibles para inicializar el chatbot, como token, nick e initial_chanels. Es importante que esta información confidencial no sea visible directamente en el código, por lo que realizaremos algunas modificaciones.

Creamos un archivo llamado config.ini en la raíz del proyecto y traeremos los datos que están definidos en index.py para acá:


Al crear un archivo config.py dentro del directorio chatbot, configuraremos una estructura que pueda capturar datos del config.ini. En este archivo también definiremos constantes para almacenar la información necesaria.

  • ConfigParser: librería responsable de leer y escribir archivos de configuración;
  • Config: instancia de una ConfigParser;
  • Config.read: lee el archivo de configuración, definiendo una codificación;
  • TOKEN y USERNAME: claves únicas leídas del archivo config.ini, almacenadas con el tipo de dato string;
  • STREAMERS: nombres de los canales leídos del archivo config.ini y almacenados separadamente con el tipo de dato list.

Debidamente configurado, index.py ya no tendrá los datos expuestos directamente. Siguiendo la serie de pasos anteriores, podemos importar las constantes y asignarlas a los parámetros del constructor de la siguiente manera:


Encapsulamiento de lógica

Otro punto de mejora a aplicar es la separación de responsabilidades para cada parte del código. Podemos ver que toda la lógica de procesamiento se mezcla con las funciones de llamada de comandos en el archivo principal.

Comando first

Comenzando por el comando first, vamos a separar la responsabilidad de manipulación de persistencia de datos, dejando la función de llamada del comando responsable de verificar y devolver la posición del usuario.

Creamos un archivo one_per_live.py en el directorio chatbot con las siguientes especificaciones:


Vamos a analizar ese archivo.

La shelve, importada al inicio del archivo, es una librería estándar de Python que sirve para la persistencia de datos. Con ella podemos realizar manipulaciones más fácilmente.

Creamos una clase llamada OnePerLive con las siguientes funciones:

1) __init__ (constructor): tenemos los siguientes atributos:

  • Self.filename: recibe el nombre del archivo donde los datos van a colocarse;
  • Self.file: llamamos la función shelve_open pasando como parámetro el nombre del archivo y definiendo writeback para permitir que sean escritos nuevos datos;
  • Self._check_struct(): es una función creada para verificar los datos contenidos en el archivo;

2) _get_date: responsable de verificar la fecha actual y el horario definido como turno del día.

3) _check_struct: verifica y manipula los datos dentro del archivo, agregando claves como date y username.

Tenga en cuenta que hay un signo _ antes de estas dos funciones. Este carácter indica que solo se puede acceder a ellos dentro de la propia clase.

  • __len__: se encarga de comprobar la cantidad de datos contenidos en la clave 'nombre de usuario' y devolver cero si el día ha cambiado.
  • __contains__: toma como parámetro un nombre de usuario y devuelve si está en la lista o no, comprobando también el cambio de día.

Estas dos funciones, así como el constructor, también contienen caracteres especiales antes y después de su nombre. Este tipo de función se llama dunder o método mágico, y comúnmente son utilizados para la sobrecarga de métodos.

  • Add recibe un nombre de usuario para agregar al archivo de persistencia, en la clave username.

    Finalizando la clase OnePerLive, vamos a llamarla en el archivo principal, en el constructor de la clase Bot:


Inicialmente, haremos el import de la clase y crearemos el atributo self.first, el cual recibe una instancia de la clase, pasando como parámetro el nombre do archvo de persistencia de datos first.tmp. De esa forma, podemos excluir todo el código de manipulación de texto que existía anteriormente.

A continuación, haremos algunas alteraciones en el comando first, definiendo el atributo self.first y utilizando los métodos de la clase.

Comando craps

La siguiente mejora que podemos hacer es eliminar la lógica del juego de dados del archivo principal, para ello crearemos un archivo craps.py, organizando todo de una forma más legible:


Llamar al comando en el archivo principal es mucho más simplificado, simplemente importando la función craps e invocarla siempre que el comando sea llamado:


La estructura final queda así:

chatbot(raiz)

│   .editorconfig

│   config.ini

│   poetry.lock

│   pyproject.toml

└───chatbot

│   __init__.py

│   __main__.py

│   config.py

│   craps.py

│   index.py

│   musica.py

│   one_per_live.py

Con todos los cambios realizados, ejecutemos el chatbot abriendo PowerShell en la carpeta raíz del proyecto y ejecutando el siguiente comando:

poetry run chatbot

Conclusión

La refactorización de código funciona para mejorar el rendimiento, la legibilidad, el diseño, etc. En esta versión creamos clases, evitamos la repetición y confiamos en la reutilización del código. Usando el ejemplo de persistencia de datos, podemos crear otras instancias de la misma clase para generar nuevas funcionalidades.

Todas las configuraciones presentadas aquí no son solo para el chatbot, sino que se pueden aplicar a cualquier proyecto de Python que requiera estructuración y organización. Podemos mejorar aún más este proyecto versionando el código en GitHub y creando documentación con MkDocs.

¡Saludos!

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.