222 lines
7.7 KiB
Python
222 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""Construction des fichiers exemples pour la documentation.
|
|
|
|
Usage:
|
|
cd /opt/scodoc/tests/api
|
|
python make_samples.py [entry_names]
|
|
|
|
si entry_names est spécifié, la génération est restreints aux exemples cités. expl: `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)
|
|
cd /opt/scodoc/tests/api
|
|
tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
|
|
|
|
Créer éventuellement un fichier `.env` dans /opt/scodoc/tests/api
|
|
avec la config du client API:
|
|
```
|
|
SCODOC_URL = "http://localhost:5000/"
|
|
```
|
|
|
|
Cet utilitaire prend en donnée 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 exemple.
|
|
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 chaucn
|
|
- 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
|
|
|
|
TODO: ajouter un argument au script permettant de ne générer qu'un seul fichier (exemple: `python make_samples.py nom_exemple`)
|
|
|
|
"""
|
|
import numpy as np
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import re
|
|
from collections import defaultdict
|
|
from pprint import pprint as pp
|
|
|
|
import urllib3
|
|
import json
|
|
|
|
from pandas import read_csv
|
|
|
|
from setup_test_api import (
|
|
API_PASSWORD,
|
|
API_URL,
|
|
API_USER,
|
|
APIError,
|
|
CHECK_CERTIFICATE,
|
|
get_auth_headers,
|
|
GET,
|
|
POST_JSON,
|
|
SCODOC_URL,
|
|
)
|
|
|
|
DATA_DIR = "/tmp/samples/"
|
|
SAMPLES_FILENAME = "tests/ressources/samples.csv"
|
|
|
|
|
|
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 == "ScoUsersAdmin":
|
|
HEADERS = get_auth_headers("admin_api", "admin_api")
|
|
else:
|
|
raise Exception(f"Bad permission : {permission}")
|
|
if self.method == "GET":
|
|
self.result = GET(self.url, HEADERS)
|
|
elif self.method == "POST":
|
|
if self.content == "":
|
|
self.result = POST_JSON(self.url, headers=HEADERS)
|
|
else:
|
|
HEADERS["Content-Type"] = "application/json ; charset=utf-8"
|
|
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
|
|
elif self.method[0] != "#":
|
|
error = f'Bad method : "{self.method}"'
|
|
raise Exception(error)
|
|
self.shorten()
|
|
file = open(f"sample_TEST.json.md", "tw")
|
|
self.dump(file)
|
|
file.close()
|
|
|
|
def _shorten(
|
|
self, item
|
|
): # abrege 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]] + ["... etc."]
|
|
return item
|
|
|
|
def shorten(self):
|
|
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(f"> `Content-Type: application/json`\n")
|
|
file.write(f"> \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(lambda: 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():
|
|
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
|
|
file.write(f"### {entry}\n\n")
|
|
for sample in sorted(
|
|
samples, key=lambda s: s.url
|
|
): # sorted de façon à rendre le fichier résultat déterministe (i.e. indépendant de l ordre d arrivée des résultats)
|
|
sample.dump(file)
|
|
file.close()
|
|
|
|
|
|
def make_samples():
|
|
if len(sys.argv) == 1:
|
|
entry_names = None
|
|
else:
|
|
entry_names = sys.argv[1:]
|
|
if os.path.exists(DATA_DIR):
|
|
if not os.path.isdir(DATA_DIR):
|
|
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
|
|
else:
|
|
# 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 = read_csv(
|
|
SAMPLES_FILENAME,
|
|
sep=";",
|
|
quotechar='"',
|
|
dtype={
|
|
"entry_name": str,
|
|
"url": str,
|
|
"permission": str,
|
|
"method": str,
|
|
"content": str,
|
|
},
|
|
keep_default_na=False,
|
|
)
|
|
df = df.reset_index()
|
|
df.apply(lambda line: samples.add_sample(line), axis=1)
|
|
samples.dump()
|
|
return samples
|
|
|
|
|
|
if not CHECK_CERTIFICATE:
|
|
urllib3.disable_warnings()
|
|
make_samples()
|