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