Génération tableau API

This commit is contained in:
Emmanuel Viennet 2024-07-23 07:07:29 +02:00
parent 36547afb0b
commit 9ca86e7900
2 changed files with 160 additions and 26 deletions

View File

@ -19,7 +19,8 @@ import app.scodoc.sco_assiduites as scass
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.api import api_bp as bp from app.api import api_bp as bp
from app.api import api_web_bp, get_model_api_object, tools 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 ( from app.models import (
Assiduite, Assiduite,
Evaluation, Evaluation,

View File

@ -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 xml.etree.ElementTree as ET
import re import re
from app.auth.models import Permission
class COLORS: class COLORS:
""" """
@ -437,15 +439,20 @@ def _create_question_mark_group(coords, href):
return group return group
# point d'entrée de la commande `flask gen-api-map`
def gen_api_map(app, endpoint_start="api"): def gen_api_map(app, endpoint_start="api"):
""" """
Fonction permettant de générer une carte SVG de l'API de ScoDoc 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 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 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 # Création du token racine
api_map = Token("") api_map = Token("")
doctable_lines: dict[str, dict] = {}
# Parcours de toutes les routes de l'application # Parcours de toutes les routes de l'application
for rule in app.url_map.iter_rules(): for rule in app.url_map.iter_rules():
# On ne garde que les routes de l'API / APIWEB # 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 child.method = method
current_token.add_child(child) 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 # On met à jour le token courant pour le prochain segment
current_token = child 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" + "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): 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]: def _get_doc_lines(keyword, doc) -> list[str]:
""" """
Renvoie les lignes de la doc qui suivent le mot clé keyword 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: La doc doit contenir des lignes de la forme:
@ -638,10 +671,18 @@ def _get_doc_lines(keyword, doc) -> list[str]:
return [] return []
# On récupère les lignes de la doc qui correspondent au keyword (enfin on espère) # On récupère les lignes de la doc qui correspondent au keyword (enfin on espère)
kw_lines = lines[kw_index + 2 :] 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 return kw_lines
def parse_doc_name(doc): def parse_doc_name(doc) -> str:
""" """
renvoie le nom de la route à partir de la docstring 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 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: <type:nom_param>} (ex: {assiduite_id : <int:assiduite_id>}) renvoie un dictionnaire {param: <type:nom_param>} (ex: {assiduite_id : <int:assiduite_id>})
@ -691,33 +732,125 @@ def parse_query_doc(doc):
return query 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__": if __name__ == "__main__":
# Exemple d'utilisation de la classe Token # Exemple d'utilisation de la classe Token
# Exemple simple de création d'un arbre de Token # Exemple simple de création d'un arbre de Token
root = Token("api") # root = Token("api")
child1 = Token("assiduites", leaf=True) # child1 = Token("assiduites", leaf=True)
child1.func_name = "assiduites_get" # child1.func_name = "assiduites_get"
child2 = Token("count") # child2 = Token("count")
child22 = Token("all") # child22 = Token("all")
child23 = Token( # child23 = Token(
"query", # "query",
query={ # query={
"etat": "<string:etat>", # "etat": "<string:etat>",
"moduleimpl_id": "<int:moduleimpl_id>", # "moduleimpl_id": "<int:moduleimpl_id>",
"count": "<int:count>", # "count": "<int:count>",
"formsemestre_id": "<int:formsemestre_id>", # "formsemestre_id": "<int:formsemestre_id>",
}, # },
) # )
child3 = Token("justificatifs", "POST") # child3 = Token("justificatifs", "POST")
child3.func_name = "justificatifs_post" # child3.func_name = "justificatifs_post"
root.add_child(child1) # root.add_child(child1)
child1.add_child(child2) # child1.add_child(child2)
child2.add_child(child22) # child2.add_child(child22)
child2.add_child(child23) # child2.add_child(child23)
root.add_child(child3) # 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]))