Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangAppAAAAAA"
ITEMS_PER_USER=2


#ENV="development"
#DATABASE_PASSWORD="motDePasseSecret"
#THIRD_API_PRIVATE_KEY="cleSecrete"
80 changes: 80 additions & 0 deletions Authentification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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



fake_users_db = {
"john": {
"username": "john",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "alice@example.com",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}


def fake_hash_password(password: str):
return "fakehashed" + password


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = None


class UserInDB(User):
hashed_password: str


def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)


def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user


async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
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)]):
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_inactive_user(current_user: Annotated[User, Depends(get_current_user)]):
if not current_user.disabled:
raise HTTPException(status_code=400, detail="Function not available because you are an Active user")
return current_user



51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
# Python_OOP_Project
# FastApiDecoratorBuilder

"FastApiDecoratorBuilder" est un algorithme permettant de concevoir un décorateur Python qui transforme une fonction Python en une API FastAPI.

## Installation

1. Télécharger les fichiers Python
2. Installer l'application FastAPI
$ pip install fastapi
3. Installer le serveur ASGI
$ 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.

## 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").

## 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'.

1. Power
Description : Calcul de la puissance d'un nombre
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
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
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




163 changes: 163 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import asyncio
from typing import Annotated
from functools import lru_cache
from fastapi import FastAPI, APIRouter, Depends, HTTPException, Query
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 settings import Settings


my_router = APIRouter()
app = FastAPI()


@lru_cache
def get_settings():
return Settings()

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):
def decorator(func):
def wrapper(**kwargs):
# Handle argument type error if type_args is not None
if type_args is not None:
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__}")

# 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)
app.include_router(my_router)
return func(**kwargs)
return wrapper
return decorator


@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=[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]}



@fast_api_decorator(route="/users/me", method=["GET"],type_args=None)
def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user

@fast_api_decorator(route="/power/", method=["POST"],type_args=[int, int])
def power_function(x: Annotated[int, Query(description="Int we'll add something")], a: Annotated[int, Query(description="Int added")], current_user: User = Depends(get_current_inactive_user)):
return {f"{x} to the power of {a}": int(x)**int(a)}

@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)):
return {f"{x} * (1 + {r}) equals": int(x) * (1 + float(r))}

@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")

return {"access_token": user.username, "token_type": "bearer"}


@app.get("/info")
async def info(settings: Annotated[Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}


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():
request_count = get_saved_value()
return {"request_count": request_count}


# On "lance" les fonctions pour qu'elles soient visibles par l'app FastAPI
read_users_me()
rendement(x=0, r=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
"""
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"])
"""
18 changes: 18 additions & 0 deletions decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 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}
47 changes: 47 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
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, rendement
import requests
import asyncio


def power(x, a):
print(requests.get(f"http://127.0.0.1:8000/power/?x={x}&a={a}").json())


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={lst[1]}").json())



if __name__ == "__main__":
power(x=9, a=2)
add(x=9, a=2)
sous(x=9, lst=[2, 1])
print(rendement(x=0, r=0.0))
print(power_function(x=9, a=2))
print(add_function(x=9, a=2))
print(sous_function(x=9, lst=[2, 1]))
6 changes: 6 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "Awesome"
admin_email: str = "Et"
items_per_user: int = 50
model_config = SettingsConfigDict(env_file=".env")