Optimizing Streamlit Application

I am currently working on a Streamlit application that has performance issues, particularly in terms of loading speed and responsiveness. I am looking for practical tips, strategies, and any recommended tools or methods that can help enhance the performance of my Streamlit app. Are there specific coding techniques, configuration settings, or plugins that have proven effective in optimizing Streamlit applications? What are the best practices for optimizing Streamlit applications to improve performance and responsiveness? Any advice on memory management, data handling, or efficient component usage would be greatly appreciated.

@Josue_Becerra change the title like request/advice, so that many people can be viewed and get solution faster

First you need to identify the main bottleneck, with measures. Trying to optimize without that is just a nice way of procrastinating.

Hello @Josue_Becerra can you please provide me your code or else the github link of that project.So that i can help you out with that issue.

# Reposicion.py
import os
import pandas as pd
import streamlit as st
from SystemResources.GestorIADashBoard.ModuloReposicion import Control_Ini  # Ajusta las rutas de importación si son necesarias
from streamlit_extras.dataframe_explorer import dataframe_explorer
from email.message import EmailMessage
from SystemResources.GestorIADashBoard.ModuloVentas import Controlador_Ventas as DashControlador
import ssl
import smtplib
from dotenv import load_dotenv
import firebirdsql
import pandas as pd
import streamlit as st
import os
from openpyxl import load_workbook
from SystemResources.GestorIADashBoard.ModuloTraslado.Utils import LectorExcel as excel
from SystemResources.Utilidades.FuncionesGenerales import round_values
import json
from datetime import datetime, timedelta
import platform
import yaml  # Importa yaml para cargar archivos de configuración en formato YAML.
from yaml.loader import SafeLoader  # Importa SafeLoader para cargar archivos YAML de forma segura.

def checkSO():
    so = platform.system()
    return so


def get_connection():

    so= checkSO()
    print(f"Gestoria se esta ejecutando en sistema operativo {so}")
    ruta = ""
    if so=="Windows":
        ruta = "C:/Proyectos/GESTORIA" 
    else:
        ruta = "/Proyectos/GESTORIA" 

        # Cargar configuración desde un archivo YAML.
    with open(os.getcwd() + '/config.yaml') as file:
        config = yaml.load(file, Loader=SafeLoader)  # Carga la configuración utilizando SafeLoader.

        if 'db_path' not in st.session_state:
            st.session_state['db_path'] = ruta + config['db_path'] 
    

    rutaDB = st.session_state['db_path'] # Para su uso debe estar establecida la carpeta "C:/Users/GesnasDataBase/VALERY3.MDF"    
    # Establece conexión con la base de datos FirebirdSQL usando parámetros dados.
    print('Conectandose con la base de datos')
    return firebirdsql.connect(
        host='localhost', 
        database= rutaDB,
        port=3050,  # Este es el puerto por defecto; cámbialo si es necesario
        user='SYSDBA', 
        password='masterkey',
        charset='ISO8859_1'  # Especifica la codificación aquí
    )


@st.cache_data(show_spinner=False)
def run_query_existencias_unificado():

    ''' 
    Autor: Luis Liscano
    Descripción: Esta función ejecuta una consulta SQL en la base de datos para consultar
    la existencia en los almacenes y almacena los resultados en un DataFrame de pandas.

    Correcciones:
    - Fecha: 18/04/2024, Creado en la version 0.2
    
    - 

    -

    '''
    df_Productosexcluir=extraer_deExcel_productos_excluidos()
    pexc_list = df_Productosexcluir['CODIGO_PRODUCTO_EXCLUIR'].to_list()
    # Añadir comillas simples a cada elemento de la lista
    pexc_list_quoted = ["'" + str(item) + "'" for item in pexc_list]

    # Generar un string de la forma 'a','b','c','d','e'
    pexc_string = ','.join(pexc_list_quoted)

    # Obtiene una conexión a la base de datos.
    conn1 = get_connection()

    # Define la consulta SQL para extraer datos específicos.
    query1 = f'''


        SELECT 
            d.NOMBRE AS CATEGORIA, 
            pt.MARCA AS MARCA,
            ptd.PRODUCTO_CODIGO, 
            pt.NOMBRE, 
            pt.COSTO_UNITARIO,
            SUM(ptd.EXISTENCIA_MAXIMA) AS TOTAL_EXISTENCIA_MAX,
            SUM(ptd.EXISTENCIA_MINIMA) AS TOTAL_EXISTENCIA_MIN, 
            SUM(ptd.EXISTENCIA_ACTUAL) AS TOTAL_EXISTENCIA
        FROM PRODUCTOS_TERMINADOS_DEPOSITOS AS ptd
        JOIN PRODUCTOS_TERMINADOS AS pt ON pt.CODIGO_PRODUCTO = ptd.PRODUCTO_CODIGO
        JOIN DEPARTAMENTOS AS d ON d.CODIGO = pt.DEPARTAMENTO_CODIGO
        JOIN DEPOSITOS AS dep ON dep.CODIGO = ptd.DEPOSITO_CODIGO
        WHERE ptd.PRODUCTO_CODIGO NOT IN ({pexc_string})
        GROUP BY CATEGORIA, MARCA, ptd.PRODUCTO_CODIGO, pt.NOMBRE, pt.COSTO_UNITARIO

    
    '''

    # Ejecuta la consulta y almacena el resultado en un DataFrame de pandas
    dataQ1 = pd.read_sql_query(query1, conn1)

    # Cierra la conexión a la base de datos.
    conn1.close()
    
    dataQ1['TOTAL_EXISTENCIA_MAX'] = dataQ1['TOTAL_EXISTENCIA_MAX'].round()
    dataQ1['TOTAL_EXISTENCIA_MIN'] = dataQ1['TOTAL_EXISTENCIA_MIN'].round()
    dataQ1['TOTAL_EXISTENCIA'] = dataQ1['TOTAL_EXISTENCIA'].round()
    dataQ1['COSTO_UNITARIO'] = dataQ1['COSTO_UNITARIO'].round()
    

    # Retorna el DataFrame con los resultados de la consulta.
    return dataQ1



@st.cache_data(show_spinner=False)
def query_existencia():
    df_existencia = DashModelo.run_query_existencias_unificado()
    return df_existencia

# Reposicion.py
import os
import pandas as pd
import streamlit as st
from SystemResources.GestorIADashBoard.ModuloReposicion import Control_Ini  # Ajusta las rutas de importación si son necesarias
from streamlit_extras.dataframe_explorer import dataframe_explorer
from email.message import EmailMessage
from SystemResources.GestorIADashBoard.ModuloVentas import Controlador_Ventas as DashControlador
import ssl
import smtplib
from dotenv import load_dotenv

load_dotenv()

def enviar_email(destinatario, asunto, mensaje, archivo_adjunto=None):
    email_sender = "lliscanobimodal@gmail.com"
    password = os.getenv("CLAVE")

    em = EmailMessage()
    em["From"] = email_sender
    em["To"] = destinatario
    em["Subject"] = asunto
    em.set_content(mensaje)

    if archivo_adjunto:
        with open(archivo_adjunto, 'rb') as f:
            file_data = f.read()
            file_name = os.path.basename(archivo_adjunto)
        em.add_attachment(file_data, maintype='application', subtype='octet-stream', filename=file_name)

    context = ssl.create_default_context()

    with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as smtp:
        smtp.login(email_sender, password)
        smtp.sendmail(email_sender, destinatario, em.as_string())

def main():

    tab_names = ["Reposición", "Inventario", "Email"]
    st.session_state['todo_bien'] = True

    st.session_state['df_reposicionHistorica'] = pd.DataFrame() 
    
    numMeses = 10

    st.info(f'##### Módulo Reposición del inventario Seleccionado.')  # Muestra un mensaje de bienvenida en la página.

    existencia_df = Control_Ini.query_existencia()
    

    st.sidebar.header("Selecciona los filtros aplicables: ")  # Añade un encabezado en la barra lateral para los filtros.

    categoria = st.sidebar.multiselect("Elige la familia de producto", existencia_df["CATEGORIA"].unique())

    df3 = Control_Ini.categoriaFiltro(categoria, existencia_df)

    marca = st.sidebar.multiselect("Elige la marca", df3["MARCA"].unique())

    df4 = Control_Ini.marcaFiltro(marca, df3)

    rango = st.sidebar.selectbox('Seleccina el rango de existencia:', ['Sin aplicar', 'Por encima del rango', 'En rango', 'Por debajo del rango'])


    filtered_df = Control_Ini.filtradoGeneral_1(categoria, marca, existencia_df, df3, df4, rango)

    
    filtered_df.rename(columns={'PRODUCTO_CODIGO': 'CODIGO', 'TOTAL_EXISTENCIA_MAX': 'MAX', 'TOTAL_EXISTENCIA_MIN': 'MIN', 'TOTAL_EXISTENCIA': 'EXISTENCIA', 'COSTO_UNITARIO': 'COSTO'}, inplace=True)


    columnas_preseleccionadas = ['CATEGORIA', 'NOMBRE', 'CODIGO', 'MAX', 'MIN', 'EXISTENCIA', 'COSTO']




    

    on = st.sidebar.toggle("¿Reponer en función del promedio mensual?", key="toggle_reponer_promedio", value=False)


        
    if 'facturacion_mensual_historica' in st.session_state:
        facturacion_mensual_historica = st.session_state['facturacion_mensual_historica']
    else:
         facturacion_df = DashControlador.query_facturacion()  # Filtra el DataFrame original por el rango de fechas seleccionado.
         facturacion_df["TOTAL"] = facturacion_df["TOTAL"].round()
         facturacion_mensual_historica = facturacion_df
         st.session_state['facturacion_mensual_historica'] = facturacion_mensual_historica

    archivo_csv = st.session_state['compraVentasProductos']


    
    
    # Cargar el archivo CSV existente
    if os.path.exists(archivo_csv):
        df_reposicionHistorica = pd.read_csv(archivo_csv)
        Control_Ini.actualizar_historico(facturacion_mensual_historica, filtered_df)
        print("Archivo CSV cargado")
    else:

        df_reposicionHistorica = Control_Ini.ReposicionHistorica(facturacion_mensual_historica, filtered_df)
        Control_Ini.guardar_data_frame_csv(df_reposicionHistorica, archivo_csv)
        print("Archivo CSV creado y guardado")

    # Calcular "Cantidad a reponer sugerida"
    df_reposicionHistorica['Cantidad a reponer sugerida'] = 0
    df_reposicionHistorica['Promedio Ventas Mensual'] = df_reposicionHistorica['Promedio Ventas Mensual'].round()
    df_reposicionHistorica['Promedio Compras Mensual'] = df_reposicionHistorica['Promedio Compras Mensual'].round()


    df_reposicionHistorica.rename(columns={'PRODUCTO_CODIGO': 'CODIGO', 'TOTAL_EXISTENCIA_MAX': 'MAX', 'TOTAL_EXISTENCIA_MIN': 'MIN', 'TOTAL_EXISTENCIA': 'EXISTENCIA', 'COSTO_UNITARIO': 'COSTO'}, inplace=True)

    #st.dataframe(df_reposicionHistorica)

    if on:
        st.warning("Se repondrá en función del promedio de las cantidades vendidas de cada producto.")
        
        numMeses = st.sidebar.number_input('Ingresa el número de meses a reponer', min_value=1, max_value=12, value=numMeses)
        st.success(f'¡Se establecieron los valores sugeridos a reponer en base al promedio de ventas para {numMeses} meses!')

        # df_reposicionHistorica['Cantidad a reponer sugerida'] = df_reposicionHistorica['Promedio Ventas Mensual'] * numMeses - df_reposicionHistorica['EXISTENCIA']
        #agregado 01/10/2024 a las 04:50 PM para aplicar la formula de reposicion y validando cuando el promedio de ventas es 0
        df_reposicionHistorica['Cantidad a reponer sugerida'] = df_reposicionHistorica.apply(
         lambda row: 0 if row['Promedio Ventas Mensual'] <= 0 or row['Promedio Ventas Mensual'] <= row['EXISTENCIA'] else row['Promedio Ventas Mensual'] * numMeses - row['EXISTENCIA'],
              axis=1
            )
        # Filtrar productos cuya "Cantidad a reponer sugerida" sea menor a 'TOTAL_EXISTENCIA_MAX'
        #df_reposicionHistorica = df_reposicionHistorica[df_reposicionHistorica['TOTAL_EXISTENCIA'] < df_reposicionHistorica['Cantidad a reponer sugerida']]
        # Filtrar productos cuya "Cantidad a reponer sugerida" sea mayor que cero
        df_reposicionHistorica = Control_Ini.filtrar_reposicion_historica(filtered_df, df_reposicionHistorica)



    else:
        st.warning("Se repondrá en función de la existencia máxima de cada producto.")

        df_reposicionHistorica['Cantidad a reponer sugerida'] = df_reposicionHistorica.apply(
            lambda row: 0 if row[f'EXISTENCIA'] >= row['MAX'] else row['MAX'] - row[f'EXISTENCIA'],
            axis=1
        )



        '''
        ultimo_mes = sorted(facturacion_mensual_historica['MES'].unique())[-1]
        df_reposicionHistorica['Cantidad a reponer sugerida'] = df_reposicionHistorica.apply(
            lambda row: 0 if row[f'Existencia_{ultimo_mes}'] >= row['TOTAL_EXISTENCIA_MAX'] else row['TOTAL_EXISTENCIA_MAX'] - row[f'Existencia_{ultimo_mes}'],
            axis=1
        )
        '''

        df_reposicionHistorica = Control_Ini.filtrar_reposicion_historica(filtered_df, df_reposicionHistorica)


    # Crear "Cantidad a reponer confirmada" inicialmente igual a "Cantidad a reponer sugerida"
    df_reposicionHistorica['Cantidad a reponer confirmada'] = df_reposicionHistorica['Cantidad a reponer sugerida']


    if on:
        df_reposicionHistorica['¿Reponer?'] = df_reposicionHistorica.apply(
        lambda row: True if row['EXISTENCIA'] < row['Cantidad a reponer sugerida'] else False,
        axis=1
        )

    else:
        df_reposicionHistorica['¿Reponer?'] = df_reposicionHistorica.apply(
        lambda row: True if row['EXISTENCIA'] < row['MAX'] else False,
        axis=1
        )


    # Configurar los gráficos de existencias utilizando AreaChartColumn y BarChartColumn
    column_config = {
        "Existencias Totales": st.column_config.BarChartColumn(
            "EXISTENCIA",
            help="El comportamiento del inventario en los últimos meses",
            width="medium"
        ),
        "Ventas Mensuales": st.column_config.AreaChartColumn(
            "VENTAS",
            help="Las ventas mensuales en los últimos meses",
            width="medium"
        ),
        "Cantidad a reponer sugerida": st.column_config.NumberColumn("Cantidad a reponer sugerida"),
        "Cantidad a reponer confirmada": st.column_config.NumberColumn("Cantidad a reponer confirmada"),
        
        "¿Reponer?": st.column_config.CheckboxColumn(
            "¿Reponer?",
            help="Selecciona si deseas reponer este producto",
            
        )
    }

    # Lista de columnas a deshabilitar
    disabled_columns = ['CATEGORIA', 'CODIGO', 'NOMBRE', 'EXISTENCIA', 'PROMEDIO', 'REPONER']

    ''' OJO REVISAR 26-09-2024
    # Añadir las columnas de ventas y compras para cada mes en la configuración de gráficos
    for mes in sorted(facturacion_mensual_historica['MES'].unique()):
        column_config[f"Ventas_{mes}"] = st.column_config.NumberColumn(f"Ventas_{mes}")
        column_config[f"Compras_{mes}"] = st.column_config.NumberColumn(f"Compras_{mes}")
    '''

    

    df_reposicionHistorica.rename(columns={'Cantidad a reponer sugerida': 'REPONER', 'Cantidad a reponer confirmada': 'CONFIRMAR', 'Promedio Ventas Mensual': 'PROMEDIO'}, inplace=True)
    #st.dataframe(df_reposicionHistorica)

    tabs = st.tabs(tab_names)
    with tabs[1]:

        filtered_df = Control_Ini.restablecer_indice(filtered_df)

        filtered_df = Control_Ini.seleccionar_columnas(filtered_df, columnas_preseleccionadas)

        
        

        if not st.session_state['df_erros']:

            mostrar_df = Control_Ini.colorear_productos(filtered_df)

            st.session_state['todo_bien'] = True


            

            st.dataframe(mostrar_df)

        else:
             st.session_state['todo_bien'] = False


    if st.session_state['todo_bien'] == True:

        with tabs[0]:
            columnas_preseleccionadas1 = ['CATEGORIA', 'CODIGO', 'NOMBRE', 'Existencias Totales', 'Ventas Mensuales', 'PROMEDIO', 'EXISTENCIA', 'REPONER', 'CONFIRMAR',  '¿Reponer?']



            df_reposicionHistorica = Control_Ini.restablecer_indice(df_reposicionHistorica)

            df_reposicionHistorica = Control_Ini.seleccionar_columnas(df_reposicionHistorica, columnas_preseleccionadas1)

            
            if not st.session_state['df_erros']:
                df_reposicionHistorica = Control_Ini.ordenar_por_categoria(df_reposicionHistorica)

                # Inicializar el DataFrame en session_state si no existe
                if 'df_reposicionHistorica' in st.session_state:
                    st.session_state['df_reposicionHistorica'] = df_reposicionHistorica

                # Mostrar el editor de datos con el DataFrame de session_state
                df_respHist_nuevo = st.data_editor(
                    st.session_state['df_reposicionHistorica'],
                    column_config=column_config,
                    hide_index=True,
                    disabled=disabled_columns  # Deshabilitar las columnas especificadas
                )

                # Botón para confirmar los cambios y generar el archivo Excel
                if st.button('Generar orden de compras en Excel', key='exportar_a_excel'):
                    # Actualizar el DataFrame en session_state con los cambios hechos por el usuario
                    st.session_state['df_reposicionHistorica'] = df_respHist_nuevo.copy()

                    # Calcular el costo total de la cantidad a reponer
                    st.session_state['df_reposicionHistorica'] = Control_Ini.filtrar_reposicion_historica(
                        filtered_df, st.session_state['df_reposicionHistorica']
                    )
                    st.session_state['df_reposicionHistorica'] = Control_Ini.CandidatoFiltro(st.session_state['df_reposicionHistorica'])

                    # Obtener las rutas de plantilla y de salida
                    template_path = st.session_state['template_orden_compra_path']
                    output_path = st.session_state['orden_compra_path']

                    # Exportar el DataFrame actualizado a Excel
                    Control_Ini.exportar_a_excel(st.session_state['df_reposicionHistorica'], template_path, output_path)

                    # Descargar el archivo Excel
                    Control_Ini.descargar_archivo(output_path)

                    # Mensaje de confirmación
                    st.success('Orden de compra generada correctamente.')

                        
                        
    with tabs[2]:
        st.header("Enviar Email")

        st.markdown("""
        <style>
        .st-emotion-cache-9ycgxx {
            visibility: hidden;
        }
        .st-emotion-cache-9ycgxx:before {
            content: 'Arrastra y suelta el archivo aquí o haz clic para seleccionar';
            visibility: visible;
        }
        </style>
        """, unsafe_allow_html=True)

            
    # Cargar proveedores desde el archivo JSON
        proveedores_data = Control_Ini.cargar_proveedores()

        if proveedores_data and "PROVEEDORES" in proveedores_data:
            # Filtrar proveedores activos
            proveedores_activos = [p for p in proveedores_data["PROVEEDORES"] if p['status'].lower() == 'activo']

            if proveedores_activos:
                # Crear opciones para selección solo con proveedores activos
                # Opciones basadas únicamente en el correo de los proveedores activos
                opciones = [f"{proveedor['correo']}" for proveedor in proveedores_activos]

                # JavaScript para prevenir la edición del input
                js_code = """
                <script>
                document.addEventListener('DOMContentLoaded', (event) => {
                    const targetNode = document.body;
                    const config = { childList: true, subtree: true };
                    const callback = function(mutationsList, observer) {
                        for(let mutation of mutationsList) {
                            if (mutation.type === 'childList') {
                                const selectInputs = document.querySelectorAll('.stSelectbox input');
                                selectInputs.forEach(input => {
                                    input.setAttribute('readonly', true);
                                    input.style.pointerEvents = 'none';
                                    input.parentElement.style.pointerEvents = 'auto';
                                });
                            }
                        }
                    };
                    const observer = new MutationObserver(callback);
                    observer.observe(targetNode, config);
                });
                </script>
                """

                # Inyectar el JavaScript en la página
                st.components.v1.html(js_code, height=0)

                # Crear el selectbox utilizando solo correos
                destinatario = st.selectbox(
                    "Seleccione un destinatario para la orden de compra:",
                    options=[""] + opciones
                )

                # Inicializamos la variable para almacenar el correo seleccionado
                correo_seleccionado = None

                # Mostramos la información del proveedor seleccionado
                if destinatario:
                    # Buscar al proveedor con el correo seleccionado
                    proveedor = next((p for p in proveedores_activos if p['correo'] == destinatario), None)
                    if proveedor:
                        # Mostramos la información del proveedor
                        st.subheader(f"Proveedor: {proveedor.get('nombre', 'Nombre no disponible')}")
                        st.write(f"**Código:** {proveedor.get('codigo', 'Código no disponible')}")
                        st.write(f"**Persona de Contacto:** {proveedor.get('persona_contacto', 'No disponible')}")
                        st.write(f"**Correo:** {proveedor['correo']}")
                        st.write(f"**Status:** {proveedor.get('status', 'No disponible')}")
                        st.write("---")
                        
                        # Guardamos el correo seleccionado
                        correo_seleccionado = proveedor['correo']

                    else:
                        st.write("No se ha seleccionado ningún destinatario.")

                        # Guardar el correo seleccionado en una variable temporal
                    if correo_seleccionado:
                        st.session_state.correo_seleccionado = correo_seleccionado  # Guardar en la sesión temporalmente

        # Campos para el asunto y el mensaje del correo
        asunto = st.text_input("Asunto del correo", "Orden de Compra")
        mensaje = st.text_area("Mensaje del correo", "Se envía la orden de compra")

        # Campo para adjuntar archivos (opcional)
        archivo_adjunto = st.file_uploader("Adjuntar archivo", type=["pdf", "docx", "xlsx", "csv", "jpg", "png", "xlsm"])

        # Botón para enviar el correo
        if st.button("Enviar correo"):
            # Validar que todos los campos estén completos

            if not destinatario or not asunto or not mensaje:
                st.error("Por favor, completa todos los campos.")
            else:
                archivo_adjunto_path = None
                if archivo_adjunto:
                    archivo_adjunto_path = f"temp_{archivo_adjunto.name}"

                    # Guardar el archivo subido temporalmente
                    with open(archivo_adjunto_path, "wb") as f:
                        f.write(archivo_adjunto.read())

                try:

                    # Llamar a la función para enviar el correo
                    enviar_email(destinatario, asunto, mensaje, archivo_adjunto_path)
                    st.success("Correo enviado exitosamente!")
                except Exception as e:
                    st.error(f"Error al enviar el correo: {e}")
                finally:
                    # Eliminar el archivo adjunto temporal si existe
                    if archivo_adjunto_path and os.path.exists(archivo_adjunto_path):
                        os.remove(archivo_adjunto_path)

if __name__ == '__main__':
    main()