forked from ScoDoc/ScoDoc
245 lines
8.1 KiB
Python
245 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""Construction des fichiers exemples pour la documentation.
|
|
|
|
Usage:
|
|
python tests/api/make_samples.py [entry_names]
|
|
python tests/api/make_samples.py -i <filepath> [entrynames]
|
|
|
|
Si entry_names est spécifié, la génération est restreinte aux exemples cités.
|
|
Exemple:
|
|
python make_samples departements departement_formsemestres
|
|
|
|
Doit être exécutée immédiatement apres une initialisation de la base pour test API!
|
|
(car dépendant des identifiants générés lors de la création des objets)
|
|
|
|
Modifer le /opt/scodoc/.env pour pointer sur la base test
|
|
SCODOC_DATABASE_URI="postgresql:///SCODOC_TEST_API"
|
|
|
|
puis re-créer cette base
|
|
tools/create_database.sh --drop SCODOC_TEST_API
|
|
flask db upgrade
|
|
flask sco-db-init --erase
|
|
flask init-test-database
|
|
|
|
et lancer le serveur test:
|
|
flask run --debug
|
|
```
|
|
|
|
Cet utilitaire prend en argument le fichier de nom `samples.csv` contenant la description
|
|
des exemples (séparés par une tabulation (\t), une ligne par exemple)
|
|
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md).
|
|
Plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra
|
|
chacun des exemples
|
|
* l'url utilisée
|
|
* la permission nécessaire (par défaut ScoView)
|
|
* la méthode GET,POST à utiliser (si commence par #, la ligne est ignorée)
|
|
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json
|
|
|
|
Implémentation:
|
|
Le code complète une structure de données (Samples) qui est un dictionnaire de set
|
|
(indicé par le nom des exemples).
|
|
Chacun des éléments du set est un exemple (Sample)
|
|
Quand la structure est complète, on génére tous les fichiers textes
|
|
- nom de l'exemple
|
|
- un ou plusieurs exemples avec pour chacun
|
|
- l'url utilisée
|
|
- les arguments éventuels
|
|
- le résultat
|
|
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples)
|
|
qui est créé ou écrasé si déjà existant.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import re
|
|
from collections import defaultdict
|
|
from pprint import pprint as pp
|
|
|
|
import urllib3
|
|
import json
|
|
|
|
import pandas as pd
|
|
|
|
from setup_test_api import (
|
|
API_PASSWORD,
|
|
API_URL,
|
|
API_USER,
|
|
APIError,
|
|
CHECK_CERTIFICATE,
|
|
get_auth_headers,
|
|
GET,
|
|
POST,
|
|
SCODOC_URL,
|
|
)
|
|
|
|
DATA_DIR = "/tmp/samples/"
|
|
SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"
|
|
|
|
|
|
class SampleException(Exception):
|
|
pass
|
|
|
|
|
|
class Sample:
|
|
def __init__(self, url, method="GET", permission="ScoView", content=None):
|
|
self.content = content
|
|
self.permission = permission
|
|
self.url = url
|
|
self.method = method
|
|
self.result = None
|
|
self.output = "json"
|
|
if permission == "ScoView":
|
|
HEADERS = get_auth_headers("test", "test")
|
|
elif permission == "ScoSuperAdmin":
|
|
HEADERS = get_auth_headers("admin_api", "admin_api")
|
|
elif permission == "UsersAdmin":
|
|
HEADERS = get_auth_headers("admin_api", "admin_api")
|
|
else:
|
|
raise SampleException(f"Bad permission : {permission}, url={self.url}")
|
|
if self.method == "GET":
|
|
self.result = GET(self.url, HEADERS)
|
|
elif self.method == "POST":
|
|
if self.content == "":
|
|
self.result = POST(self.url, headers=HEADERS)
|
|
else:
|
|
HEADERS["Content-Type"] = "application/json ; charset=utf-8"
|
|
try:
|
|
data = json.loads(self.content)
|
|
except json.decoder.JSONDecodeError as exc:
|
|
raise ValueError(
|
|
f"JSON invalide: {self.content}\nurl={self.url}"
|
|
) from exc
|
|
self.result = POST(self.url, data, HEADERS)
|
|
elif self.method[0] != "#":
|
|
raise SampleException(f'Bad method : "{self.method}", url={self.url}')
|
|
self.shorten()
|
|
with open("sample_TEST.json.md", "tw", encoding="utf-8") as f:
|
|
self.dump(f)
|
|
|
|
def _shorten(self, item):
|
|
"Abrège les longues listes: limite à 2 éléments et affiche '...' etc. à la place"
|
|
if isinstance(item, list):
|
|
return [self._shorten(child) for child in item[:2] + ["..."]]
|
|
return item
|
|
|
|
def shorten(self):
|
|
"Abrège le résultat"
|
|
self.result = self._shorten(self.result)
|
|
|
|
def pp(self):
|
|
print(f"------ url: {self.url}")
|
|
print(f"method: {self.method}")
|
|
print(f"content: {self.content}")
|
|
print(f"permission: {self.permission}")
|
|
pp(self.result, indent=4)
|
|
|
|
def dump(self, file):
|
|
self.url = self.url.replace("?date_courante=2022-07-20", "")
|
|
|
|
file.write(f"#### {self.method} {self.url}\n")
|
|
if len(self.content) > 0:
|
|
file.write("> `Content-Type: application/json`\n")
|
|
file.write("> \n")
|
|
file.write(f"> `{self.content}`\n\n")
|
|
|
|
file.write("```json\n")
|
|
content = json.dumps(self.result, indent=4, sort_keys=True)
|
|
content = content.replace("... etc.", "...")
|
|
# regexp for date like: "2022-08-14T10:01:44.043869+02:00"
|
|
regexp = re.compile(
|
|
r'"(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?"'
|
|
)
|
|
content = regexp.sub('"2022-08-20T12:00:00.000000+02:00"', content)
|
|
file.write(content)
|
|
file.write("\n```\n\n")
|
|
|
|
|
|
class Samples:
|
|
def __init__(self, entry_names):
|
|
"""Entry_names: la liste des entrées à reconstruire.
|
|
si None, la totalité des lignes de samples.csv est prise en compte
|
|
"""
|
|
self.entries = defaultdict(set)
|
|
self.entry_names = entry_names
|
|
|
|
def add_sample(self, line):
|
|
entry = line["entry_name"]
|
|
url = line["url"]
|
|
method = line["method"]
|
|
permission = line["permission"]
|
|
content = line["content"]
|
|
if self.entry_names is None or entry in self.entry_names:
|
|
if method[0] == "#":
|
|
detail = "**ignored**"
|
|
elif content == "":
|
|
detail = ""
|
|
else:
|
|
detail = f": {content}"
|
|
print(f"{entry:50} {method:5} {url:50} {detail}")
|
|
sample = Sample(url, method, permission, content)
|
|
self.entries[entry].add(sample)
|
|
|
|
def pp(self):
|
|
for entry, samples in self.entries.items():
|
|
print(f"=== {entry}")
|
|
for sample in samples:
|
|
sample.pp()
|
|
|
|
def dump(self):
|
|
for entry, samples in self.entries.items():
|
|
with open(f"{DATA_DIR}sample_{entry}.json.md", "tw", encoding="utf-8") as f:
|
|
f.write(f"### {entry}\n\n")
|
|
# Trié de façon à rendre le fichier indépendant de l'ordre des résultats
|
|
for sample in sorted(samples, key=lambda s: s.url):
|
|
sample.dump(f)
|
|
|
|
|
|
def make_samples(samples_filename):
|
|
"Génère les samples"
|
|
entry_names = None
|
|
if len(sys.argv) >= 3 and sys.argv[1] == "-i":
|
|
samples_filename = sys.argv[2]
|
|
entry_names = sys.argv[3:] if len(sys.argv) > 3 else None
|
|
else:
|
|
entry_names = sys.argv[1:]
|
|
|
|
if os.path.exists(DATA_DIR):
|
|
if not os.path.isdir(DATA_DIR):
|
|
raise SampleException(f"{DATA_DIR} existe déjà et n'est pas un répertoire")
|
|
# DATA_DIR existe déjà - effacer et recréer
|
|
shutil.rmtree(DATA_DIR)
|
|
os.mkdir(DATA_DIR)
|
|
else:
|
|
os.mkdir(DATA_DIR)
|
|
|
|
samples = Samples(entry_names)
|
|
df = pd.read_csv(
|
|
samples_filename,
|
|
comment="#",
|
|
sep=";",
|
|
quotechar='"',
|
|
dtype={
|
|
"entry_name": str,
|
|
"url": str,
|
|
"permission": str,
|
|
"method": str,
|
|
"content": str,
|
|
},
|
|
keep_default_na=False,
|
|
)
|
|
df = df.reset_index()
|
|
df.apply(samples.add_sample, axis=1)
|
|
samples.dump()
|
|
return samples
|
|
|
|
|
|
if not CHECK_CERTIFICATE:
|
|
urllib3.disable_warnings()
|
|
|
|
make_samples(SAMPLES_FILENAME)
|
|
|
|
print(f"Fichiers samples générés dans {DATA_DIR}")
|