Cómo automatizar la exportación de datos e informes por correo electrónico con Python – una guía paso a paso

Cómo automatizar la exportación de datos y enviar informes por correo electrónico con Python una guía paso a paso

En el mundo actual basado en datos, la automatización es clave para agilizar tareas y ahorrar tiempo. En este tutorial fácil de seguir, te guiaré a través del proceso de automatizar exportaciones de datos desde una base de datos PostgreSQL y enviarlos como archivo adjunto por correo electrónico utilizando Python.

Esta guía paso a paso te ayudará a comprender los fundamentos del trabajo con bases de datos, manipulación de datos y comunicación por correo electrónico, todo mientras automatizas estos procesos con un script en Python.

Contexto Empresarial

Imagina que eres parte de una organización donde tus gerentes esperan un informe semanal lleno de información valiosa. Pero crear este informe está lejos de ser una tarea sencilla.

Para obtener la información que necesitas, debes ejecutar manualmente diez consultas diferentes a la base de datos, recopilar los resultados y luego compilarlos minuciosamente en una hoja de cálculo de Excel. Es un proceso que consume mucho tiempo y propenso a errores que puede dejarte exhausto.

En este escenario, ¿no sería un cambio revolucionario si Python pudiera encargarse de todo este proceso por ti?

Imagina esto: cada semana, sin intervención manual alguna, Python extrae sin problemas los datos requeridos, los compila en un archivo Excel ordenado e incluso lo envía a tus gerentes puntualmente.

Este tutorial te ayudará a aprender cómo hacer esto. Te guiaré a través de los pasos para automatizar este proceso, haciendo que tus informes semanales o mensuales sean pan comido y liberándote para que te enfoques en tareas más críticas.

Tabla de contenidos

  1. Requisitos
  2. Cómo configurar tu entorno virtual
  3. Cómo configurar tu base de datos de ejemplo
  4. Cómo configurar el registro y las variables de entorno
  5. Cómo extraer los datos de la base de datos
  6. Cómo estructurar los datos de reserva con la clase BookingInfo
  7. Cómo convertir los datos en una hoja de cálculo de Excel
  8. Cómo combinar las funcionalidades
  9. Cómo enviar un correo electrónico con el informe de datos de reservas
  10. Cómo probar el flujo
  11. Cómo programar la aplicación
  12. Conclusión

Requisitos

Antes de comenzar, asegúrate de tener lo siguiente:

  1. Python instalado en tu computadora. Puedes descargar Python desde Python.org.
  2. Conocimientos básicos del lenguaje de programación Python
  3. Familiaridad con el envío de correos electrónicos en Python
  4. PostgreSQL instalado en tu computadora. Puedes descargar PostgreSQL desde aquí.

Cómo configurar tu entorno virtual

Antes de comenzar a codificar, deberás asegurarte de tener todas las herramientas y bibliotecas necesarias instaladas.

Para asegurarte de tener un entorno limpio y aislado, crearás un entorno virtual utilizando venv.

Crea un directorio de proyecto y navega hasta él en la terminal:

mkdir report-automationcd report-automation

Crea un entorno virtual llamado env utilizando el siguiente comando:

python -m venv env

Python ahora viene con la biblioteca preinstalada venv para crear entornos virtuales.

Activa el entorno virtual de la siguiente manera:

source env/bin/activate

Nota: si estás en Windows, necesitarás utilizar source env/Scripts/activate para activar el entorno.

Deberías ver (env) en el indicador de tu terminal, indicando que el entorno virtual ha sido activado.

Cómo instalar las bibliotecas requeridas

Ahora que has creado el entorno virtual, puedes instalar las siguientes bibliotecas:

  • psycopg2: adaptador de Python para PostgreSQL, que permite que las aplicaciones Python interactúen con bases de datos PostgreSQL.
  • pandas: una biblioteca versátil de manipulación y análisis de datos para Python, ideal para trabajar con datos estructurados.
  • xlsxwriter: módulo de Python para crear y dar formato a archivos de Excel (XLSX), útil para generar informes y hojas de cálculo.

Para instalar las bibliotecas, ejecuta el siguiente comando:

pip install psycopg2 pandas xlsxwriter

Cómo configurar tu base de datos de muestra

En esta sección, te guiaré en la configuración de una base de datos de demostración llamada “airlines” que utilizaremos a lo largo de este tutorial. La base de datos incluye tres tablas: bookings, flights y airports_data.

Te proporcionaré un archivo de script SQL llamado airlines_db.sql que crea la base de datos y la rellena con datos de muestra. Para configurar la base de datos, necesitarás tener PostgreSQL instalado en tu sistema.

Descarga e instalación de la base de datos

  1. Descarga el archivo de script SQL “airlines_db.sql” desde aquí.
  2. Abre tu terminal o símbolo del sistema.
  3. Utiliza el siguiente comando para instalar la base de datos. Asegúrate de tener las herramientas de línea de comandos de PostgreSQL instaladas y de que puedes acceder al comando psql. Reemplaza postgres por tu nombre de usuario de PostgreSQL si es diferente.
psql -f airlines_db.sql -U postgres

Este comando ejecutará el script SQL y creará la base de datos “airlines” con las tablas bookings, flights y airports_data.

Descripción del esquema

El esquema principal en la base de datos es bookings. Echemos un vistazo más de cerca a las tablas en la base de datos “airlines”:

Screenshot-2023-10-29-115228
Diagrama de Esquema

Tabla bookings.bookings

La tabla “bookings” está diseñada para almacenar información crucial sobre las reservas realizadas para los vuelos. Cada reserva está identificada de forma única por el campo book_ref, que es de tipo character(6). El campo total_amount es de tipo numeric(10,2) y representa el costo total de la reserva.

Para hacer un seguimiento de la fecha y hora de la reserva, la tabla incluye un campo book_date de tipo bigint. Esta tabla sirve como repositorio central para los datos de reserva y es esencial para hacer un seguimiento de las reservas de pasajeros, los costos y las fechas de reserva.

Tabla bookings.flights

La tabla “flights” está dedicada a capturar detalles exhaustivos sobre los vuelos, incluyendo información sobre sus estados, horarios de salida y llegada programados y reales, y otros datos importantes relacionados con los vuelos.

La clave primaria para esta tabla es el flight_id, un identificador integer. Cada vuelo está asociado con un número de vuelo específico indicado por el campo flight_no, de tipo character(6).

Para comprender el origen y destino del vuelo, los campos departure_airport y arrival_airport almacenan los códigos de aeropuerto de salida y llegada como tipos character(3), respectivamente.

El campo status es un character varying(20) que registra el estado del vuelo, que debe ser uno de los siguientes: ‘A tiempo’, ‘Retrasado’, ‘Salido’, ‘Llegado’, ‘Programado’ o ‘Cancelado’. La tabla también incluye campos para horarios de salida y llegada programados (scheduled_departure y scheduled_arrival) y horarios de salida y llegada reales (actual_departure y actual_arrival).

Además, esta tabla establece dos claves foráneas esenciales: flights_arrival_airport_fkey y flights_departure_airport_fkey, que se vinculan con el campo airport_code en la tabla “airports_data”. Esto establece conexiones entre los vuelos y sus respectivos aeropuertos de salida y llegada.

Tabla bookings.airports_data

La tabla “airports_data” sirve como un repositorio para datos relacionados con aeropuertos y sus ubicaciones geográficas. Cada aeropuerto se identifica con un código único character(3) almacenado en el campo airport_code, que también funciona como clave primaria.

El campo timezone, de tipo text, registra la zona horaria específica del aeropuerto, proporcionando información esencial para programar y operar de manera adecuada. El campo airport_name es de tipo character varying y almacena el nombre del aeropuerto. Además, la tabla incluye el campo city como un tipo character varying, indicando la ciudad en la que se encuentra el aeropuerto.

Estos detalles permiten que la tabla “airports_data” proporcione una visión general completa de las ubicaciones e información de los aeropuertos. Esto sirve como referencia para la tabla “flights” a través de las claves foráneas flights_arrival_airport_fkey y flights_departure_airport_fkey, facilitando la asociación entre los vuelos y sus respectivos aeropuertos de salida y llegada.

Cómo configurar el registro y las variables de entorno

En esta sección, configuraremos el registro para proporcionar mensajes informativos y manejar errores a lo largo del código. También configuraremos variables de entorno para almacenar de forma segura información confidencial y parámetros de configuración. Estas prácticas mejoran la legibilidad, mantenibilidad y seguridad del código.

Configuración del registro

Utilizaremos el módulo de registro integrado de Python, logging, para configurar un sistema de registro. El registro es esencial para realizar un seguimiento del flujo de ejecución del código y capturar información importante o errores.

Se llama al método logging.basicConfig para definir el formato de los mensajes de registro y establecer el nivel de registro en INFO.

import logginglogging.basicConfig(    format="%(asctime)s | %(levelname)s : %(message)s", level=logging.INFO)
  • Formato: El parámetro format especifica el formato de los mensajes de registro. En este caso, cada entrada de registro incluye una marca de tiempo, nivel de registro (por ejemplo, INFO, ERROR), y el mensaje de registro real.
  • Niveles de registro: Establecemos el nivel de registro en INFO, lo que significa que el registrador registrará mensajes informativos. También se pueden utilizar niveles de gravedad más altos, como WARNING o ERROR, para problemas más críticos.

Puedes obtener más información sobre el registro en Python en este tutorial.

Cómo gestionar las variables de entorno

Crearemos un archivo .env para gestionar las variables de entorno. Las variables de entorno se utilizan para almacenar información confidencial y configuraciones, lo que nos permite mantener separados estos datos del código.

En este caso, establecemos variables de entorno para las credenciales del correo electrónico y los detalles de conexión a la base de datos.

export EMAIL=export PASSWORD=export EMAIL_PORT=587export SMTP_SERVER=smtp.gmail.comexport DB_HOSTNAME=localhostexport DB_NAME=airlinesexport DB_PORT=5432export DB_USERNAME=postgresexport DB_PASSWORD=postgres

Aquí hay un desglose de las variables:

  • EMAIL: La dirección de correo electrónico que se utilizará para enviar correos electrónicos.
  • PASSWORD: La contraseña asociada con la cuenta de correo electrónico.
  • EMAIL_PORT: El puerto para el servidor de correo electrónico (por ejemplo, servidor SMTP). El valor predeterminado es 587 para la transmisión de correo electrónico segura (TLS/SSL).
  • SMTP_SERVER: La dirección del servidor SMTP, a menudo específica del proveedor de servicios de correo electrónico.
  • DB_HOSTNAME: El nombre de host o dirección IP del servidor de base de datos PostgreSQL.
  • DB_NAME: El nombre de la base de datos PostgreSQL.
  • DB_PORT: El número de puerto para conectarse a la base de datos (el valor predeterminado es 5432 para PostgreSQL).
  • DB_USERNAME: El nombre de usuario para autenticarse en la base de datos.
  • DB_PASSWORD: La contraseña para el usuario de la base de datos.

Asegúrate de ejecutar source .env para cargar las variables de entorno.

Al utilizar variables de entorno, los datos sensibles como contraseñas y credenciales de correo electrónico se pueden mantener separados del código, reduciendo el riesgo de exposición accidental o acceso no autorizado. El código puede acceder a estas variables en tiempo de ejecución, garantizando seguridad y flexibilidad en la configuración.

Cómo extraer los datos de la base de datos

Comencemos estableciendo las configuraciones de la base de datos.

import loggingimport oslogging.basicConfig(    format="%(asctime)s | %(levelname)s : %(message)s", level=logging.INFO)DB_CONFIG = {    "host": os.environ.get("DB_HOSTNAME"),    "database": os.environ.get("DB_NAME"),    "user": os.environ.get("DB_USERNAME"),    "password": os.environ.get("DB_PASSWORD"),}

El diccionario DB_CONFIG se utiliza para almacenar los parámetros de configuración para la conexión a la base de datos PostgreSQL. Estos parámetros incluyen el host, el nombre de la base de datos, el nombre de usuario y la contraseña. Estos valores se pueden establecer a través de variables de entorno.

Cómo conectarse a la base de datos

Antes de extraer los datos de la base de datos, necesitamos conectarnos a nuestra base de datos. Utilizaremos la librería psycopg2 para conectarnos a la base de datos PostgreSQL.

Comenzaremos definiendo una clase DataExporter que contendrá métodos para extraer la base de datos y generar la hoja de Excel.

class DataExporter:    def __init__(self):        """Inicializa DataExporter con la configuración de la base de datos."""        self.db_config = DB_CONFIG

El constructor de la clase inicializa DataExporter con la configuración de la base de datos almacenada en el diccionario DB_CONFIG.

A continuación, vamos a definir un método que se conecte a la base de datos.

...import psycopg2...class DataExporter:    def __init__(self):        """Inicializa DataExporter con la configuración de la base de datos."""        self.db_config = DB_CONFIG    def __connect_to_database(self) -> None:        """        Establece una conexión con la base de datos PostgreSQL.        Lanza:            Exception: Si no se puede establecer una conexión con la base de datos.        """        try:            self.conn = psycopg2.connect(**self.db_config)            self.cursor = self.conn.cursor()            logging.info("Conectado a la base de datos")        except Exception as e:            logging.error(                "Error al conectar a la base de datos: %s", e)            raise

El método privado __connect_to_database se encarga de establecer una conexión con la base de datos PostgreSQL. Utiliza la librería psycopg2 para crear una conexión y un cursor para ejecutar consultas SQL. Si la conexión falla, registra un error y lanza una excepción.

Puedes aprender más sobre el manejo de excepciones en Python aquí.

Cómo obtener datos de la base de datos

Ahora vamos a definir otro método privado que se conecta a la base de datos y obtiene el número total de reservas y el monto total de la base de datos.

from datetime import datetimeclass DataExporter:    ...    def __fetch_from_database(self, start_timestamp, end_timestamp) -> list | None:        """        Obtiene datos de reserva de la base de datos para un rango de tiempo dado.        Args:            start_timestamp (datetime): El inicio del rango de tiempo.            end_timestamp (datetime): El final del rango de tiempo.        Returns:            list: Una lista que contiene los datos de reserva (num_bookings, total_amount) o None si ocurre un error.        """        self.__connect_to_database()        query = f"""        SELECT COUNT(*) AS num_bookings, SUM(total_amount) AS total_amount        FROM bookings        WHERE book_date >= {int(start_timestamp.timestamp()) * 1000} AND book_date <= {int(end_timestamp.timestamp()) * 1000}        """        logging.info(            "Extrayendo datos de reservas de la base de datos para el inicio del timestamp=%s y el final del timestamp=%s",            start_timestamp,            end_timestamp,        )        result = None        try:            self.cursor.execute(query)            result = list(self.cursor.fetchone())            result.append(                f'{start_timestamp.strftime("%d %b, %Y")} - {end_timestamp.strftime("%d %b, %Y")}'            )            logging.info(                "Se extrajeron exitosamente los datos de reservas de la base de datos para el inicio del timestamp=%s y el final del timestamp=%s",                start_timestamp,                end_timestamp,            )        except Exception as e:            logging.error(                "Ocurrió un error al extraer los datos de reservas de la base de datos: %s", e            )        return result

Este método privado recupera los datos de reserva de la base de datos para un rango de tiempo especificado.

Recibe dos objetos datetime como argumentos, start_timestamp y end_timestamp. También construye una consulta SQL para recuperar el conteo de reservas y el monto total de reservas para ese rango de tiempo.

La consulta se ejecuta, y si es exitosa, el método devuelve los datos como una tupla. Convertimos la tupla en una lista y agregamos el intervalo de tiempo para el cual se extrajeron los datos a la lista. Si ocurre un error durante la interacción con la base de datos, se registra un error y se devuelve None.

Con el método anterior, puedes extraer datos de reserva para diversos intervalos de tiempo, ya sea para una semana, un mes, un año o cualquier rango de tiempo personalizado de tu elección.

Cómo estructurar los datos de reserva con la clase BookingInfo

En esta sección, definiremos una clase BookingInfo en booking_info.py, la cual funciona como un contenedor estructurado para los datos de reserva recuperados de la base de datos. La clase encapsula la información relacionada con las reservas, lo que facilita el trabajo y la presentación de los datos.

from decimal import Decimalclass BookingInfo:    def __init__(self, data_list: list):        """        Inicializa BookingInfo con datos de la base de datos.        Args:            data_list (list): Una lista que contiene los datos de reserva (total_bookings, total_amount, timestamp).        Nota:            El total_amount se convierte en un tipo Decimal.        """        self.__total_bookings, self.__total_amount, self.__timestamp = data_list        self.__total_amount = Decimal(self.__total_amount) if self.__total_amount else Decimal(0)    def __str__(self) -> str:        """        Devuelve una representación en forma de cadena de BookingInfo.        Returns:            str: Una cadena en el formato "Reservas totales: X, Monto total: $Y".        """        return f"Reservas totales: {self.__total_bookings}, Monto total: ${self.__total_amount}"    def get_total_bookings(self) -> int:        """        Obtiene el número total de reservas.        Returns:            int: El número total de reservas.        """        return self.__total_bookings    def get_total_amount(self) -> Decimal:        """        Obtiene el monto total de la reserva como un Decimal.        Returns:            Decimal: El monto total de la reserva.        """        return self.__total_amount    def get_timestamp(self) -> str:        """        Obtiene la marca de tiempo asociada con los datos de reserva.        Returns:            str: La marca de tiempo como una cadena.        """        return self.__timestamp

La clase BookingInfo está diseñada para organizar y representar los datos de reserva devueltos por la base de datos. Recibe una lista de valores que contiene el total de reservas, el monto total de reserva y una marca de tiempo como entrada, y convierte el monto total en un tipo Decimal. La clase ofrece métodos para acceder y presentar estos datos de manera estructurada.

El constructor de la clase BookingInfo toma una lista de data_list como entrada, la cual se espera que sea una lista que contenga los siguientes elementos:

  • total_bookings: Un entero que representa el número total de reservas.
  • total_amount: Un valor de punto flotante que representa el monto total de reserva.
  • timestamp: Una marca de tiempo asociada con los datos de reserva.

El método __init__ inicializa las variables de instancia privadas (__total_bookings, __total_amount y __timestamp) con los valores de data_list. También convierte el __total_amount a un tipo decimal para un manejo preciso de los valores monetarios.

El método __str__ se implementa para proporcionar una representación en forma de cadena del objeto BookingInfo. Devuelve una cadena en el formato “Reservas totales: X, Monto total: $Y”, donde X es el número total de reservas y Y es el monto total de reserva formateado como dólares.

Métodos Getter

La clase proporciona tres métodos getter para acceder a los datos encapsulados:

  • get_total_bookings(): Devuelve el número total de reservas como un entero.
  • get_total_amount(): Devuelve el monto total de reserva como un tipo Decimal.
  • get_timestamp(): Devuelve la marca de tiempo asociada con los datos de reserva como una cadena.

Al encapsular los datos de reserva dentro de la clase BookingInfo, el código está más organizado, legible y reutilizable. Este enfoque estructurado simplifica el manejo de la información de reserva en toda la aplicación, haciéndola más intuitiva para trabajar y presentar los datos.

Cómo convertir los datos en una hoja de Excel

Ahora que puedes recuperar datos de la base de datos para un rango de tiempo específico, también puedes generar una hoja de Excel basada en los datos extraídos.

Para hacer esto, definamos otro método privado para crear la hoja de Excel.

...import pandas as pdfrom booking_info import BookingInfo...class DataExporter:	...    def __convert_to_excelsheet(self, data: list, sheet_name: str):        """        Convierte los datos obtenidos en una hoja de Excel.        Args:            data (list): Una lista que contiene datos de reserva.            sheet_name (str): Nombre de la hoja de Excel a crear.        Raises:            ValueError: Si hay un error al convertir los datos en una hoja de Excel.        """        try:            booking_info = BookingInfo(data)            data = {                "": ["Reservas Totales", "Cantidad Total ($)"],                booking_info.get_timestamp(): [                    booking_info.get_total_bookings(),                    booking_info.get_total_amount(),                ],            }            logging.info("Convirtiendo los datos en un dataframe de pandas")            df = pd.DataFrame(data)            logging.info("Insertando los datos en la hoja de Excel")            with pd.ExcelWriter(sheet_name, engine="xlsxwriter") as writer:                df.to_excel(writer, sheet_name="Sheet1", index=False)            logging.info("Datos insertados exitosamente en la hoja de Excel")        except ValueError as e:            logging.error("Error al convertir los datos en Excel: %s", e)

El método __convert_to_excelsheet dentro de la clase DataExporter es responsable de estructurar y convertir los datos de reserva extraídos en una hoja de Excel.

Acepta dos parámetros de entrada. El primer parámetro, data, se espera que sea una lista que contenga datos de reserva específicos. Estos datos incluyen el número total de reservas, el monto total de reserva y una marca de tiempo para la cual se extrajeron los datos. El segundo parámetro, sheet_name, representa el nombre deseado para la hoja de Excel que contendrá los datos formateados.

Un aspecto clave del método es la estructuración de los datos. Para lograr esto, el método inicia la creación de un objeto BookingInfo, al que se hace referencia como booking_info. El objeto BookingInfo proporciona una representación estructurada de los datos de reserva, lo que simplifica el formato y la presentación posteriores.

Después de crear el objeto booking_info, se genera un nuevo diccionario llamado data. Este diccionario está diseñado para estructurar los datos en un formato adecuado para la conversión en una hoja de Excel.

El diccionario consta de dos pares clave-valor:

  • El primer par utiliza una cadena vacía como clave y contiene una lista con dos valores de encabezado, “Reservas Totales” y “Cantidad Total ($)”.
  • El segundo par utiliza la marca de tiempo obtenida de booking_info.get_timestamp() como clave e incluye una lista con dos elementos: el número total de reservas (booking_info.get_total_bookings()) y el monto total de reserva (booking_info.get_total_amount()).

Este diccionario permite que los datos se inserten en la hoja de Excel de la siguiente manera:

Captura-de-pantalla-2023-10-29-135512
Hoja de Excel de muestra

Luego, el diccionario estructurado data se convierte en un DataFrame de pandas, al que nos referimos como df. Los DataFrames son estructuras de datos comúnmente utilizadas para manejar datos tabulares en Python. Este paso agiliza la manipulación y exportación de los datos para su posterior procesamiento o visualización.

Para crear la hoja de Excel, el código utiliza el gestor de contexto pd.ExcelWriter con el motor “xlsxwriter”. Este gestor de contexto garantiza que el archivo de Excel esté preparado adecuadamente para la inserción de datos. El parámetro sheet_name se proporciona para especificar el nombre de la hoja dentro del archivo de Excel.

Los datos dentro del DataFrame, df, se escriben en la hoja de Excel. Se utiliza el método to_excel en conjunto con el objeto writer, y el parámetro index se establece en False. Esta configuración específica excluye los números de fila predeterminados que normalmente se incluyen en las hojas de Excel.

Cómo combinar las funcionalidades

Ahora vamos a escribir un método público que los usuarios pueden utilizar para extraer los datos de la base de datos y convertirlos en un archivo de hoja de Excel.

...class DataExporter:	    ...        def generar_hojadeexcel(        self,        start_timestamp: datetime,        end_timestamp: datetime,        sheet_name: str = "Datos de Reservas.xlsx",    ) -> bool:        """        Genera una hoja de Excel con los datos de reservas para un rango de tiempo especificado.        Args:            start_timestamp (datetime): El inicio del rango de tiempo.            end_timestamp (datetime): El final del rango de tiempo.            sheet_name (str, opcional): Nombre de la hoja de Excel a crear. Por defecto es "Datos de Reservas.xlsx".        Returns:            bool: True si se generó la hoja de Excel con éxito, de lo contrario False.        Nota:            Este método registra errores pero no lanza excepciones para evitar interrumpir el flujo de trabajo.        """        data = self.__fetch_from_database(start_timestamp, end_timestamp)        if data is not None:            self.__convertir_a_hojadeexcel(data, sheet_name)            return True        else:            logging.error("No hay datos para convertir en hoja de Excel")            return False

Este método acepta varios parámetros, incluyendo start_timestamp y end_timestamp, que definen el inicio y el fin del periodo de tiempo para la extracción de datos. También hay un parámetro opcional sheet_name que permite al usuario especificar el nombre de la hoja de Excel. Por defecto, la hoja se llama “Datos de Reservas.xlsx” para proporcionar una opción predeterminada conveniente.

Al ejecutarse, el método inicia el proceso de recuperación de datos llamando al método __fetch_from_database, un método privado interno de la clase, con el rango de tiempo especificado.

Si la recuperación de datos es exitosa y hay datos disponibles, el método procede a llamar al método __convert_to_excelsheet. Este estructura y formatea los datos para insertarlos en la hoja de Excel.

Si, por otro lado, no hay datos disponibles para el rango de tiempo proporcionado, el método registra un mensaje de error y devuelve “False” para indicar que la generación de la hoja de Excel no tuvo éxito.

Cómo enviar un correo electrónico con el informe de datos de reservas

En esta sección, aprenderás cómo puedes usar Python para enviar un correo electrónico con un informe de datos de reservas como adjunto.

Crea un archivo mailer.py y agrega el siguiente contenido:

import loggingimport osimport smtplibimport sslfrom email import encodersfrom email.mime.base import MIMEBasefrom email.mime.multipart import MIMEMultipartfrom email.mime.text import MIMETextlogging.basicConfig(    format="%(asctime)s | %(levelname)s : %(message)s", level=logging.INFO)SMTP_SERVER = os.environ.get("SMTP_SERVER")PORT = os.environ.get("EMAIL_PORT")EMAIL = os.environ.get("EMAIL")PASSWORD = os.environ.get("PASSWORD")def enviar_correo_electronico(para_email: str, asunto: str, nombre_adjunto: str):    """    Envia un correo electrónico con un adjunto al destinatario especificado.    Args:        para_email (str): La dirección de correo electrónico del destinatario.        asunto (str): El asunto del correo electrónico.        nombre_adjunto (str): El nombre del archivo adjunto.    Nota:        Esta función asume que el servidor SMTP requiere cifrado TLS.    Raises:        smtplib.SMTPException: Si hay un problema al enviar el correo electrónico.    """    message = MIMEMultipart()    message["From"] = EMAIL    message["To"] = para_email    message["Subject"] = asunto    body = "Hola\n\nPor favor encuentra adjunto tu informe.\n\nGracias"    message.attach(MIMEText(body, "plain"))    with open(nombre_adjunto, "rb") as archivo:        parte = MIMEBase(            "application", "vnd.openxmlformats-officedocument.spreadsheetml.sheet"        )        parte.set_payload(archivo.read())    encoders.encode_base64(parte)    parte.add_header(        "Content-Disposition",        f"attachment; filename= {nombre_adjunto}",    )    logging.info(f"Adjuntando {nombre_adjunto} al correo electrónico")    message.attach(parte)    texto = message.as_string()    context = ssl.create_default_context()    with smtplib.SMTP(SMTP_SERVER, PORT) as server:        logging.info(f"Enviando correo electrónico a {para_email}")        server.starttls(context=context)        server.login(EMAIL, PASSWORD)        server.sendmail(EMAIL, para_email, texto)        logging.info(f"Correo electrónico enviado con éxito a {para_email}")

Como de costumbre, hemos configurado el registro y las variables de entorno en nuestro script.

La funcionalidad principal está encapsulada dentro de la función send_email. Esta función toma tres parámetros:

  1. to_email: La dirección de correo electrónico del destinatario.
  2. subject: El asunto del correo electrónico.
  3. attachment_name: El nombre de archivo del adjunto, que debe ser el informe de datos de reservas en este contexto.

Dentro de la función, construimos un mensaje de correo electrónico utilizando la clase MIMEMultipart. Este mensaje incluye la dirección de correo electrónico del remitente, la dirección de correo electrónico del destinatario, el asunto y un cuerpo de texto sin formato con un mensaje sencillo.

El script permite adjuntar el informe de datos de reservas como un adjunto. Lee el archivo adjunto, lo codifica y lo agrega al mensaje de correo electrónico. Esto asegura que el destinatario pueda acceder y descargar fácilmente el informe de datos desde el correo electrónico.

Puedes aprender cómo agregar adjuntos al enviar correos electrónicos usando Python aquí.

La función create_default_context de la biblioteca ssl crea un contexto SSL seguro para la comunicación por correo electrónico. Finalmente, el script se conecta al servidor SMTP, inicia sesión utilizando la dirección de correo electrónico y la contraseña del remitente, envía el correo electrónico y registra un mensaje de éxito al transmitirlo correctamente.

Cómo probar el flujo

Finalmente, probemos el flujo de la aplicación.

finalmente-hour-timeout

En esta sección, automatizaremos los informes mensuales. Crea un archivo main.py y agrega el siguiente contenido:

from exporter import DataExporterfrom datetime import datetimefrom mailer import send_emailstart_timestamp = datetime(2023, 5, 28, 00, 00, 00)  # 28 de mayo de 2023 a las 00:00:00end_timestamp = datetime(2023, 8, 20, 23, 59, 59)  # 20 de agosto de 2023 a las 23:59:59exporter = DataExporter()if exporter.generate_excelsheet(        start_timestamp, end_timestamp, sheet_name="Bookings Data.xlsx"):    send_email("[email protected]", "Tu informe", "Bookings Data.xlsx")

En el código anterior, creamos dos objetos de marca de tiempo, start_timestamp y end_timestamp, para especificar un rango de tiempo. Tenemos la fecha de inicio configurada para el 28 de mayo de 2023 a medianoche y la fecha de finalización configurada para el 20 de agosto de 2023 justo antes de medianoche.

A continuación, creamos una instancia de la clase DataExporter, que maneja la exportación de datos y la generación de hojas de Excel. Se llama al método generate_excelsheet de esta instancia con las marcas de tiempo previamente definidas para crear un informe relacionado con las reservas.

Finalmente, el código envía un correo electrónico con la hoja de Excel generada como adjunto utilizando la función send_email.

Cómo programar la aplicación

A continuación, nuestro objetivo es automatizar el proceso de programación de informes. Nuestro objetivo es programar las entregas de informes para dos escenarios distintos: todos los lunes para los datos de la semana anterior y el primer día de cada mes para la información del mes anterior.

Para programar la ejecución, necesitarás instalar la biblioteca schedule:

pip install schedule

Una vez que la biblioteca esté instalada, así es como puedes automatizar los informes mensuales y semanales:

import schedulefrom exporter import DataExporterfrom datetime import datetime, timedeltafrom mailer import send_emaildef main():    today = datetime.now()    sheet_name = "Bookings Data.xlsx"    if today.weekday() == 0:  # Comprueba si es lunes (0 significa lunes)        # Es lunes, obtén los datos de la semana anterior (de lunes a domingo)        start_timestamp = (today - timedelta(days=7)                           ).replace(hour=0, minute=0, second=0, microsecond=0)        end_timestamp = (today - timedelta(days=1)                         ).replace(hour=23, minute=59, second=59, microsecond=0)        sheet_name = "Informe semanal.xlsx"    elif today.day == 29:        # Es el primer día del mes, obtén los datos del mes anterior        start_timestamp = (today.replace(day=1) - timedelta(days=1)                           ).replace(day=1, hour=0, minute=0, second=0, microsecond=0)        end_timestamp = (today.replace(day=1) - timedelta(days=1)                         ).replace(hour=23, minute=59, second=59, microsecond=0)        sheet_name = "Informe mensual.xlsx"    exporter = DataExporter()    exporter.generate_excelsheet(        start_timestamp, end_timestamp, sheet_name)    send_email("[email protected]",               "Tu informe", sheet_name)schedule.every().day.at("00:00").do(main)while True:    schedule.run_pending()

El script anterior utiliza la biblioteca schedule para ejecutar la función main diariamente a medianoche. La función main calcula los timestamps para la extracción de datos y la generación de la hoja de Excel. Después de generar la hoja de Excel, el script la envía por correo electrónico a un destinatario especificado.

Si el script se ejecuta un lunes, se configura para generar un informe semanal. Calcula el start_timestamp y end_timestamp de la semana anterior. El start_timestamp se establece en el lunes anterior a medianoche (00:00:00), y el end_timestamp se establece en el domingo anterior justo antes de la medianoche (23:59:59). La hoja de Excel se llama “Informe semanal.xlsx”.

El primer día del mes, el script cambia su enfoque a la generación de un informe mensual. Calcula el start_timestamp y end_timestamp para abarcar todo el mes anterior. El start_timestamp se establece en el primer día del mes anterior a medianoche (00:00:00), mientras que el end_timestamp se establece en el último día del mes anterior justo antes de la medianoche (23:59:59). La hoja de Excel se llama “Informe mensual.xlsx”.

Conclusión

En este tutorial, aprendiste cómo puedes aprovechar Python para automatizar la generación de un informe y enviarlo a los destinatarios del correo electrónico. ¡Espero que hayas encontrado útil el tutorial!

Alcance futuro

  • Puedes agregar los destinatarios de correo electrónico en una base de datos y obtener su lista desde allí en lugar de codificarlos directamente en el código. Esto hará que la aplicación sea más configurable.
  • También puedes utilizar trabajos cron para automatizar la ejecución del script todos los días a medianoche. En ese caso, no necesitarás la biblioteca schedule.

Aquí tienes un enlace al Repositorio de código de Github.


Leave a Reply

Your email address will not be published. Required fields are marked *