diff --git a/.env b/.env new file mode 100644 index 0000000..25f1fc7 --- /dev/null +++ b/.env @@ -0,0 +1,17 @@ +# TITLE: The title of the FastAPI application +TITLE="FastApi Decorator Builder" +# DESCRIPTION: A description of the API +DESCRIPTION="FastApi Decorator Builder est un algorithme permettant de concevoir un décorateur Python qui transforme +une fonction Python en une API FastAPI. Les fonctionnalités sont accessible suivant une authentification." +# ADMIN_EMAIL: The email address for the admin user of the application +ADMIN_EMAIL="admin@fastApi.com" +# COMMAND_LOAD: The command to start the FastAPI application using uvicorn with automatic reloading +COMMAND_LOAD="python -m uvicorn main:app --reload" +# URL: The base URL of the FastAPI application +URL="http://127.0.0.1:8000" +# SAVE_COUNT_FILE: The file name to save the count data +SAVE_COUNT_FILE="saved_count.pki" +# HASH_PASSWORD: A fake hashed password +HASH_PASSWORD="fakehashed" + + diff --git a/Authentification.py b/Authentification.py new file mode 100644 index 0000000..db96b92 --- /dev/null +++ b/Authentification.py @@ -0,0 +1,117 @@ +from __future__ import annotations +from typing import Annotated +from fastapi import Depends, FastAPI, HTTPException, status +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from pydantic import BaseModel + +# Users database with 2 regular users and 1 administrator +fake_users_db = { + "admin": { + "username": "admin", + "full_name": "admin", + "email": "admin@fastApi.com", + "hashed_password": "fakehashedsecret", + "disabled": False, + "admin": True, + }, + "alice": { + "username": "alice", + "full_name": "Alice Wonderson", + "email": "alice@example.com", + "hashed_password": "fakehashedsecret2", + "disabled": True, + "admin": False, + }, + "bod": { + "username": "bod", + "full_name": "bod Wonderson", + "email": "bod@example.com", + "hashed_password": "fakehashedsecret1", + "disabled": False, + "admin": False, + }, +} + + +def fake_hash_password(password: str): + """ + Password hashing function. + :param password: The input password to be hashed. + :return: The hashed password. + """ + return "fakehashed" + password + +# OAuth2 password bearer scheme +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +# Pydantic class to represent the structure of a user +class User(BaseModel): + username: str + email: str = None + full_name: str = None + disabled: bool = None + admin: bool = None + + +class UserInDB(User): + hashed_password: str + + +def get_user(db, username: str): + """ + Get user from the database. + :param db: The user database. + :param username: The username of the user to retrieve. + :return: An instance of UserInDB if the user exists, otherwise None. + """ + if username in db: + user_dict = db[username] + return UserInDB(**user_dict) + + +def fake_decode_token(token): + """ + function to decode a token. + :param token: The token to be decoded. + :return: An instance of UserInDB if the user exists, otherwise None. + """ + user = get_user(fake_users_db, token) + return user + + +async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): + """ + Get the current user based on the provided token. + :param token: The OAuth2 token. + :return: An instance of UserInDB if the user exists, otherwise raise HTTPException. + """ + user = fake_decode_token(token) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authentication credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + return user + + +async def get_current_active_user(current_user: Annotated[User, Depends(get_current_user)]): + """ + Get the current active user based on the current user. + :param current_user: The current user. + :return: The current user if active, otherwise raise HTTPException. + """ + if current_user.disabled: + raise HTTPException(status_code=400, detail="Function not available because you are an Inactive user") + return current_user + + +async def get_current_admin_user(current_user: Annotated[User, Depends(get_current_user)]): + """ + Get the current admin user based on the current user. + :param current_user: The current user. + :return:The current user if an admin, otherwise raise HTTPException. + """ + if not current_user.admin: + raise HTTPException(status_code=400, detail="Function not available because you are not an Administrator user") + return current_user diff --git a/README.md b/README.md index 001a724..e622831 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# FastApiDecoratorBuilder +# FastApi Decorator Builder -"FastApiDecoratorBuilder" est un algorithme permettant de concevoir un décorateur Python qui transforme une fonction Python en une API FastAPI. +"FastApi Decorator Builder" est un algorithme permettant de concevoir un décorateur Python qui transforme +une fonction Python en une API FastAPI. Les fonctionnalités sont accessible suivant une authentification. +L'API permet de collecter des données statisques. ## Installation @@ -11,40 +13,111 @@ $ 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"). +1. Le décorateur +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'. +2. L'authentification +Cet API utilise une authentification OAuth2 Password Bearer pour assurer la sécurité des endpoints. +Trois types d'utilisateurs sont définis: administrateur (username : "admin" & password : "secret"), utilisateur régulier +(username : "bod" & password : "secret1") et utilisateur inactif (username : "alice" & password : "secret2"). +L'administrateur a accès à toutes les fonctionnalitées proposées par l'API dont les statistiques. +L'utilisateur actif peut utiliser les fonctions mathématiques alors que l'utilisateur inactif ne peut qu'accèder qu'à la +fonctionnalité 'info' de l'API. +Si un token invalide ou si une fonctionnalité réservée à un type d'utilisateur spécifique est renseigné +sans les permissions nécessaires, l'API renverra une erreur : + - 401 Unauthorized: Le token fourni est invalide. + - 400 Bad Request: Vous n'avez pas les permissions nécessaires pour accéder à cette fonctionnalité. + + +3. Suivi des Statistiques +Les informations telles que le nombre d'appels par route, le temps moyen d'exécution par route sont collectées +automatiquement à chaque appel d'API. -> enregistré dans un fichier .pki + +## Fonctionnalités +Dans le code suivant, il y a plusieurs fonctions qui ont été implémenté dans l'API. Certaines fonctions nécéssitent des authentifications. + +### Information API + Info + Description : Obtenir des informations sur l'API + Route: '/info/' + Accès : Pas d'accès spécifique + +### Opérations Mathématiques 1. Power Description : Calcul de la puissance d'un nombre + Route: '/power/' + Accès via : Un token d'authentification Paramètres : - 'x': Nombre (integer) - 'a': La puissance (integer) - - Exemple : '/power/?x=2&a=3' retournera le résultat de 2 à la puissance 3 + 2. Add Description : Calcul l'addition de deux nombres + Route: '/add/' + Accès via : Un token d'authentification Paramètres : - 'x': Nombre (integer) - 'a': Nombre (integer) - - Exemple : '/add/?x=2&a=3' retournera la somme de 2 et 3 + 3. Sous Description : Calcul la soustraction de trois nombres + Route: '/sous/' + Accès via : Un token d'authentification Paramètres : - - 'x': Nombre à soustraire(integer) - - 'a': Nombre (integer) - - 'b': Nombre (integer) - - Exemple : '/sous/?x=10&a=3&b=2' pour soustraire 3 et 2 à 10 + - 'x': Nombre à soustraire (integer) + - [a, b] : liste de nombre (List[int]) + + +4. Div + Description : Calcul la division d'un nombre + Route: '/div/' + Accès via : Un token d'authentification + Paramètres: + - 'x': Numérateur(integer) + - item: Modèle Pydantic contenant le diviseur + +### Gestion des Utilisateurs + User + Description : Obtenir des informations sur l'utilisateur actuel + Route: '/users/me' + Accès via : Un token d'authentification + +### Obtention d'un token + Token + Description : Obtenir le jeton d'accès + Route: '/token/' + Paramètres: + - 'username': Nom d'utilisateur + - 'password': Mot de passe + +### Statistiques + Stats + Description : Obtenir le jeton d'accès + Route: '/stats/' + Accès via : Un token d'authentification d'administrateur +## 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. diff --git a/app.py b/app.py index ddeff24..e1fc4e4 100644 --- a/app.py +++ b/app.py @@ -243,4 +243,5 @@ async def info(): 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) \ No newline at end of file +div_function(x=100, item=input_item) + diff --git a/main.py b/main.py index 983351c..526b1b1 100644 --- a/main.py +++ b/main.py @@ -1,25 +1,4 @@ -""" -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. -import app -from app import power_function, add_function, sous_function +from app import app, power_function, add_function, sous_function import requests @@ -30,13 +9,17 @@ def add(x, a): print(requests.get(f"http://127.0.0.1:8000/add/?x={x}&a={a}").json()) 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])) + print(power_function(x=9, a=2)) + print(add_function(x=9, a=2)) + print(sous_function(x=9, lst=[2, 1])) + + diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..bb9f27a --- /dev/null +++ b/settings.py @@ -0,0 +1,12 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + +# This Pydantic settings class is used to load the configuration parameters from the .env file +class Settings(BaseSettings): + title: str = None + description: str = None + admin_email: str = None + command_load:str = None + url:str = None + save_count_file:str =None + hash_password:str =None +