diff --git a/app/api/assiduites.py b/app/api/assiduites.py index 8bf50805e..6b75b0998 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -19,7 +19,8 @@ import app.scodoc.sco_assiduites as scass import app.scodoc.sco_utils as scu from app.api import api_bp as bp from app.api import api_web_bp, get_model_api_object, tools -from app.decorators import permission_required, scodoc +from app.api import api_permission_required as permission_required +from app.decorators import scodoc from app.models import ( Assiduite, Evaluation, diff --git a/tools/create_api_map.py b/tools/create_api_map.py index 2db5996ab..fd1fcdcc1 100644 --- a/tools/create_api_map.py +++ b/tools/create_api_map.py @@ -7,6 +7,8 @@ Script permettant de générer une carte SVG de l'API de ScoDoc import xml.etree.ElementTree as ET import re +from app.auth.models import Permission + class COLORS: """ @@ -437,15 +439,20 @@ def _create_question_mark_group(coords, href): return group +# point d'entrée de la commande `flask gen-api-map` def gen_api_map(app, endpoint_start="api"): """ Fonction permettant de générer une carte SVG de l'API de ScoDoc Elle récupère les routes de l'API et les transforme en un arbre de Token puis génère un fichier SVG à partir de cet arbre """ + + print("DEBUG", app.view_functions["apiweb.user_info"].scodoc_permission) # Création du token racine api_map = Token("") + doctable_lines: dict[str, dict] = {} + # Parcours de toutes les routes de l'application for rule in app.url_map.iter_rules(): # On ne garde que les routes de l'API / APIWEB @@ -500,6 +507,28 @@ def gen_api_map(app, endpoint_start="api"): child.method = method current_token.add_child(child) + # Gestion de doctable + doctable = parse_doctable_doc(func.__doc__ or "") + href = func_name.replace("_", "-") + if child.query and not href.endswith("-query"): + href += "-query" + + permissions: str + try: + permissions: str = ", ".join( + sorted(Permission.permissions_names(func.scodoc_permission)) + ) + except AttributeError: + permissions = "Aucune permission requise" + + doctable_lines[func_name] = { + "doctable": doctable, + "method": method, + "nom": func_name, + "href": href, + "permission": permissions, + } + # On met à jour le token courant pour le prochain segment current_token = child @@ -510,6 +539,9 @@ def gen_api_map(app, endpoint_start="api"): + "Vous pouvez la consulter à l'adresse suivante : /tmp/api_map.svg" ) + # On génère le tableau à partir de doctable_lines + _gen_table(sorted(doctable_lines.values(), key=lambda x: x["nom"])) + def _get_bbox(element, x_offset=0, y_offset=0): """ @@ -617,6 +649,7 @@ def generate_svg(element, fname): def _get_doc_lines(keyword, doc) -> list[str]: """ Renvoie les lignes de la doc qui suivent le mot clé keyword + Attention : s'arrête à la première ligne vide La doc doit contenir des lignes de la forme: @@ -638,10 +671,18 @@ def _get_doc_lines(keyword, doc) -> list[str]: return [] # On récupère les lignes de la doc qui correspondent au keyword (enfin on espère) kw_lines = lines[kw_index + 2 :] + + # On s'arrête à la première ligne vide + first_empty_line: int + try: + first_empty_line: int = kw_lines.index("") + except ValueError: + first_empty_line = len(kw_lines) + kw_lines = kw_lines[:first_empty_line] return kw_lines -def parse_doc_name(doc): +def parse_doc_name(doc) -> str: """ renvoie le nom de la route à partir de la docstring @@ -658,7 +699,7 @@ def parse_doc_name(doc): return name_lines[0] if name_lines else None -def parse_query_doc(doc): +def parse_query_doc(doc) -> dict[str, str]: """ renvoie un dictionnaire {param: } (ex: {assiduite_id : }) @@ -691,33 +732,125 @@ def parse_query_doc(doc): return query +def parse_doctable_doc(doc) -> dict[str, str]: + """ + Retourne un dictionnaire représentant les informations du tableau d'api + à partir de la doc (DOC-TABLE) + + éléments optionnels: + - `permissions` permissions nécessaires pour accéder à la route (ScoView, AbsChange, ...) + - `href` nom (sans #) de l'ancre dans la page ScoDoc9API + + DOC-TABLE + --------- + permissions: ScoView + href: une-fonction + """ + + doc_lines: list[str] = _get_doc_lines("DOC-TABLE", doc) + + # On crée un dictionnaire + table = {} + + # on parcourt les lignes de la doc + for line in doc_lines: + # On sépare le paramètre et sa valeur + param, value = line.split(":") + # On met à jour le dictionnaire + table[param.strip()] = value.strip() + + return table + + +def _gen_table_line(nom, href, method, permission, doctable: dict): + """ + Génère une ligne de tableau markdown + + | nom de la route| methode HTTP| Permission | + """ + lien: str = href + if "href" in doctable: + lien: str = doctable.get("href") + nav: str = f"[{nom}]({'#'+lien})" + + table: str = "|" + for string in [nav, method, doctable.get("permissions") or permission]: + table += f" {string} |" + + return table + + +def _gen_table_head() -> str: + """ + Génère la première ligne du tableau markdown + """ + + headers: str = "| Route | Méthode | Permission |" + line: str = "|---|---|---|" + + return f"{headers}\n{line}\n" + + +def _gen_table(lines: list[dict], filename: str = "/tmp/api_table.md") -> str: + """ + Génère un tableau markdown à partir d'une liste de lignes + + lines : liste de dictionnaire au format : + + - doctable : dict généré par parse_doctable_doc + - nom : nom de la fonction associée à la route + - method : GET ou POST + - permission : Permissions de la route (auto récupérée) + + """ + table = _gen_table_head() + table += "\n".join([_gen_table_line(**line) for line in lines]) + + with open(filename, "w", encoding="UTF-8") as f: + f.write(table) + + print( + f"Le tableau a été généré avec succès. Vous pouvez le consulter à l'adresse suivante : {filename}" + ) + + if __name__ == "__main__": # Exemple d'utilisation de la classe Token # Exemple simple de création d'un arbre de Token - root = Token("api") - child1 = Token("assiduites", leaf=True) - child1.func_name = "assiduites_get" - child2 = Token("count") - child22 = Token("all") - child23 = Token( - "query", - query={ - "etat": "", - "moduleimpl_id": "", - "count": "", - "formsemestre_id": "", - }, - ) - child3 = Token("justificatifs", "POST") - child3.func_name = "justificatifs_post" + # root = Token("api") + # child1 = Token("assiduites", leaf=True) + # child1.func_name = "assiduites_get" + # child2 = Token("count") + # child22 = Token("all") + # child23 = Token( + # "query", + # query={ + # "etat": "", + # "moduleimpl_id": "", + # "count": "", + # "formsemestre_id": "", + # }, + # ) + # child3 = Token("justificatifs", "POST") + # child3.func_name = "justificatifs_post" - root.add_child(child1) - child1.add_child(child2) - child2.add_child(child22) - child2.add_child(child23) - root.add_child(child3) + # root.add_child(child1) + # child1.add_child(child2) + # child2.add_child(child22) + # child2.add_child(child23) + # root.add_child(child3) - group_element = root.to_svg_group() + # group_element = root.to_svg_group() - generate_svg(group_element, "/tmp/api_map.svg") + # generate_svg(group_element, "/tmp/api_map.svg") + dt: dict = parse_doctable_doc(parse_doctable_doc.__doc__) + md: str = "POST" + hf: str = "assiduites-query" + + doc: dict = { + "doctable": dt, + "method": md, + "href": hf, + } + print(_gen_table([doc]))