From 3dd1adeb25d3f8d17e87b7608d241ce63511ba5d Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 12 Nov 2023 16:14:26 +0100 Subject: [PATCH 01/12] add argument check in decorator --- app.py | 25 +++++++++++++++++-------- decorator.py | 18 ------------------ main.py | 8 ++++---- 3 files changed, 21 insertions(+), 30 deletions(-) delete mode 100644 decorator.py diff --git a/app.py b/app.py index d201419..89f0a58 100644 --- a/app.py +++ b/app.py @@ -1,33 +1,42 @@ from fastapi import FastAPI, APIRouter +import ast my_router = APIRouter() app = FastAPI() -def fast_api_decorator(route, method): +def fast_api_decorator(route, method, type_args): def decorator(func): - def wrapper(*args, **kwargs): + def wrapper(**kwargs): + # Handle argument type error + for value, expected_type in zip(kwargs.values(), type_args): + if not isinstance(value, expected_type): + raise TypeError(f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}") + + # add endpoint to the API my_router.add_api_route(path=route, endpoint=func, methods=method) app.include_router(my_router) - return func(*args, **kwargs) + return func(**kwargs) return wrapper return decorator -@fast_api_decorator(route="/power/", method=["GET"]) +@fast_api_decorator(route="/power/", method=["GET"], type_args=[str, str]) def power_function(x: str, a: str): return {f"{x} to the power of {a}": int(x)**int(a)} -@fast_api_decorator(route="/add/", method=["GET"]) +@fast_api_decorator(route="/add/", method=["GET"], type_args=[str, str]) def add_function(x: str, a: str): return {f"{x} + {a} equals": int(x) + int(a)} -@fast_api_decorator(route="/sous/", method=["GET"]) -def sous_function(x: str, lst): - return {f"{x} - {lst[0]} - {lst[1]} equals": int(x) - int(lst[0]) - int(lst[1])} +@fast_api_decorator(route="/sous/", method=["GET"], type_args=[str, list]) +def sous_function(x, lst): + x = ast.literal_eval(x) + # lst = ast.literal_eval(lst) + return {f"{x} - {lst[0]} - {lst[1]} equals": x - lst[0] - lst[1]} # On "lance" les fonctions pour qu'elles soient lisibles par l'app FastAPI diff --git a/decorator.py b/decorator.py deleted file mode 100644 index 3e40809..0000000 --- a/decorator.py +++ /dev/null @@ -1,18 +0,0 @@ -# In this script we build the decorator and the function we want to transform into an API -from app import app, my_router - - -def fast_api_decorator(route): - def decorator(func): - def wrapper(x, a): - func(x, a) - return func(x, a) - my_router.add_api_route(path='/{func}/{x}/{a}', endpoint=wrapper) - app.include_router(my_router) - return wrapper - return decorator - - -@fast_api_decorator(route="") -def power_function(x, a): - return {f"{x} to the power of {a}": x**a} diff --git a/main.py b/main.py index 79dc920..55c669f 100644 --- a/main.py +++ b/main.py @@ -31,14 +31,14 @@ def add(x, a): print(requests.get(f"http://127.0.0.1:8000/add/?x={x}&a={a}").json()) -def sous(x, a, b): - print(requests.post(f"http://127.0.0.1:8000/sous/?x={x}&a={a}&b={b}").json()) +def sous(x, lst): + print(requests.get(f"http://127.0.0.1:8000/sous/?x={x}&lst=[{lst[0]},{lst[1]}]").json()) if __name__ == "__main__": power(x=9, a=2) add(x=9, a=2) - sous(x=9, a=2, b=1) + #sous(x=9, lst=[2, 1]) print(power_function(x="9", a="2")) print(add_function(x="9", a="2")) - print(sous_function(x="9", a="2", b="1")) + print(sous_function(x="9", lst=[2, 1])) From 208d1055ed5e1aebbade6d52ac071f686c4f6002 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 12 Nov 2023 19:49:43 +0100 Subject: [PATCH 02/12] handle list, objects and json parameters --- app.py | 53 ++++++++++++++++++++++++++++++++++++++--------------- main.py | 10 +++++----- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/app.py b/app.py index 89f0a58..16c0d78 100644 --- a/app.py +++ b/app.py @@ -1,9 +1,11 @@ -from fastapi import FastAPI, APIRouter -import ast +from fastapi import FastAPI, APIRouter, Query +from typing import Annotated +from pydantic import BaseModel my_router = APIRouter() app = FastAPI() +request_count = 0 def fast_api_decorator(route, method, type_args): @@ -14,6 +16,10 @@ def wrapper(**kwargs): if not isinstance(value, expected_type): raise TypeError(f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}") + # Count the number of request + global request_count + request_count += 1 + # add endpoint to the API my_router.add_api_route(path=route, endpoint=func, methods=method) app.include_router(my_router) @@ -22,27 +28,44 @@ def wrapper(**kwargs): return decorator -@fast_api_decorator(route="/power/", method=["GET"], type_args=[str, str]) -def power_function(x: str, a: str): - return {f"{x} to the power of {a}": int(x)**int(a)} +@fast_api_decorator(route="/power/", method=["GET"], type_args=[int, int]) +def power_function(x: Annotated[int, Query(description="Int we'll compute the power")], a: Annotated[int, Query(description="Power of the calculation")]): + return {f"{x} to the power of {a}": x ** a} -@fast_api_decorator(route="/add/", method=["GET"], type_args=[str, str]) -def add_function(x: str, a: str): - return {f"{x} + {a} equals": int(x) + int(a)} +@fast_api_decorator(route="/add/", method=["GET"], type_args=[int, int]) +def add_function(x: Annotated[int, Query(description="Int we'll add something")], a: Annotated[int, Query(description="Int added")]): + return {f"{x} + {a} equals": x + a} -@fast_api_decorator(route="/sous/", method=["GET"], type_args=[str, list]) -def sous_function(x, lst): - x = ast.literal_eval(x) - # lst = ast.literal_eval(lst) +@fast_api_decorator(route="/sous/", method=["GET"], type_args=[int, list]) +def sous_function(x: Annotated[int, Query(description="Int we'll substract something")], lst: Annotated[list[int], Query(description="List of 2 int that will be substracted")]): return {f"{x} - {lst[0]} - {lst[1]} equals": x - lst[0] - lst[1]} +class InputDiv(BaseModel): + div: int + + +# Pour faire une requête avec un argument "Body" ou un json avec des arguments il faut passer +# par une méthode "POST" et pas "GET" +@fast_api_decorator(route="/div/", method=["POST"], type_args=[int, InputDiv]) +def div_function(x: Annotated[int, Query(description="Int we will divide something")], item: InputDiv): + return {f"{x} / {item.div} equals": item.div} + + +@app.get("/stats") +async def get_stats(): + global request_count + return {"request_count": request_count} + + # On "lance" les fonctions pour qu'elles soient lisibles par l'app FastAPI -power_function(x="0", a="0") -add_function(x="0", a="0") -sous_function(x="0", lst=[0, 0]) +power_function(x=0, a=0) +add_function(x=0, a=0) +sous_function(x=0, lst=[0, 0]) +input_item = InputDiv(div=10) +div_function(x=100, item=input_item) # résolution pb de lancement des fonctions """ diff --git a/main.py b/main.py index 55c669f..1e6fc04 100644 --- a/main.py +++ b/main.py @@ -32,13 +32,13 @@ def add(x, a): def sous(x, lst): - print(requests.get(f"http://127.0.0.1:8000/sous/?x={x}&lst=[{lst[0]},{lst[1]}]").json()) + print(requests.get(f"http://127.0.0.1:8000/sous/?x={x}&lst={lst[0]}&lst={lst[1]}").json()) if __name__ == "__main__": power(x=9, a=2) add(x=9, a=2) - #sous(x=9, lst=[2, 1]) - print(power_function(x="9", a="2")) - print(add_function(x="9", a="2")) - print(sous_function(x="9", lst=[2, 1])) + sous(x=9, lst=[2, 1]) + print(power_function(x=9, a=2)) + print(add_function(x=9, a=2)) + print(sous_function(x=9, lst=[2, 1])) From 99b6a1e97fa241e50507c53cc18315819e9bc939 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 12 Nov 2023 19:58:40 +0100 Subject: [PATCH 03/12] count in func --- app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app.py b/app.py index 16c0d78..1aa0103 100644 --- a/app.py +++ b/app.py @@ -30,6 +30,8 @@ def wrapper(**kwargs): @fast_api_decorator(route="/power/", method=["GET"], type_args=[int, int]) def power_function(x: Annotated[int, Query(description="Int we'll compute the power")], a: Annotated[int, Query(description="Power of the calculation")]): + global request_count + request_count += 1 return {f"{x} to the power of {a}": x ** a} From 0bdd5a92cf498f6ea71ef3fa78201e64406b98d0 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 13 Nov 2023 21:48:56 +0100 Subject: [PATCH 04/12] count ok --- app.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index 1aa0103..044b236 100644 --- a/app.py +++ b/app.py @@ -5,7 +5,25 @@ my_router = APIRouter() app = FastAPI() -request_count = 0 + + +def get_saved_value(): + try: + with open("saved_count.txt", "r") as file: + value = int(file.read()) + except FileNotFoundError: + with open("saved_count.txt", 'w') as file: + file.write('0') + value = 0 + return value + + +request_count = get_saved_value() + + +def save_value(value): + with open("saved_count.txt", "w") as file: + file.write(str(value)) def fast_api_decorator(route, method, type_args): @@ -19,6 +37,7 @@ def wrapper(**kwargs): # Count the number of request global request_count request_count += 1 + save_value(request_count) # add endpoint to the API my_router.add_api_route(path=route, endpoint=func, methods=method) @@ -30,8 +49,6 @@ def wrapper(**kwargs): @fast_api_decorator(route="/power/", method=["GET"], type_args=[int, int]) def power_function(x: Annotated[int, Query(description="Int we'll compute the power")], a: Annotated[int, Query(description="Power of the calculation")]): - global request_count - request_count += 1 return {f"{x} to the power of {a}": x ** a} @@ -58,7 +75,7 @@ def div_function(x: Annotated[int, Query(description="Int we will divide somethi @app.get("/stats") async def get_stats(): - global request_count + request_count = get_saved_value() return {"request_count": request_count} From 6ffdd078079d6ae9839e87d1d746372a84e95160 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Fri, 17 Nov 2023 12:21:02 +0100 Subject: [PATCH 05/12] readme update --- README.md | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 001a724..bec3cd3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # FastApiDecoratorBuilder -"FastApiDecoratorBuilder" est un algorithme permettant de concevoir un décorateur Python qui transforme une fonction Python en une API FastAPI. +"FastApiDecoratorBuilder" est un algorithme permettant de concevoir un décorateur Python qui transforme +une fonction Python en une API FastAPI. ## Installation @@ -11,14 +12,22 @@ $ pip install "uvicorn[standard]" 4. Lancer le serveur dans le terminal de l'application Python $ python -m uvicorn main:app --reload -5. Le serveur API démarrera et sera accessible à http://127.0.0.1:8000. +5. Le serveur API démarrera et sera accessible à l'adresse suivante : http://127.0.0.1:8000. +6. Un aperçu des requêtes implémentées est disponible à l'adresse suivante : http://127.0.0.1:8000/docs. ## Utilisation -Le décorateur 'fast_api_decorator' ajoute une route avec un endpoint correspondant aux paramètres de la requête (paramètres de la fonction). Le décorateur appliqué à une fonction puis "lancée" sur le même script de l'instance FastAPI (app) permet de requêter l'API avec une route qui dépend de la fonction et de ses propres paramètres. -L'API est configurée directement grâce aux paramètres du décorateur avec les routes ("/power/", "/add/" et "/sous/") et les méthodes HTTP ("GET", "POST", "PUT", "DELETE"). +Le décorateur 'fast_api_decorator' ajoute une route avec un endpoint correspondant aux paramètres de la requête +(paramètres de la fonction). Le décorateur appliqué à une fonction puis exécutée sur le même script de l'instance +FastAPI (app) permet de rendre utilisable l'API avec une route qui dépend de la fonction et de ses propres paramètres. +Ainsi, une fois cette étape réalisée, il est possible de requêter l'API de la fonction à laquelle on a appliqué le +décorateur avec n'importe quels arguments. La réponse de l'API est évidemment l'output de cette fonction. +L'API est configurée directement grâce aux paramètres du décorateur avec les routes (ex: "/power/", "/add/" ou "/sous/"), +les méthodes HTTP (ex: "GET", "POST", "PUT", "DELETE") et une liste de type correspondants aux types des arguments de la +fonction décorée. ## Test -Dans le code suivant, il y a trois fonctions qui ont été implémenté dans l'API. Il suffit d'entrer les points de terminaison suivants après le lien 'http://127.0.0.1:8000'. +Dans le code suivant, il y a trois fonctions qui ont été implémenté dans l'API. Il suffit d'entrer les points de +terminaison suivants après le lien 'http://127.0.0.1:8000'. 1. Power Description : Calcul de la puissance d'un nombre @@ -39,12 +48,19 @@ Dans le code suivant, il y a trois fonctions qui ont été implémenté dans l'A 3. Sous Description : Calcul la soustraction de trois nombres Paramètres : - - 'x': Nombre à soustraire(integer) - - 'a': Nombre (integer) - - 'b': Nombre (integer) + - 'x': Nombre à soustraire (integer) + - [a, b] : liste de nombre (List[int]) - Exemple : '/sous/?x=10&a=3&b=2' pour soustraire 3 et 2 à 10 + Exemple : '/sous/?x=10&lst=3&lst=2' pour soustraire 3 et 2 à 10 +## Compte rendu du projet - +Dans FastAPI, les routes doivent être enregistrées auprès de l'instance d'application pour qu'elles soient +accessibles lorsque le serveur est en cours d'exécution. Dans notre code, les routes sont définies à l'aide de +décorateurs qui configurent essentiellement le comportement de routage. Cependant, jusqu'à ce qu'on invoque +les fonctions décorées avec le décorateur @fast_api_decorator, les routes ne sont pas ajoutées à l'application +FastAPI. En effet, lors du lancement de l'application, le programme app.py est exécuté mais les routes ne sont pas +ajoutées tant que le décorateur n'est pas appelé, c'est-à-dire que les fonctions décorées ne sont pas exécutées une +première fois. Il est alors nécessaire d'appeler chaque fonction dans le programme app afin de rendre utilisable +leur route. From f32fa5ae32114fa6469dcfd6dc45fe09b166df0d Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sat, 18 Nov 2023 23:14:57 +0100 Subject: [PATCH 06/12] finish sur les stats --- app.py | 75 +++++++++++++++++++++------------------------------------ main.py | 21 ---------------- 2 files changed, 27 insertions(+), 69 deletions(-) diff --git a/app.py b/app.py index 044b236..f464aa4 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,8 @@ -from fastapi import FastAPI, APIRouter, Query +from fastapi import FastAPI, APIRouter, Query, Request from typing import Annotated from pydantic import BaseModel - +import pickle +from collections import Counter my_router = APIRouter() app = FastAPI() @@ -9,21 +10,32 @@ def get_saved_value(): try: - with open("saved_count.txt", "r") as file: - value = int(file.read()) + with open("saved_count.pkl", "rb") as file: + values = pickle.load(file) except FileNotFoundError: - with open("saved_count.txt", 'w') as file: - file.write('0') - value = 0 - return value + with open("saved_count.pkl", 'wb') as file: + values = {"power_function": 0, "add_function": 0, "sous_function": 0, "div_function": 0} + pickle.dump(values, file) + return values request_count = get_saved_value() -def save_value(value): - with open("saved_count.txt", "w") as file: - file.write(str(value)) +def save_value(values): + with open("saved_count.pkl", "wb") as file: + pickle.dump(values, file) + + +route_request_counter = Counter() + + +@app.middleware("http") +async def count_requests(request: Request, call_next): + route = request.url.path + route_request_counter[route] += 1 + response = await call_next(request) + return response def fast_api_decorator(route, method, type_args): @@ -35,8 +47,8 @@ def wrapper(**kwargs): raise TypeError(f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}") # Count the number of request - global request_count - request_count += 1 + request_count = get_saved_value() + request_count[func.__name__] += 1 save_value(request_count) # add endpoint to the API @@ -75,8 +87,8 @@ def div_function(x: Annotated[int, Query(description="Int we will divide somethi @app.get("/stats") async def get_stats(): - request_count = get_saved_value() - return {"request_count": request_count} + return {"Nombre d'appels aux fonctions décorées" : get_saved_value(), + "Nombre d'appels totaux des API par route" : route_request_counter} # On "lance" les fonctions pour qu'elles soient lisibles par l'app FastAPI @@ -85,36 +97,3 @@ async def get_stats(): sous_function(x=0, lst=[0, 0]) input_item = InputDiv(div=10) div_function(x=100, item=input_item) - -# résolution pb de lancement des fonctions -""" -from fastapi import FastAPI, APIRouter - -app = FastAPI() - -class PowerEndpoint: - router = APIRouter() - - @router.get("/power/") - async def power_function(self, x: str, a: str): - return {f"{x} to the power of {a}": int(x)**int(a)} - -class AddEndpoint: - router = APIRouter() - - @router.get("/add/") - async def add_function(self, x: str, a: str): - return {f"{x} + {a} equals": int(x) + int(a)} - -class SousEndpoint: - router = APIRouter() - - @router.get("/sous/") - async def sous_function(self, x: str, lst): - return {f"{x} - {lst[0]} - {lst[1]} equals": int(x) - int(lst[0]) - int(lst[1])} - -# Including the routers directly in the main app -app.include_router(PowerEndpoint.router, tags=["power"]) -app.include_router(AddEndpoint.router, tags=["add"]) -app.include_router(SousEndpoint.router, tags=["sous"]) -""" \ No newline at end of file diff --git a/main.py b/main.py index 1e6fc04..cda75a2 100644 --- a/main.py +++ b/main.py @@ -1,24 +1,3 @@ -""" -FastApiDecoratorBuilder - -Description du Projet : -Votre défi dans le projet "FastApiDecoratorBuilder" est de concevoir un décorateur Python qui -transforme une fonction Python en une API FastAPI basée sur la fonction et des configurations -définies. - -Objectifs du Projet : -- Création de Décorateur: Construire un décorateur qui transforme une fonction Python en API FastAPI. -- Gestion de Configurations: Implanter un mécanisme de configuration pour l’API. - -Consignes : -1) Développement du Décorateur : Elaborez un décorateur qui, appliqué à une fonction, génère une API FastAPI -correspondante. -2) Configuration de l'API : Intégrez une méthode pour configurer les propriétés de l’API générée, telles que -les routes et les méthodes HTTP acceptées. -""" - -# Use command : "python -m uvicorn main:app --reload" to lauch server and be able to request the "app" API. - from app import app, power_function, add_function, sous_function import requests From 15cdb61fd07a389af78a25912f02c391e4b1b3a9 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 19 Nov 2023 11:58:25 +0100 Subject: [PATCH 07/12] =?UTF-8?q?finish=20sur=20les=20stats=20de=20compte?= =?UTF-8?q?=20de=20requ=C3=AAtes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 52 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/app.py b/app.py index f464aa4..0fe6d1c 100644 --- a/app.py +++ b/app.py @@ -3,33 +3,37 @@ from pydantic import BaseModel import pickle from collections import Counter +import os my_router = APIRouter() app = FastAPI() +route_request_counter = Counter() + + +@app.on_event("shutdown") +def shutdown_event(): + try: + os.remove('saved_count.pkl') + except Exception as e: + print(f"Error deleting statistics file: {e}") -def get_saved_value(): +def get_saved_values(): try: with open("saved_count.pkl", "rb") as file: values = pickle.load(file) except FileNotFoundError: - with open("saved_count.pkl", 'wb') as file: - values = {"power_function": 0, "add_function": 0, "sous_function": 0, "div_function": 0} + with open('saved_count.pkl', 'wb') as file: + values = dict() pickle.dump(values, file) return values -request_count = get_saved_value() - - def save_value(values): with open("saved_count.pkl", "wb") as file: pickle.dump(values, file) -route_request_counter = Counter() - - @app.middleware("http") async def count_requests(request: Request, call_next): route = request.url.path @@ -38,19 +42,29 @@ async def count_requests(request: Request, call_next): return response +def check_args_type(dict_args, type_args): + for value, expected_type in zip(dict_args.values(), type_args): + if not isinstance(value, expected_type): + raise TypeError(f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}") + + +def count_func_call(func): + request_count = get_saved_values() + key_func = func.__name__ + if key_func in request_count: + request_count[key_func] += 1 + else: + request_count[key_func] = 1 + save_value(request_count) + + def fast_api_decorator(route, method, type_args): def decorator(func): def wrapper(**kwargs): # Handle argument type error - for value, expected_type in zip(kwargs.values(), type_args): - if not isinstance(value, expected_type): - raise TypeError(f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}") - + check_args_type(dict_args=kwargs, type_args=type_args) # Count the number of request - request_count = get_saved_value() - request_count[func.__name__] += 1 - save_value(request_count) - + count_func_call(func=func) # add endpoint to the API my_router.add_api_route(path=route, endpoint=func, methods=method) app.include_router(my_router) @@ -87,8 +101,8 @@ def div_function(x: Annotated[int, Query(description="Int we will divide somethi @app.get("/stats") async def get_stats(): - return {"Nombre d'appels aux fonctions décorées" : get_saved_value(), - "Nombre d'appels totaux des API par route" : route_request_counter} + return {"Nombre d'appels aux fonctions décorées": get_saved_values(), + "Nombre d'appels totaux des API par route": route_request_counter} # On "lance" les fonctions pour qu'elles soient lisibles par l'app FastAPI From b209a7f2d131ec2f5075462268a704c5e20569fc Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 19 Nov 2023 12:27:30 +0100 Subject: [PATCH 08/12] finish temps moyen d'API --- app.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 0fe6d1c..3360f2c 100644 --- a/app.py +++ b/app.py @@ -4,10 +4,12 @@ import pickle from collections import Counter import os +import time my_router = APIRouter() app = FastAPI() route_request_counter = Counter() +route_time_counter = Counter() @app.on_event("shutdown") @@ -38,7 +40,10 @@ def save_value(values): async def count_requests(request: Request, call_next): route = request.url.path route_request_counter[route] += 1 + start_time = time.time() response = await call_next(request) + end_time = time.time() + route_time_counter[route] += end_time - start_time return response @@ -101,8 +106,12 @@ def div_function(x: Annotated[int, Query(description="Int we will divide somethi @app.get("/stats") async def get_stats(): + avg_time = dict() + for key in route_request_counter.keys(): + avg_time[key] = route_time_counter[key] * 1000 / route_request_counter[key] return {"Nombre d'appels aux fonctions décorées": get_saved_values(), - "Nombre d'appels totaux des API par route": route_request_counter} + "Nombre d'appels totaux des API par route": route_request_counter, + "Temps moyen d'exécution par route en ms": avg_time} # On "lance" les fonctions pour qu'elles soient lisibles par l'app FastAPI From 7d4eb216b31acc413d3f5f5caa9b597d82af5905 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 19 Nov 2023 12:52:02 +0100 Subject: [PATCH 09/12] commentaires --- app.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app.py b/app.py index 3360f2c..e88ff6e 100644 --- a/app.py +++ b/app.py @@ -14,6 +14,9 @@ @app.on_event("shutdown") def shutdown_event(): + """ + Remove the statistics file when the app shutdown + """ try: os.remove('saved_count.pkl') except Exception as e: @@ -21,6 +24,10 @@ def shutdown_event(): def get_saved_values(): + """ + Retrieve the statistics data saved in the file + :return: dict with statistics values of all API routes + """ try: with open("saved_count.pkl", "rb") as file: values = pickle.load(file) @@ -32,12 +39,22 @@ def get_saved_values(): def save_value(values): + """ + Save the current API statistics in a file + :param values: dict with statistics values of all API routes + """ with open("saved_count.pkl", "wb") as file: pickle.dump(values, file) @app.middleware("http") async def count_requests(request: Request, call_next): + """ + Increment the number of request by route and calculate the time while processing + :param request: incoming HTTP request + :param call_next: function that represents the next middleware or request handler in the processing pipeline + :return: response of the API request + """ route = request.url.path route_request_counter[route] += 1 start_time = time.time() @@ -48,12 +65,21 @@ async def count_requests(request: Request, call_next): def check_args_type(dict_args, type_args): + """ + Check if args in the dictionary corresponds to the expected type, raise an error if not + :param dict_args: dict of arguments to check + :param type_args: list of expected types + """ for value, expected_type in zip(dict_args.values(), type_args): if not isinstance(value, expected_type): raise TypeError(f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}") def count_func_call(func): + """ + Increment the number of call by fonction + :param func: methode to increment the count + """ request_count = get_saved_values() key_func = func.__name__ if key_func in request_count: From d2264424a0dc77c86fc67f67f48893d1c32afcc6 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 19 Nov 2023 15:01:31 +0100 Subject: [PATCH 10/12] merge --- app.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app.py b/app.py index c3cd3c5..f9674cc 100644 --- a/app.py +++ b/app.py @@ -1,15 +1,9 @@ import asyncio -from typing import Annotated from functools import lru_cache -from fastapi import FastAPI, APIRouter, Depends, HTTPException, Query +from fastapi import FastAPI, APIRouter, Depends, HTTPException, Query,Request from fastapi.security import OAuth2PasswordRequestForm -from Authentification import User, get_current_active_user, fake_users_db, UserInDB, fake_hash_password, \ - get_current_inactive_user -from pydantic import BaseModel - +from Authentification import User, get_current_active_user, fake_users_db, UserInDB, fake_hash_password, get_current_inactive_user from settings import Settings - -from fastapi import FastAPI, APIRouter, Query, Request from typing import Annotated from pydantic import BaseModel import pickle From c08794b76a0e8587a6fda12f7931a20d05b9943e Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 19 Nov 2023 15:23:30 +0100 Subject: [PATCH 11/12] update --- app.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index e3e4443..0d23ba4 100644 --- a/app.py +++ b/app.py @@ -82,7 +82,9 @@ def check_args_type(dict_args, type_args): """ for value, expected_type in zip(dict_args.values(), type_args): if not isinstance(value, expected_type): - raise TypeError(f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}") + raise TypeError( + f"Type d'argument incorrect. Attendu : {expected_type.__name__}, Reçu : {type(value).__name__}" + ) @lru_cache @@ -120,17 +122,23 @@ def wrapper(**kwargs): @fast_api_decorator(route="/power/", method=["GET"], type_args=[int, int]) -def power_function(x: Annotated[int, Query(description="Int we'll compute the power")], a: Annotated[int, Query(description="Power of the calculation")]): +def power_function(x: Annotated[int, Query(description="Int we'll compute the power")], + a: Annotated[int, Query(description="Power of the calculation")], + current_user: User = Depends(get_current_active_user)): return {f"{x} to the power of {a}": x ** a} @fast_api_decorator(route="/add/", method=["GET"], type_args=[int, int]) -def add_function(x: Annotated[int, Query(description="Int we'll add something")], a: Annotated[int, Query(description="Int added")],current_user: User = Depends(get_current_active_user)): +def add_function(x: Annotated[int, Query(description="Int we'll add something")], + a: Annotated[int, Query(description="Int added")], + current_user: User = Depends(get_current_active_user)): return {f"{x} + {a} equals": x + a} @fast_api_decorator(route="/sous/", method=["GET"], type_args=[int, list]) -def sous_function(x: Annotated[int, Query(description="Int we'll substract something")], lst: Annotated[list[int], Query(description="List of 2 int that will be substracted")],current_user: User = Depends(get_current_active_user)): +def sous_function(x: Annotated[int, Query(description="Int we'll substract something")], + lst: Annotated[list[int], Query(description="List of 2 int that will be substracted")], + current_user: User = Depends(get_current_active_user)): return {f"{x} - {lst[0]} - {lst[1]} equals": x - lst[0] - lst[1]} @@ -146,7 +154,7 @@ def div_function(x: Annotated[int, Query(description="Int we will divide somethi @app.get("/stats") -async def get_stats(): +async def get_stats(current_user: User = Depends(get_current_admin_user)): avg_time = dict() for key in route_request_counter.keys(): avg_time[key] = route_time_counter[key] * 1000 / route_request_counter[key] @@ -161,7 +169,9 @@ def read_users_me(current_user: User = Depends(get_current_active_user)): @fast_api_decorator(route="/rendement/", method=["POST"], type_args=[int, float]) -def rendement(x: Annotated[int, Query(description="Int we'll add something")], r: Annotated[float, Query(description="float added")], current_user: User = Depends(get_current_active_user)): +def rendement(x: Annotated[int, Query(description="Int we'll add something")], + r: Annotated[float, Query(description="float added")], + current_user: User = Depends(get_current_active_user)): return {f"{x} * (1 + {r}) equals": int(x) * (1 + float(r))} From d3dd56934e4ad0b3fe7f80acc3850d173ca3516b Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sun, 19 Nov 2023 15:33:56 +0100 Subject: [PATCH 12/12] round ms time --- app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 0d23ba4..9c1d237 100644 --- a/app.py +++ b/app.py @@ -70,7 +70,7 @@ async def count_requests(request: Request, call_next): start_time = time.time() response = await call_next(request) end_time = time.time() - route_time_counter[route] += end_time - start_time + route_time_counter[route] += round(end_time - start_time, 6) return response @@ -163,7 +163,7 @@ async def get_stats(current_user: User = Depends(get_current_admin_user)): "Temps moyen d'exécution par route en ms": avg_time} -@fast_api_decorator(route="/users/me", method=["GET"], type_args=None) +@fast_api_decorator(route="/users/me", method=["GET"], type_args=[]) def read_users_me(current_user: User = Depends(get_current_active_user)): return current_user