ScoDoc/tests/api/make_samples.py

245 lines
8.1 KiB
Python
Raw Permalink Normal View History

2022-08-14 11:36:24 +02:00
#!/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]
2022-08-14 11:36:24 +02:00
Si entry_names est spécifié, la génération est restreinte aux exemples cités.
Exemple:
python make_samples departements departement_formsemestres
2022-08-14 11:36:24 +02:00
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
2022-08-14 11:36:24 +02:00
```
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
2022-08-14 11:36:24 +02:00
* 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).
2022-08-14 11:36:24 +02:00
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
2022-08-14 11:36:24 +02:00
- 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.
2022-08-14 11:36:24 +02:00
"""
import os
import shutil
import sys
import re
2022-08-14 11:36:24 +02:00
from collections import defaultdict
from pprint import pprint as pp
import urllib3
import json
2022-11-12 15:35:36 +01:00
import pandas as pd
2022-11-12 15:35:36 +01:00
2022-08-14 11:36:24 +02:00
from setup_test_api import (
API_PASSWORD,
API_URL,
API_USER,
APIError,
CHECK_CERTIFICATE,
get_auth_headers,
GET,
POST,
2022-08-14 11:36:24 +02:00
SCODOC_URL,
)
DATA_DIR = "/tmp/samples/"
SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"
2022-08-14 11:36:24 +02:00
class SampleException(Exception):
pass
2022-08-14 11:36:24 +02:00
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"
2022-08-14 11:36:24 +02:00
if permission == "ScoView":
HEADERS = get_auth_headers("test", "test")
elif permission == "ScoSuperAdmin":
HEADERS = get_auth_headers("admin_api", "admin_api")
elif permission == "UsersAdmin":
2022-08-14 11:36:24 +02:00
HEADERS = get_auth_headers("admin_api", "admin_api")
else:
raise SampleException(f"Bad permission : {permission}, url={self.url}")
2022-08-14 11:36:24 +02:00
if self.method == "GET":
self.result = GET(self.url, HEADERS)
elif self.method == "POST":
if self.content == "":
self.result = POST(self.url, headers=HEADERS)
2022-08-14 11:36:24 +02:00
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)
2022-08-14 11:36:24 +02:00
elif self.method[0] != "#":
raise SampleException(f'Bad method : "{self.method}", url={self.url}')
2022-08-14 11:36:24 +02:00
self.shorten()
with open("sample_TEST.json.md", "tw", encoding="utf-8") as f:
self.dump(f)
2022-08-14 11:36:24 +02:00
def _shorten(self, item):
"Abrège les longues listes: limite à 2 éléments et affiche '...' etc. à la place"
2022-08-14 11:36:24 +02:00
if isinstance(item, list):
return [self._shorten(child) for child in item[:2] + ["..."]]
2022-08-14 11:36:24 +02:00
return item
def shorten(self):
"Abrège le résultat"
2022-08-14 11:36:24 +02:00
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", "")
2022-08-14 11:36:24 +02:00
file.write(f"#### {self.method} {self.url}\n")
if len(self.content) > 0:
file.write("> `Content-Type: application/json`\n")
file.write("> \n")
2022-08-14 11:36:24 +02:00
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)
2022-08-14 11:36:24 +02:00
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
2022-08-14 11:36:24 +02:00
2022-11-12 15:35:36 +01:00
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)
2022-08-14 11:36:24 +02:00
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)
2022-08-14 11:36:24 +02:00
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:]
2022-08-14 11:36:24 +02:00
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)
2022-08-14 11:36:24 +02:00
else:
2022-11-12 15:35:36 +01:00
os.mkdir(DATA_DIR)
2022-08-14 11:36:24 +02:00
samples = Samples(entry_names)
df = pd.read_csv(
samples_filename,
comment="#",
2022-11-12 15:35:36 +01:00
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)
2022-08-14 11:36:24 +02:00
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}")