File size: 7,674 Bytes
f3c7e36 5afe693 f3c7e36 5afe693 f3c7e36 5bdb051 3d78a2b f3c7e36 5bdb051 e5ac538 3d78a2b f3c7e36 3d78a2b f3c7e36 3d78a2b f3c7e36 772ba36 f3c7e36 1848052 f3c7e36 9649fff a77590a f3c7e36 85edfd8 f3c7e36 85edfd8 f3c7e36 5afe693 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
from fastapi import FastAPI, HTTPException, Header, Depends, Request
from fastapi.responses import JSONResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.exceptions import RequestValidationError
from typing import Optional, List
from pydantic import BaseModel, ValidationError
import pandas as pd
import datetime
api = FastAPI()
# Charger les données à partir du fichier CSV
questions_data = pd.read_csv('questions.csv')
# Dictionnaire des identifiants des utilisateurs
users_credentials = {
"alice": "wonderland",
"bob": "builder",
"clementine": "mandarine",
"admin": "4dm1N" # Ajout de l'utilisateur admin
}
# Modèle Pydantic pour représenter une question
class Question(BaseModel):
question: str
subject: str
correct: Optional[str] = None # Champ optionnel
use: str
responseA: str
responseB: str
responseC: Optional[str] = None # Champ optionnel
responseD: Optional[str] = None # Champ optionnel
# remark: Optional[str] = None # Champ optionnel
# Modèle pour représenter une exception personnalisée
class MyException(Exception):
def __init__(self,
status_code: int,
name : str,
message : str):
self.status_code = status_code
self.name = name
self.message = message
self.date = str(datetime.datetime.now())
# Gestionnaire d'exception personnalisé
@api.exception_handler(MyException)
def MyExceptionHandler(
request: Request,
exception: MyException
):
return JSONResponse(
status_code=exception.status_code,
content={
'url': str(request.url),
'name': exception.name,
'message': exception.message,
'date': exception.date
}
)
# Gestionnaire d'exception pour les erreurs de validation de la requête
@api.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
'url': str(request.url),
'name': "Erreur de validation de la requête (parametre requis)",
'message': exc.errors(),
'date': str(datetime.datetime.now())
},
)
# Gestionnaire d'exception pour les erreurs Pydantic
@api.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
return JSONResponse(
status_code=422,
content={
'url': str(request.url),
'name': "Erreur de validation Pydantic",
'message': exc.errors(),
'date': str(datetime.datetime.now())
},
)
# Fonction pour vérifier l'authentification de l'utilisateur
def authenticate(authorisation: str = Header(None)):
if not authorisation:
raise HTTPException(status_code=401, detail="Utilisateur non authorisé")
try:
scheme, credentials = authorisation.split()
if scheme != 'Basic':
raise HTTPException(status_code=401, detail="Utilisateur non authorisé")
username, password = credentials.split(":")
if username not in users_credentials or users_credentials[username] != password:
raise HTTPException(status_code=401, detail="Utilisateur non authorisé ")
except Exception as e:
raise HTTPException(status_code=401, detail="Utilisateur non authorisé")
return username, password
# Endpoint pour vérifier que l'API est fonctionnelle
@api.get('/', name="Vérification que l'API fonctionne")
def check_api():
return {'message': "L'API fonctionne"}
# Endpoint pour récupérer les questions en fonction du type de test (use) et des catégories (subject) spécifiés
@api.get('/questions', name="Récupération des questions pour un QCM")
def get_questions(use: str,
subject: str,
num_questions: int,
authorisation: str = Header(None)):
"""
Récupère les questions en fonction du type de test (use) et des catégories (subject) spécifiés
L'application peut produire des QCMs de 5, 10 ou 20 questions (seulement)
Les questions sont retournées dans un ordre aléatoire
Seuls les utilisateurs se trouvant dans users_credentials peuvent utiliser cette application
"""
# Verifier si l'utilsateur existe et a le droit
authenticate(authorisation)
# Verifier si le nombre de questions demandé correspond au nombre de questions d'un QCM
if num_questions not in [5,10,20]:
raise MyException(status_code=422, name="num_questions invalide", \
message="La requête peut contenir 51,10 ou 20 questions, mais pas "+str(num_questions))
# Filtrer les questions en fonction des paramètres spécifiés
filtered_questions = questions_data
if use:
filtered_questions = filtered_questions[filtered_questions['use'] == use]
if subject is not None:
s = subject.split(',')
filtered_questions = filtered_questions[filtered_questions['subject'].isin(s)]
print("len(filtered_questions)=",len(filtered_questions))
print("num_questions=",num_questions)
# Vérifier si des questions sont disponibles dans la catégorie spécifiée
if len(filtered_questions) == 0:
raise MyException(status_code=404, name="Aucune question", \
message="Aucune question ne correspond aux critères sélectionnés")
# Verifier si le nombre de questions diponibles >= au nombre requis
elif (len(filtered_questions) < num_questions):
raise MyException(status_code=400, name="Nb insuffisant de questions ", \
message="Le nombre de questions correspondantes aux critères sélectionnés est < au nombre requis")
# Supprimer les valeurs NaN dans les champs correct,responseC, responseD (ils ne sont pas toujours remplis)
filtered_questions['correct'] = filtered_questions['correct'].apply(lambda x: None if pd.isna(x) else x)
filtered_questions['responseC'] = filtered_questions['responseC'].apply(lambda x: None if pd.isna(x) else x)
filtered_questions['responseD'] = filtered_questions['responseD'].apply(lambda x: None if pd.isna(x) else x)
# Sélectionner un nombre aléatoire de questions
selected_questions = filtered_questions.sample(n=min(num_questions, len(filtered_questions)))
# Convertir les données en liste de dictionnaires
questions_list = selected_questions.to_dict(orient='records')
# Convertir les dictionnaires en objets Pydantic de type Question
questions_objects = [Question(**question) for question in questions_list]
return questions_objects
# Endpoint pour créer une nouvelle question (accessible uniquement par l'utilisateur admin)
@api.post('/questions/create', name="Création d'une nouvelle question")
def create_question(question: Question,
authorisation: str = Header(None)):
"""
Crée une nouvelle question et l'ajoute à questions.csv
Seuls l' utilisateur admin a le droit d'utiliser cette fonction
"""
global questions_data
username, password = authenticate(authorisation)
if username != 'admin':
raise HTTPException(status_code=401, detail="Utilisateur non authorisé")
# Ajouter la nouvelle question au DataFrame
new_question = question.model_dump()
questions_data = questions_data.append(new_question, ignore_index=True)
# Sauvegarder les modifications dans le fichier CSV
questions_data.to_csv('questions.csv', index=False)
return {'message': 'Question créée avec succès'}
|