Spaces:
Running
Running
Claude Sonnet 5.5
perf(sheets): bouclier anti-429 global (cache GET gspread, invalidation sur ecriture, secours perime, reessais)
dd08ad5 Download src/core/sheets_cache.py from klydekushy/Vortex-Flux: direct link, hf CLI and curl.
- Browser
- Download file 5.78 kB
-
https://huggingface.co/spaces/klydekushy/Vortex-Flux/resolve/main/src/core/sheets_cache.py
- Command line
-
hf download hf://spaces/klydekushy/Vortex-Flux/src/core/sheets_cache.py
-
curl -L -o sheets_cache.py https://huggingface.co/spaces/klydekushy/Vortex-Flux/resolve/main/src/core/sheets_cache.py
5.78 kB
| """ | |
| 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 | |
| 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() | |
| 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)) | |
| 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() | |