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()