Skip to main content

Package de développement d'API basé FastAPI

Project description

I - Présentation

Logo

1. Description

Passioné par la programmation et le développement avec python je me lance dans la création progressive d'une bibliothèque personnalisée basé sur pour FASTAPI m'ameliorer , devenir plus productif et partager mon expertise .

2. Objectifs

ElrahAPI permet notament dans le cadre d'un développement avec FASTAPI de :

  • Démarrer rapidement un projet en fournissant une architecture de dossier ;

  • Minimiser les configurations de base de données et de gestion des sessions pour un projet ;

  • Fournir et gérer un système d'authentification simple et configurable ;

  • Générer les principaux cruds d'un model ;

  • Fournir Configurer facilement les routes avec des configurations personnalisées ;

  • Pemettre d'utiliser les sessions asynchrones ;

  • Fourni des classes pour gérer les seeders ;

  • Permet d'effectuer un enregistrement des logs dans la base de donnée grâce à un middleware de log ;

  • Fournir un middleware de gestion d'erreur ;

  • Une gestion simple et efficace de l'autorisation par l'utilisation de rôles et privileges ;

  • Fournir une pile d'utilitaires ;

  • L'utilisation de gestionnaire de websocket pour particulièrement envoyer les erreurs des requêtes .

II - Installation

Il serait judicieux de créer un environnement virtuel dans un repertoire avant de poursuivre l'installation

  • Créer un environnement virtuel :
    python -m venv env

ou si virtualenv est dejà installé au préalable

    virtualenv env
  • Avec Github :

    git clone https://github.com/Harlequelrah/Library-ElrahAPI
    
    cd Library-ElrahAPI
    
    pip install -e ./elrahapi
    
  • Avec pip :

    pip install elrahapi
    

III - Lancez vous !

1. Quelques recommandations :

  • Il est recommandé de créer un environnement virtuel pour chaque projet ;

  • myproject designe le nom de votre projet ;

  • myapp designe le nom d'une application ;

  • Après la creation du projet configurer l'environnement .

2. créer un projet

   elrahapi startproject myproject

3. configurer l'environnement

  • Ouvrez le fichier .env et configurez le !

  • Configurer alembic au besoin :

    • Configurer le alembic.ini par son paramètre sqlalchemy.url :

      • exemple pour sqlite : sqlite:///database.db
    • Configurer le alembic/env.py :

      • Ajouter l'import : from myproject.settings.models_metadata import Base ;

      • Passer les metadata à target_metadata : database.target_metadata=Base.metadata ;

4. Demarrer le projet

  • Accéder au repertoire du projet :
  cd myproject
  • Créer les tables :
  elrahapi create_tables
  • Démarrer le serveur :
  elrahapi run

5. Créer une application

  • Assurer vous d'être dans le dossier du projet

  • Génerer l'application

  elrahapi startapp myapp

6. Configurer une application

  • Ouvrer le dossier myproject/myapp

6.1. Définir les models de l'application

Entity représente le nom d'une entité de base de donnée .

  • Créer les models SQLAlchemy dans models.py

  • Créer les schémas Pydantic dans schemas.py

  • Créer les meta models dans meta_models.py si nécessaire ;

  • Ajouter le model dans models_metadata.py comme suite : from .myapp.models import Model

Note: :

Avec SQLAlchemy en asynchrone , si dans vos schémas vous retourner des relations d'un model , il faudra ajouter lazy=joined aux relationship des models SQLAlchemy .

Pour les schémas pydantic l'on pourra d'abord créer ou non un EntityBaseModel dans meta_models.py pour le réutiliser au besoin dans les autres schémas .

Dans schemas.py il peut y avoir généralement :

  • EntityCreateModel : pour la création d' une entité ;

  • EntityUpdateModel : pour la mise à jour totale d'une entité ;

  • EntityPatchModel : pour la mise à jour partielle d'une 'entité ;

  • EntityReadModel : pour la lecture partielle d'une entité ;

  • EntityFullReadModel : pour la lecture totale d'une entité avec ses relations ;

exemple :

  class User( UserModel,database.base):
    user_privileges = relationship("UserPrivilege", back_populates="user",lazy="joined")
    user_roles=relationship("UserRole",back_populates="user",lazy="joined")

  class UserFullReadModel(UserReadModel) :
    user_roles:List["MetaUserRoleModel"] = []
    user_privileges: List["MetaUserPrivilegeModel"]=[]

6.2. Créer les cruds

Dans cruds.py

  • Créer un CrudModels

  • Créer un CrudForgery dans cruds.py

exemple :

myapp_crud_models = CrudModels(
    entity_name="myapp",
    primary_key_name="id",  #remplacer au besoin par le nom de la clé primaire
    SQLAlchemyModel=Entity, #remplacer par l'entité SQLAlchemy
    ReadModel=EntityReadModel,
    CreateModel=EntityCreateModel, #Optionel
    UpdateModel=EntityUpdateModel, #Optionel
    PatchModel=EntityPatchModel, #Optionel
    FullReadModel=EntityFullReadModel #Optionel
)
myapp_crud = CrudForgery(
    crud_models=myapp_crud_models,
    session_manager=database.session_manager
)

6.3. Configurer le fournisseur de routage de l'application

Configurer le CustomRouterProvider dans router.py

  • Configuration de base

Il faut au préalable s'assurer importer le crud depuis myapp/cruds

   router_provider = CustomRouterProvider(
    prefix="/items",
    tags=["item"],
    crud=myapp_crud
)
  • Configuration avec authentification et autorisation

Pour utiliser les méthodes qui peuvent prendre en compte des routes protégées faut s'assurer d'ajouter l'attribut authentication . Avec ce paramètre on peut aussi gérer les autorisation en ajoutant des roles et privileges directement qui seront utilisés par toutes les routes .

   router_provider = CustomRouterProvider(
    prefix="/items",
    tags=["item"],
    crud=myapp_crud,
    authentication = authentication,
    roles = ["ADMIN"],
    privileges = [
    "CAN_CREATE_BOOK",
    "CAN_DELETE_CATEGORY"
    ]
)
  • Configuration des models de réponse :

La configuration des relations se fait par le paramètre read_with_relations par défaut à False qui détermine si les models de réponses doivent inclure ou non les relations c'est à dire si EntityReadModel sera utilisé ou EntityFullReadModel

   router_provider = CustomRouterProvider(
    prefix="/items",
    tags=["item"],
    crud=myapp_crud,
    read_with_relations = True
)
  • Configuration des relations :

Cette configuration se fait par le paramètre relations qui définit une liste d'instance de Relationship.

exemple :

   router_provider = CustomRouterProvider(
    prefix="/items",
    tags=["item"],
    crud=myapp_crud,
    relations=[
    user_role_relation,
    post_relation
    ],
)

exemples de relations :

Note : RELATION_RULES est un dictionnaire où la clé est le type de relation et la valeur une liste des routes permises .

  • Relation plusieurs à plusieurs avec une classe SQLAlchemy intermédiaire :
user_role_relation: Relationship = Relationship(
    relationship_name="user_roles",
    second_entity_crud=role_crud,
    relationship_crud=user_role_crud,
    type_relation=TypeRelation.MANY_TO_MANY_CLASS,
    relationship_key1_name="user_id",
    relationship_key2_name="role_id",
    default_public_relation_routes_name=[
      RelationRoutesName.READ_ALL_BY_RELATION,
      RelationRoutesName.DELETE_RELATION,
    ],
)
  • Relation plusieurs à plusieurs avec table Table intermédiaire :
tag_relation: Relationship = Relationship(
    relation_table=post_tag_table,
    relationship_name="tags",
    second_entity_crud=tag_crud,
    relationship_key1_name="post_id",
    relationship_key2_name="tag_id",
    type_relation=TypeRelation.MANY_TO_MANY_TABLE,
    default_public_relation_routes_name=RELATION_RULES[TypeRelation.MANY_TO_MANY_TABLE],
)
  • Relation un à un :
profile_relation: Relationship = Relationship(
    relationship_name="profile",
    second_entity_crud=profile_crud,
    type_relation=TypeRelation.ONE_TO_ONE,
    default_public_relation_routes_name=RELATION_RULES[TypeRelation.ONE_TO_ONE],
)
  • Relation un à plusieurs :
post_relation: Relationship = Relationship(
    relationship_name="posts",
    second_entity_crud=post_crud,
    second_entity_fk_name="user_id",
    type_relation=TypeRelation.ONE_TO_MANY,
    default_public_relation_routes_name=RELATION_RULES[TypeRelation.ONE_TO_MANY],
)

Note : second_entity_fk_name est facultatif et permet d'attribuer cette valeur automatiquement dans la création , mise à jour partielle , mise à jour totale de l'entité secondaire . Il faut tout de même avoir ce champ dans les schémas correspondant ,et il pourra désormais etre optionel .

  • Relation plusieurs à un :
user_relation: Relationship = Relationship(
    relationship_name="user",
    second_entity_crud=user_crud,
    type_relation=TypeRelation.MANY_TO_ONE,
    default_public_relation_routes_name=RELATION_RULES[TypeRelation.MANY_TO_ONE],
)

6.4. Configurer un router

Les possibilités de configuration d'un routeur :

  • Créér des configurations de routes
  custom_init_data: List[RouteConfig] = [
    RouteConfig(
      route_name=DefaultRoutesName.CREATE,
      is_activated=True,
      read_with_relations = False
     ),
    RouteConfig(
      route_name=DefaultRoutesName.READ_ONE,
      is_activated=True,
      roles= ["SECRETARY"],
      is_protected=True
     ),
    RouteConfig(
      route_name=DefaultRoutesName.READ_ALL,
      is_activated=True,
      privileges=["CAN_CREATE_MEET"],
      is_protected=True
     ),
    RouteConfig(route_name=DefaultRoutesName.UPDATE, is_activated=True),
    RouteConfig(route_name=DefaultRoutesName.DELETE, is_activated=True),
]
  • Création des configurations de routes de relation
user_relation: Relationship = Relationship(
    relationship_name="user",
    second_entity_crud=user_crud,
    type_relation=TypeRelation.MANY_TO_ONE,
    default_public_relation_routes_name=[
    RouteConfig(route_name=RelationRoutesName.UPDATE_BY_RELATION, is_activated=True)
    ],
)
  • Création des configurations d'authorizations de routes
  custom_authorizations : List[AuthorizationConfig] = [
  AuthorizationConfig(route_name=DefaultRoutesName.DELETE,roles=["ADMIN","MANAGER"]),
  AuthorizationConfig(route_name=DefaultRoutesName.UPDATE,privileges=["CAN_UPDATE_ENTITY"]
  ]
  • Création des configurations de model de réponse pour les routes
  custom_response_models : List[ResponseModelConfig] = [
  ResponseModelConfig(route_name=DefaultRoutesName.READ_ONE,response_model=MyModel),
  ResponseModelConfig(route_name=DefaultRoutesName.READ_ALL,read_with_relations=True
  ]
  • Créer un router en initialisant totalement une configuration
app_myapp = router_provider.initialize_router(
init_data=custom_init_data,
)

le paramètre exclude_routes_name pourra éventuellement être utilisé pour exclure certaines routes.

le paramètre authorizations pourra éventuellement utilisés pour configurer les permissions .

le paramètre response_model_configs pourra éventuellement utilisés pour configurer les models de reponses .

  • Créér un router préconfiguré sans authentification
  app_myapp = router_provider.get_public_router()
  • Créér un router préconfiguré avec authentification
  app_myapp = router_provider.get_protected_router(
  authorizations=custom_authorizations
  )
  • Créer un router avec configuration et des routes publiques
  app_myapp = router_provider.get_custom_router(
    init_data= custom_init_data ,
    routes_name=[DefaultRoutesName.PATCH],
    exclude_routes_name=[DefaultRoutesName.READ_ONE],
    type_route=TypeRoute.PUBLIC
  )
  • Créer un router avec éventuellement une configuration et avec des routes publics et protégées
  app_myapp = router_provider.get_mixed_router(
    protected_routes_name=[DefaultRoutesName.COUNT],
    public_routes_name=[DefaultRoutesName.READ_ALL]
  )

Note : Ajouter le router au main.py

app.include_router(app_myapp)

7. Configurer les logs

  • Configurer au besoin settings/logger et ajouter son routeur au myproject/main.py

  • Dans le fichier myproject/main.py du projet , ajouter et configurer le middleware de logs et ou celui d'erreur :

from elrahapi.middleware.log_middleware import LoggerMiddleware
from elrahapi.middleware.error_middleware import ErrorHandlingMiddleware
from elrahapi.middleware.middleware_helper import MiddlewareHelper
from settings.logger.router import app_logger
from settings.logger.model import LogModel
from settings.database import database

app = FastAPI()
app.include_router(app_logger)
middleware_helper = MiddlewareHelper(LogModel=LogModel, authentication=authentication)
app.add_middleware(ErrorHandlingMiddleware, middleware_helper=middleware_helper)
app.add_middleware(LoggerMiddleware, middleware_helper=middleware_helper)

Note: Il est recommandé d'utiliser l'ordre des middlewares comme dans l'exemple et de configurer aussi le middleware d'erreur pour avoir les logs des erreurs aussi.

8. Configurer l'authentification:

  • Configurer au besoin myproject/settings/auth

  • Ajouter au besoin les routers du myproject/settings/auth/routers au myproject/main.py

  • Ajouter au besoin le router pour l'authentification du myproject/settings/auth/configs au myproject/main.py

9. Utilisation des seeders

Deux classes principales servent au seeders . Seed pour un seed simple et SeedManager pour gérer les opérations de plusieurs Seed simultanément.

Dans le repertoire myproject/settings/seeders du projet vous trouverez les seeders par défaut.

Vous pouvez voir des fichers seeders Seed comme user_seed.py et ``SeedManagercommeseed_manager_seed.py`.

Dans le repertoire seeders/log il y a le ficher seeders_logger.py qui contient un logger pour enregistrer les logs des seeders dans le fichier seeders.log.

9.1. Creer un seeder

elrahapi create_seed nomduseeder

ex : nomduseeder peut être user , book ou item etc...

Note : Ensuite mettez à jour les imports et le ficher

9.2. Lancer un seeder

Seed ou SeedManager se lance en mode up ou down. Le mode up pour seeder et down pour rollback. Sans précision c'est le mode up qui sera actif.

9.2.1 Seed

exemple :

elrahapi run_seed nomduseeder

ou

elrahapi run_seed nomduseeder down

9.2.1 SeedManager

exemple :

elrahapi run_seed_manager up

la variable seeds_dict est un dictionnaire qui contient les seeders et leurs noms.

On peut toutes fois remplir seeds_name avec des noms de seeders pour en exécuter que ceux là .

En mode up les seeders sont exécutés dans l'ordre de leur nom dans le dictionnaire seeds_dict et dans l'ordre inverse en mode down .

9.3. Seeder des privlièges

Il est possible de seeder des privilèges pour des entités précises et obtenir des privileges génériques facilement comme CAN CREATE BOOK ou CAN READ ITEM etc...

Cela se passe dans seeders/privilege_seed.py.

exemple :

from elrahapi.utility.utils import get_entities_all_privilege_data

data: list[PrivilegeCreateModel] = []
entities_name:list[str]=[
    "user",
    "role",
    "role_privilege",
    "user_privilege",
    "user_role",
    "privilege",
    ] # lister les entités pour lesquelles on veut créer des privilèges génériques
entities_data =  get_entities_all_privilege_data(entities_names=entities_name)
data.extend(entities_data)

10. Utilisation de ConnectionManager et de ChatManager

La classe ConnectionManager permet de gérer les websockets de façon basique . Exemple :

Il est aussi possible d'ajouter un objet de type ConnectionManager aux middlewares pour notifier les erreurs dans le projet

from elrahapi.websocket.connectionManager import ConnectionManager
from fastapi import (
    FastAPI,
    WebSocketDisconnect,
    WebSocket,
)
app = FastAPI()
websocket_manager = ConnectionManager()

@app.websocket("/ws/notifications")
async def websocket_notification(websocket: WebSocket):
    await websocket_manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await websocket_manager.broadcast(data)
    except WebSocketDisconnect:
        await websocket_manager.disconnect(websocket)

La classe ChatManager permet une gestion plus poussé des websockets avec utilisations des rooms et des sub.

exemple :

chat_manager = ChatConnectionManager()
@app.websocket("/ws/room/{room_name}")
async def chat_websocket(websocket:WebSocket,room_name:str,sub:str=Query(...)):
    chat_manager.create_room(room_name=room_name)
    await chat_manager.connect(websocket=websocket,room_name=room_name,sub=sub)
    try:
        while True:
            data= await websocket.receive_text()
            await chat_manager.broadcast(room_name=room_name,message=f"{sub} a dit : {data}")
    except WebSocketDisconnect:
        sub = await chat_manager.disconnect(websocket=websocket,room_name=room_name)

Note: : Pour en savoir plus , vous pouvez consulter : https://github.com/Harlequelrah/learning_websocket

11. Utilisation de certaines fonctions utiles :

  • raise_custom_http_exception : permet de lever un CustomHttpException
  from elrahapi.exception.exception_utils import raise_custom_http_exception
  from fastapi import status
  raise_custom_http_exception(
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
  detail="Cette requête est intraitable par le serveur")
  • validate_value : permet de valider une valeur
  from elrahapi.utility.utils import validate_value
  a = validate_value("True") # a contient True
  b = validate_value("false") # b continent False
  c = validate_value("1")  # c contient 1
  • update_entity : permet de mettre à jour un objet sqlalchemy
  from elrahapi.utility.utils import update_entity
        existing_plant = update_entity(
            existing_entity=existing_plant,
            update_entity=plant_update_obj
        )

12. Patterns :

  • TELEPHONE_PATTERN

  • URL_PATTERN

exemple :

from elrahapi.utility.patterns import TELEPHONE_PATTERN
class Test(BaseModel):
    telephone: str = Field(
        example="+22891361029",
        pattern=TELEPHONE_PATTERN,
        description="Telephone number must be in the format +<country_code><number>"
    )

13. Generation de clé :

Vous pouve générer une clé pour coder vos tokens JWT comme suit :

elrahapi generate_secret_key

Note : par défaut l'algorithme est HS256

elrahapi generate_secret_key HS512

Note : vous pouvez choisir entre HS256 , HS384 et HS512 .

V - Contact ou Support

Pour des questions ou du support, contactez-moi à maximeatsoudegbovi@gmail.com ou au (+228) 91 36 10 29.

La version actuelle est le 1.2.0.4

Vérifier la version en executant pip show elrahapi

Pour un exemple concret , vous pouvez consulter la branche du repository de test pour cette version ou la plus récente si les améliorations son minimes: https://github.com/Harlequelrah/elrahapi-testproject

Vous pouvez consulter la documentation technique pour découvrir toutes les fonctionnaliés :

docs/
├── README.md

Note : la documentation technique n'est pas à jour.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

elrahapi-1.2.0.7.tar.gz (53.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

elrahapi-1.2.0.7-py3-none-any.whl (73.8 kB view details)

Uploaded Python 3

File details

Details for the file elrahapi-1.2.0.7.tar.gz.

File metadata

  • Download URL: elrahapi-1.2.0.7.tar.gz
  • Upload date:
  • Size: 53.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for elrahapi-1.2.0.7.tar.gz
Algorithm Hash digest
SHA256 58213f82033bd8b91b07f78a93bb6876428d25c9adbd71d895d109c536b6f5c6
MD5 ed03cff8218dd3c14e0cf69fae1a488f
BLAKE2b-256 f2a24acffba5deb19668518da2f1f35d781adf52c4f0b64cece181e9407b3b46

See more details on using hashes here.

Provenance

The following attestation bundles were made for elrahapi-1.2.0.7.tar.gz:

Publisher: python-publish.yml on Harlequelrah/Library-ElrahAPI

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file elrahapi-1.2.0.7-py3-none-any.whl.

File metadata

  • Download URL: elrahapi-1.2.0.7-py3-none-any.whl
  • Upload date:
  • Size: 73.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for elrahapi-1.2.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 dbc2adfba37c7d23c8169e03a8faeb8ae13ad958f61827c22395a210b72d25ec
MD5 870366b75d971b41e5a3a10f828daeb5
BLAKE2b-256 449d050909766b5e053100da3c55de766ee87006e1860df415d790185c6357b0

See more details on using hashes here.

Provenance

The following attestation bundles were made for elrahapi-1.2.0.7-py3-none-any.whl:

Publisher: python-publish.yml on Harlequelrah/Library-ElrahAPI

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page