Spaces:
Running
Running
File size: 5,783 Bytes
397b23d c20487a 397b23d c20487a 397b23d c73b85c 397b23d c20487a e3cb468 c20487a e3cb468 c20487a e3cb468 c20487a e6b6e8c e3cb468 c20487a 397b23d dd08ad5 397b23d c20487a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | """
MODULE SHEETS_CACHE — cache partage pour les lectures Google Sheets
=====================================================================
Chaque interaction Streamlit (clic sur un bouton, coche une case, ouverture
d'un expander...) relance l'integralite du script de la page. Sans cache,
cela relit a chaque fois toutes les feuilles Google Sheets utilisees par
cette page — plusieurs lectures par clic — ce qui declenche regulierement
des erreurs 429 (quota Google Sheets depasse) sur les pages a fort trafic
de clics (validation des prets, remboursements...).
On met donc ces lectures en cache le temps d'un court TTL, et on vide
explicitement le cache juste apres toute ecriture (append_row/update_cell/
update) pour ne jamais laisser un agent decider sur des donnees perimees.
Usage :
sh = client.open(sheet_name)
df = get_cached_df(sh, sheet_name, "Prets_Requests")
...
ws.append_row([...])
clear_sheets_cache() # apres CHAQUE ecriture
Ne pas utiliser ce cache pour une lecture dont le resultat sert
immediatement a decider d'ecrire (ex: garde anti-doublon avant un
append_row) : lire directement la feuille dans ce cas precis, sinon le
cache peut masquer une ecriture toute recente faite par un autre onglet/
tentative precedente.
ATTENTION — numerisation automatique : get_cached_df()/get_cached_dfs_batch()
reproduisent le comportement PAR DEFAUT de get_all_records() (numericise_all
de gspread), qui devine si une valeur texte est un nombre. Un module qui
appelle deliberement get_all_records(numericise_ignore=['all']) (voir
Check_Up_Loans.py : la numerisation automatique corrompt les decimales au
format francais, "1,5" devient 15) doit continuer a lire directement la
feuille sans passer par ce cache, sous peine de reintroduire cette
corruption silencieuse.
"""
import pandas as pd
import streamlit as st
from core.gspread_fr import numericise_fr as numericise
DEFAULT_TTL = 60 # secondes — assez court pour rester a jour, assez long pour absorber une rafale de clics
@st.cache_data(ttl=DEFAULT_TTL, show_spinner=False)
def cached_get_all_records(_sh, sheet_key, worksheet_name):
"""Lecture mise en cache de get_all_records(). `_sh` (objet Spreadsheet
deja ouvert) est ignore pour le hachage du cache (convention Streamlit
du prefixe underscore) ; `sheet_key` (le nom du classeur, une chaine)
sert de cle de cache a sa place."""
return _sh.worksheet(worksheet_name).get_all_records()
@st.cache_data(ttl=DEFAULT_TTL, show_spinner=False)
def cached_get_all_values(_sh, sheet_key, worksheet_name):
"""Equivalent de cached_get_all_records() pour get_all_values()."""
return _sh.worksheet(worksheet_name).get_all_values()
def get_cached_df(sh, sheet_key, worksheet_name):
"""Retourne un DataFrame pandas a partir d'une lecture mise en cache."""
return pd.DataFrame(cached_get_all_records(sh, sheet_key, worksheet_name))
@st.cache_data(ttl=DEFAULT_TTL, show_spinner=False)
def _cached_batch_values(_sh, sheet_key, worksheet_names: tuple):
"""Lit plusieurs feuilles EN UN SEUL appel API (values_batch_get) au lieu
d'un appel par feuille : une page qui a besoin de N feuilles ne consomme
plus que 1 lecture au lieu de N sur le quota partage (60 lectures/minute,
tous utilisateurs de l'app confondus — voir la note sur le code 429)."""
resp = _sh.values_batch_get(list(worksheet_names))
return [vr.get("values", []) for vr in resp.get("valueRanges", [])]
def get_cached_dfs_batch(sh, sheet_key, worksheet_names, numericise_ignore_columns=None):
"""Variante groupee de get_cached_df() pour plusieurs feuilles a la fois
(1 appel API au lieu de N). Reproduit le comportement de get_all_records()
(numericisation des valeurs via l'utilitaire interne de gspread, lignes
completees a la longueur de l'en-tete), sauf pour les colonnes indiquees
dans `numericise_ignore_columns`, qui restent brutes pour preserver leur
format decimal localise. Retourne {nom_feuille: DataFrame}."""
raw = _cached_batch_values(sh, sheet_key, tuple(worksheet_names))
numericise_ignore_columns = numericise_ignore_columns or {}
# `numericise_ignore_columns` conserve brutes les colonnes localisées choisies.
out = {}
for name, values in zip(worksheet_names, raw):
if not values:
out[name] = pd.DataFrame()
continue
header, *rows = values
width = len(header)
# Lignes plus longues que l'en-tete (cellules ecrites hors des colonnes
# nommees, ex. geoloc KYC ajoutee en fin de ligne) : tronquees comme
# le fait get_all_records(), sinon pd.DataFrame leve une erreur.
padded = [row[:width] + [""] * (width - len(row)) for row in rows]
ignored_indexes = {
index for index, column in enumerate(header)
if column in numericise_ignore_columns.get(name, ())
}
numericised = [
[value if index in ignored_indexes else numericise(value) for index, value in enumerate(row)]
for row in padded
]
out[name] = pd.DataFrame(numericised, columns=header)
return out
def clear_sheets_cache():
"""A appeler juste apres toute ecriture (append_row/update_cell/update)
sur n'importe quelle feuille, pour que le prochain rerun relise des
donnees fraiches au lieu de servir le cache perime. Vide tout le cache
(plutot qu'une entree precise) : les ecritures sont rares comparees aux
lectures, le cout d'un cache un peu trop large est negligeable face au
risque d'afficher des donnees obsoletes apres une ecriture."""
from core import sheets_quota
sheets_quota.vider()
cached_get_all_records.clear()
cached_get_all_values.clear()
_cached_batch_values.clear()
|