# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """Gestion des caches Ré-écrite pour ScoDoc8, utilise flask_caching et REDIS ScoDoc est maintenant multiprocessus / mono-thread, avec un cache partagé. """ # API pour les caches: # sco_cache.MyCache.get( formsemestre_id) # => sco_cache.MyCache.get(formsemestre_id) # # sco_cache.MyCache.delete(formsemestre_id) # sco_cache.MyCache.delete_many(formsemestre_id_list) # # Bulletins PDF: # sco_cache.SemBulletinsPDFCache.get(formsemestre_id, version) # sco_cache.SemBulletinsPDFCache.set(formsemestre_id, version, filename, pdfdoc) # sco_cache.SemBulletinsPDFCache.delete(formsemestre_id) suppr. toutes les versions # Evaluations: # sco_cache.EvaluationCache.get(evaluation_id), set(evaluation_id, value), delete(evaluation_id), # import traceback from flask import g import app from app import db, log from app.scodoc import notesdb as ndb from app.scodoc import sco_utils as scu from app.scodoc.sco_exceptions import ScoException CACHE = None # set in app.__init__.py class ScoDocCache: """Cache for ScoDoc objects. keys are prefixed by the current departement: g.scodoc_dept MUST be set. """ timeout = None # ttl, infinite by default prefix = "" verbose = False # if true, verbose logging (debug) @classmethod def _get_key(cls, oid): return g.scodoc_dept + "_" + cls.prefix + "_" + str(oid) @classmethod def get(cls, oid): """Returns cached object, or None""" key = cls._get_key(oid) try: return CACHE.get(key) except: log(f"XXX CACHE Warning: error in get(key={key})") log(traceback.format_exc()) return None @classmethod def set(cls, oid, value): """Store value""" key = cls._get_key(oid) if cls.verbose: log( f"{cls.__name__}.set key={key}, type={type(value).__name__}, timeout={cls.timeout}" ) try: status = CACHE.set(key, value, timeout=cls.timeout) if not status: log("Error: cache set failed !") except Exception as exc: log("XXX CACHE Warning: error in set !!!") log(exc) status = None return status @classmethod def delete(cls, oid): """Remove from cache""" # if cls.verbose: # log(f"{cls.__name__}.delete({oid})") CACHE.delete(cls._get_key(oid)) @classmethod def delete_many(cls, oids): """Remove multiple keys at once""" if cls.verbose: log(f"{cls.__name__}.delete_many({oids})") # delete_many seems bugged: # CACHE.delete_many([cls._get_key(oid) for oid in oids]) for oid in oids: cls.delete(oid) class EvaluationCache(ScoDocCache): """Cache for evaluations. Clé: evaluation_id Valeur: { 'etudid' : note } """ prefix = "EVAL" @classmethod def invalidate_sem(cls, formsemestre_id): "delete evaluations in this formsemestre from cache" from app.models.evaluations import Evaluation from app.models.moduleimpls import ModuleImpl evaluation_ids = [ e.id for e in Evaluation.query.join(ModuleImpl).filter_by( formsemestre_id=formsemestre_id ) ] cls.delete_many(evaluation_ids) @classmethod def invalidate_all_sems(cls): "delete all evaluations in current dept from cache" evaluation_ids = [ x[0] for x in ndb.SimpleQuery( """SELECT e.id FROM notes_evaluation e, notes_moduleimpl mi, notes_formsemestre s WHERE s.dept_id=%(dept_id)s AND s.id = mi.formsemestre_id AND mi.id = e.moduleimpl_id; """, {"dept_id": g.scodoc_dept_id}, ) ] cls.delete_many(evaluation_ids) class AbsSemEtudCache(ScoDocCache): """Cache pour les comptes d'absences d'un étudiant dans un semestre. Ce cache étant indépendant des semestres, le compte peut être faux lorsqu'on change les dates début/fin d'un semestre. C'est pourquoi il expire après timeout secondes. Le timeout evite aussi d'éliminer explicitement ces éléments cachés lors des suppressions d'étudiants ou de semestres. Clé: etudid + "_" + date_debut + "_" + date_fin Valeur: (nb_abs, nb_abs_just) """ prefix = "ABSE" timeout = 60 * 60 # ttl 60 minutes class SemBulletinsPDFCache(ScoDocCache): """Cache pour les classeurs de bulletins PDF d'un semestre. Document pdf assez volumineux. La clé inclut le type de bulletin (version). Clé: formsemestre_id_version Valeur: (filename, pdfdoc) """ prefix = "SBPDF" timeout = 12 * 60 * 60 # ttl 12h @classmethod def invalidate_sems(cls, formsemestre_ids): """Clear cached pdf for all given formsemestres""" for version in scu.BULLETINS_VERSIONS: oids = [ str(formsemestre_id) + "_" + version for formsemestre_id in formsemestre_ids ] cls.delete_many(oids) class SemInscriptionsCache(ScoDocCache): """Cache les inscriptions à un semestre. Clé: formsemestre_id Valeur: liste d'inscriptions [ {'formsemestre_inscription_id': 'SI78677', 'etudid': '1234', 'formsemestre_id': 'SEM012', 'etat': 'I', 'etape': ''}, ... ] """ prefix = "SI" duration = 12 * 60 * 60 # ttl 12h class TableRecapCache(ScoDocCache): """Cache table recap (pour TableRecap) Clé: formsemestre_id Valeur: le html (