Mejores Prácticas en bots en Python¶
Este documento describe las mejores prácticas utilizadas por Unimate RPA para la construcción de bots RPA y Analytics en Python.
Estructura del Proyecto¶
Organizar el código en una estructura modular y fácil de mantener, siguiendo esta estructura:
project_name/
│
├── project_name/ # Paquete principal
│ ├── __init__.py # Indica que es un paquete de Python
│ ├── main.py # Punto de entrada principal
│ ├── logs/ # Carpeta para logs del bot
│ ├── tasks/ # Módulos para cada tarea del bot (si es necesario)
│ │ ├── task1.py
│ │ ├── task2.py
│ │ └── ...
│
├── .env # Variables de entorno (credenciales seguras)
├── config.yaml # Archivo de configuración (rutas, parámetros)
├── README.md # Documentación del bot
├── requirements.txt # Dependencias del proyecto
├── launcher.py # Script para gestionar entorno y ejecutar el bot
Código Modular y Separación de Responsabilidades¶
Cada funcionalidad debe estar en su propio módulo, evitando código innecesario en main.py.
El flujo del bot debe estar claro y delegar responsabilidades correctamente a módulos en tasks/.
tasks/task1.py (Módulo de tarea específica, lógica reusable)
"""
Módulo de procesamiento de datos.
"""
def process_data(data):
"""
Procesa los datos y devuelve el resultado.
Parámetros:
data (list): Lista de strings a procesar.
Retorna:
list: Lista de strings en mayúsculas.
"""
return [d.upper() for d in data]
main.py (Punto de entrada con toda la lógica del bot)
"""
Punto de entrada principal del bot RPA.
"""
import logging
from tasks.task1 import process_data
# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def main():
"""Ejecuta el bot y maneja su flujo principal."""
logger.info("Iniciando bot...")
data = ["rpa", "automation", "bot"]
resultado = process_data(data)
print("Datos procesados:", resultado)
if __name__ == "__main__":
main()
Uso de requirements.txt y Entorno Virtual (venv)¶
Siempre se debe usar un entorno virtual para evitar conflictos entre dependencias.
Creación y activación del entorno virtual:
python -m venv env
env\Scripts\activate
Instalación de dependencias:
pip install -r requirements.txt
Ejemplo de requirements.txt:
python-dotenv
requests
selenium
pyautogui
pandas
Manejo de Rutas y Configuración en el Bot¶
Para garantizar flexibilidad y escalabilidad en los bots de RPA, es fundamental evitar rutas hardcodeadas y utilizar una estrategia estándar para gestionar rutas y configuraciones. Esto se logra combinando rutas absolutas y relativas, junto con el uso de archivos de configuración (config.yaml) y variables de entorno (.env).
Importante:
config.yaml almacena rutas, parámetros generales y configuraciones que no son sensibles.
.env debe siempre estar presente, ya que almacena credenciales y datos sensibles.
El bot no debe ejecutarse si .env no está presente.
Uso de Configuración con config.yaml y .env¶
Ejemplo de config.yaml:
rutas:
logs: "logs/bot.log"
entrada: "data/input.xlsx"
salida: "data/output.xlsx"
parametros:
modo_debug: true
max_intentos: 3
Ejemplo de .env:
API_KEY=my_secret_key
DB_PASSWORD=super_secure_password
Carga de Configuración en main.py¶
import os
import yaml
from dotenv import load_dotenv
import sys
# Cargar variables de entorno desde .env (el script debe fallar si .env no está presente)
if not os.path.exists(".env"):
print("Error: Archivo .env no encontrado. El bot no puede ejecutarse sin credenciales.")
sys.exit(1)
load_dotenv()
# Cargar configuración desde config.yaml
with open("config.yaml", "r") as file:
config = yaml.safe_load(file)
# Obtener parámetros del YAML
LOGS_PATH = config["rutas"]["logs"]
INPUT_FILE = config["rutas"]["entrada"]
OUTPUT_FILE = config["rutas"]["salida"]
DEBUG_MODE = config["parametros"]["modo_debug"]
# Obtener credenciales desde .env
API_KEY = os.getenv("API_KEY")
DB_PASSWORD = os.getenv("DB_PASSWORD")
print(f"Ruta de logs: {LOGS_PATH}")
print(f"Archivo de entrada: {INPUT_FILE}")
print(f"Modo Debug: {DEBUG_MODE}")
Gestión de Entornos: Desarrollo y Producción¶
Para manejar distintos entornos (desarrollo y producción), config.yaml puede contener configuraciones separadas para cada uno, evitando modificar el código fuente al cambiar de entorno.
Estructura de config.yaml con múltiples entornos
entorno: "desarrollo" # Puede ser "desarrollo" o "producción"
configuracion:
desarrollo:
rutas:
logs: "logs/dev_bot.log"
entrada: "data/dev_input.xlsx"
salida: "data/dev_output.xlsx"
parametros:
modo_debug: true
max_intentos: 5
produccion:
rutas:
logs: "logs/prod_bot.log"
entrada: "data/input.xlsx"
salida: "data/output.xlsx"
parametros:
modo_debug: false
max_intentos: 3
Cómo cargar la configuración en el código
El bot debe seleccionar automáticamente la configuración del entorno definido en config.yaml:
import yaml
with open("config.yaml", "r") as file:
config = yaml.safe_load(file)
# Obtener el entorno activo
entorno_activo = config["entorno"]
# Cargar la configuración correspondiente al entorno activo
configuracion = config["configuracion"][entorno_activo]
# Acceder a las rutas y parámetros
LOGS_PATH = configuracion["rutas"]["logs"]
INPUT_FILE = configuracion["rutas"]["entrada"]
OUTPUT_FILE = configuracion["rutas"]["salida"]
DEBUG_MODE = configuracion["parametros"]["modo_debug"]
print(f"Entorno: {entorno_activo}")
print(f"Ruta de logs: {LOGS_PATH}")
print(f"Modo Debug: {DEBUG_MODE}")
Cambio de entorno sin modificar el código
Para cambiar de desarrollo a producción, basta con modificar la línea:
entorno: "produccion"
También es posible hacer que el entorno sea configurable desde una variable de entorno .env:
ENTORNO=produccion
Y en el código:
import os
entorno_activo = os.getenv("ENTORNO", "desarrollo") # Usa "desarrollo" por defecto si no está definido
print(f"Entorno: {entorno_activo}")
Uso de Parámetros desde la Línea de Comandos¶
Los bots pueden recibir parámetros externos al ejecutarse, en lugar de depender solo de config.yaml.
Cuándo usar argparse en lugar de config.yaml?
config.yaml → Para parámetros fijos en la configuración.
argparse → Para valores que cambian en cada ejecución.
Ejemplo de main.py con argparse:
import argparse
def main(fecha):
print(f"Procesando datos para la fecha: {fecha}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Ejecutar bot con fecha específica.")
parser.add_argument("--fecha", type=str, required=True, help="Fecha en formato YYYY-MM-DD")
args = parser.parse_args()
main(args.fecha)
Manejo de Rutas Absolutas y Relativas¶
Uso de rutas relativas
Siempre que sea posible, utilizar rutas relativas dentro del proyecto en config.yaml:
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
input_path = os.path.join(BASE_DIR, "data", "input.xlsx")
print(f"Ruta absoluta del archivo de entrada: {input_path}")
Cuándo usar rutas absolutas?
Las rutas absolutas pueden ser necesarias en algunos casos:
Interacción con programas externos
Ejemplo: Almacenar archivos en una carpeta específica que otro programa lee.
Rutas de red compartidas
Ejemplo: Acceder a un archivo en un servidor compartido
\\servidor\carpeta\archivo.xlsx
.
Ejemplo de cómo definir rutas absolutas en config.yaml:
rutas:
reporte: "C:/Users/Usuario/Documentos/reporte.xlsx"
Ejemplo en código:
reporte_path = config["rutas"]["reporte"]
print(f"Ruta absoluta del reporte: {reporte_path}")
Logging y Manejo de Errores¶
Capturar y registrar errores de manera adecuada es fundamental para la depuración y mantenimiento de los bots.
Configuración del Logging
El archivo config.yaml debe definir la ubicación del archivo de logs y el nivel de logging:
rutas:
logs: "logs/bot.log"
logging:
nivel: "INFO"
Validación del nivel de logging Para evitar errores por valores inválidos en config.yaml, se debe validar antes de aplicarlo:
import logging
import yaml
with open("config.yaml", "r") as file:
config = yaml.safe_load(file)
LOGS_PATH = config["rutas"]["logs"]
LOG_LEVEL = config["logging"]["nivel"].upper()
if LOG_LEVEL not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
LOG_LEVEL = "INFO"
logging.basicConfig(
filename=LOGS_PATH,
level=getattr(logging, LOG_LEVEL, logging.INFO),
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
Manejo de Errores en main.py¶
main.py debe capturar y registrar errores usando traceback, asegurando que el bot no falle silenciosamente.
import traceback
from logs.logger import logger
from tasks.task1 import process_data
def main():
"""Ejecuta el bot y maneja su flujo principal."""
logger.info("Iniciando bot...")
try:
data = ["rpa", "automation", "bot"]
resultado = process_data(data)
print("Datos procesados:", resultado)
except FileNotFoundError as e:
logger.error(f"Archivo no encontrado: {e}\n{traceback.format_exc()}")
except ValueError as e:
logger.warning(f"Error de valor: {e}\n{traceback.format_exc()}")
except Exception as e:
logger.critical(f"Error inesperado: {e}\n{traceback.format_exc()}")
if __name__ == "__main__":
main()
Buenas prácticas en manejo de errores:
Registrar errores en logs, no solo imprimirlos.
Diferenciar errores recuperables y errores fatales.
Configurar el nivel de logging en config.yaml para ajustar detalle sin cambiar código.
Uso de launcher.py para ejecución Manual y Automática¶
launcher.py se encarga de:
Activar el entorno virtual.
Instalar dependencias solo si es necesario.
Ejecutar main.py.
Ejemplo de launcher.py:
import os
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
VENV_PATH = os.path.join(BASE_DIR, "env")
REQUIREMENTS_FILE = os.path.join(BASE_DIR, "requirements.txt")
def setup_environment():
"""Crea el entorno virtual si no existe e instala las dependencias."""
if not os.path.exists(VENV_PATH):
print("Creando entorno virtual...")
subprocess.run([sys.executable, "-m", "venv", VENV_PATH], check=True)
print("Instalando dependencias si es necesario...")
subprocess.run([os.path.join(VENV_PATH, "Scripts", "python.exe"), "-m", "pip", "install", "-r", REQUIREMENTS_FILE], check=True)
def run_bot():
"""Ejecuta el bot principal."""
try:
subprocess.run([PYTHON_EXEC, "main.py"], check=True)
except subprocess.CalledProcessError as e:
print(f"Error en la ejecución de main.py: {e}")
sys.exit(1)
if __name__ == "__main__":
try:
setup_environment()
run_bot()
except Exception as e:
print(f"Error crítico: {e}")
sys.exit(1)
Ejecutarlo manualmente:
python launcher.py
Programar en Windows:
Programa:
C:\ruta\al\proyecto\env\Scripts\python.exe
Argumentos:
C:\ruta\al\proyecto\launcher.py
Iniciar en:
C:\ruta\al\proyecto\
Comentarios y Docstrings¶
Para mantener el código claro y entendible, es importante documentarlo correctamente con docstrings y comentarios en línea.
Reglas generales:
Usar docstrings en funciones y clases para describir su propósito.
Usar comentarios en línea solo cuando sea necesario explicar un paso específico.
Mantener los comentarios actualizados, evitando información desactualizada que pueda confundir.
Ejemplo de buenos docstrings y comentarios en main.py:
"""
Punto de entrada principal del bot RPA.
Este módulo contiene la lógica principal del bot, incluyendo la inicialización
del entorno y la ejecución de tareas específicas.
"""
import logging
from tasks.task1 import process_data
logger = logging.getLogger(__name__)
def main():
"""Inicia la ejecución del bot."""
logger.info("Bot iniciado.")
data = ["rpa", "automation", "bot"]
resultado = process_data(data)
print("Datos procesados:", resultado)
if __name__ == "__main__":
main()
Ejemplo de documentación con parámetros y retorno en tasks/task1.py:
"""
Módulo de procesamiento de datos del bot.
"""
def process_data(data):
"""
Procesa los datos y los convierte a mayúsculas.
Parámetros:
data (list): Lista de strings a procesar.
Retorna:
list: Lista de strings en mayúsculas.
"""
return [d.upper() for d in data]
Ejemplo de comentarios en línea cuando son necesarios en main.py:
def execute_task():
"""Ejecuta una tarea y maneja errores con traceback."""
try:
logger.info("Ejecutando tarea...")
result = 10 / 0 # Esto generará un ZeroDivisionError
return result
except Exception as e:
logger.error(f"Error en execute_task: {e}")
return None
Documentación con README.md¶
Cada bot debe incluir un archivo README.md claro y estructurado.
Ejemplo de README.md:
# Bot RPA con Python
Este bot automatiza procesos repetitivos utilizando Python.
## Estructura del Proyecto
project_name/
│
│── project_name/ # Código fuente
│ │── main.py # Punto de entrada
│ │── tasks/ # Módulos de tareas
│
│── logs/ # Carpeta de logs
│── .env # Variables de entorno
│── requirements.txt # Dependencias
│── launcher.py # Script de ejecución
## Instalación y Configuración
1. Descomprimir el paquete en la ubicación deseada
- Extraer el contenido del .zip en una carpeta de trabajo.
2. Ejecutar launcher.py
El archivo launcher.py se encarga de:
- Crear y activar el entorno virtual.
- Instalar las dependencias necesarias.
- Ejecutar main.py.
## Uso
Para ejecutar el bot manualmente:
```sh
python launcher.py
```
Para programarlo en Windows:
- Programa: `C:\ruta\al\proyecto\env\Scripts\python.exe`
- Argumentos: `C:\ruta\al\proyecto\launcher.py`
- Iniciar en: `C:\ruta\al\proyecto\`
## Configuración
El bot utiliza un archivo `.env` para gestionar credenciales y configuraciones sensibles.
Ejemplo de `.env`:
```ini
API_KEY=my_secret_key
DB_PASSWORD=super_secure_password
DB_HOST=localhost
LOG_LEVEL=INFO
```
**Carga de variables en Python:**
```python
from dotenv import load_dotenv
import os
load_dotenv() # Carga variables de entorno desde .env
API_KEY = os.getenv("API_KEY")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_HOST = os.getenv("DB_HOST", "default_host") # Valor por defecto
```
Eficiencia y Buenas Prácticas en Código¶
Usa listas por comprensión en lugar de bucles innecesarios:
# Forma tradicional con for loop
squared_numbers = []
for num in range(10):
squared_numbers.append(num**2)
# Lista por comprensión (más eficiente)
squared_numbers = [num**2 for num in range(10)]
Usa context managers para manejar archivos:
# Mala práctica: No se cierra el archivo automáticamente
file = open("archivo.txt", "r")
content = file.read()
file.close()
# Buena práctica: Context manager cierra automáticamente el archivo
with open("archivo.txt", "r") as file:
content = file.read()
Evita esperas fijas en RPA y usa estrategias dinámicas:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Mala práctica: Espera fija, desperdicia tiempo
import time
time.sleep(5) # Siempre espera 5 segundos aunque el elemento ya esté disponible
# Buena práctica: Espera dinámica con WebDriverWait
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "boton")))
Conclusión¶
La implementación de estas buenas prácticas permite garantizar que los bots desarrollados con Unimate sean modulares, seguros, eficientes y fáciles de mantener.
Se establecen estándares en la estructura del código, el manejo de entornos virtuales, la gestión de dependencias, la documentación y la automatización, asegurando un desarrollo escalable y sostenible.