Metadata-Version: 2.4
Name: elrahapi
Version: 1.2.0.9
Summary: Package de développement d'API basé FastAPI
Home-page: https://github.com/Harlequelrah/Library-ElrahAPI
Author: Harlequelrah
Author-email: maximeatsoudegbovi@gmail.com
License: LGPL-3.0-only
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: fastapi[standard]>=0.112.0
Requires-Dist: alembic>=1.13.3
Requires-Dist: argon2-cffi>=23.1.0
Requires-Dist: python-dotenv>=1.0.1
Requires-Dist: python-jose[cryptography]>=3.3.0
Requires-Dist: black>=24.10.0
Requires-Dist: sqlalchemy>=2.0.38
Requires-Dist: sqlalchemy-utils>=0.41.2
Requires-Dist: aiosqlite>=0.21.0
Requires-Dist: colorama>=0.4.6
Dynamic: author
Dynamic: author-email
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# I - **`Présentation`**

![Logo](https://raw.githubusercontent.com/Harlequelrah/Library-ElrahAPI/main/Elrah.png)

# **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 :**

```bash
    python -m venv env
```

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

```bash
    virtualenv env
```

- **`Avec Github :`**

  ```bash
  git clone https://github.com/Harlequelrah/Library-ElrahAPI

  cd Library-ElrahAPI

  pip install -e ./elrahapi
  ```

- **`Avec pip :`**

  ```bash
  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`

```bash
   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 :

```bash
  cd myproject
```

- Créer les tables :

```bash
  elrahapi create_tables
```

- Démarrer le serveur :

```bash
  elrahapi run
```

## **5.** `Créer une application`

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

- Génerer l'application

```bash
  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 : `**

```python
  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 : `**

```python
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`

```python
   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 .

```python
   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`

```python
   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`** :

```python
   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 :

```python
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 :

```python
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 :

```python
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 :

```python
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 :

```python
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`**

```python
  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`**

```python
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`**

```python
  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`**

```python
  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`**

```python
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`**

```python
  app_myapp = router_provider.get_public_router()
```

- **`Créér un router préconfiguré avec authentification`**

```python
  app_myapp = router_provider.get_protected_router(
  authorizations=custom_authorizations
  )
```

- **`Créer un router avec configuration et des routes publiques`**

```python
  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`**

```python
  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`

```python
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 :

```python
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 ``SeedManager` comme `seed_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`

```cmd
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`** :

```cmd
elrahapi run_seed nomduseeder
```

ou

```cmd
elrahapi run_seed nomduseeder down
```

### **9.2.1** `SeedManager`

**`exemple`** :

```cmd
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`** :

```python
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

```python
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`** :

```python

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

```python
  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

```python
  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

```python
  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 :**

```python
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 :

```cmd
elrahapi generate_secret_key
```

**Note** : par défaut l'algorithme est HS256

```cmd
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`.
