465 lines
18 KiB
Markdown
465 lines
18 KiB
Markdown
# Audit et Plan de Refactorisation - app_optimized.py
|
|
|
|
**Date**: 2024-12-14
|
|
**Fichier analysé**: `app/app_optimized.py`
|
|
**Lignes totales**: 7098
|
|
**Framework**: FastAPI + SQLAlchemy async
|
|
|
|
---
|
|
|
|
## PHASE 1: AUDIT ET VALIDATION
|
|
|
|
### 1.1 Statistiques du Fichier
|
|
|
|
| Métrique | Valeur |
|
|
|----------|--------|
|
|
| Lignes de code | 7098 |
|
|
| Classes | 21 |
|
|
| Fonctions/Méthodes | ~150 |
|
|
| Endpoints API | ~65 |
|
|
| Modèles Pydantic | 28 |
|
|
| Services | 6 |
|
|
|
|
### 1.2 Structure Actuelle
|
|
|
|
```
|
|
app_optimized.py (7098 lignes)
|
|
├── Imports (L1-53)
|
|
├── Configuration globale (L55-100)
|
|
├── Utilitaires PDF/Markdown (L105-548)
|
|
├── Modèles Pydantic (L550-932)
|
|
├── Services métier (L937-2893)
|
|
│ ├── TaskLogService
|
|
│ ├── AdHocHistoryService
|
|
│ ├── BootstrapStatusService
|
|
│ ├── HostStatusService
|
|
│ └── SchedulerService
|
|
├── WebSocketManager (L2896-2929)
|
|
├── AnsibleService (L2933-3561)
|
|
├── Bootstrap SSH (L3564-3932)
|
|
├── HybridDB (L3935-4089)
|
|
├── Endpoints API (L4092-7023)
|
|
│ ├── Help/Documentation
|
|
│ ├── Hosts CRUD
|
|
│ ├── Groups management
|
|
│ ├── Tasks CRUD
|
|
│ ├── Logs CRUD
|
|
│ ├── Ansible execution
|
|
│ ├── Playbooks CRUD
|
|
│ ├── Bootstrap
|
|
│ ├── Ad-hoc history
|
|
│ ├── Health checks
|
|
│ ├── Schedules CRUD
|
|
│ └── Notifications
|
|
└── Startup/Shutdown events (L7025-7098)
|
|
```
|
|
|
|
---
|
|
|
|
## 1.3 Problèmes Identifiés
|
|
|
|
### 🔴 CRITIQUES (Bloquants)
|
|
|
|
| ID | Problème | Lignes | Impact |
|
|
|----|----------|--------|--------|
|
|
| C1 | **God Object Anti-pattern** | 1-7098 | Fichier monolithique de 7000+ lignes, impossible à maintenir |
|
|
| C2 | **Variables globales multiples** | 2889-2893, 3561, 4089 | État partagé entre modules (`db`, `ansible_service`, `ws_manager`, `scheduler_service`) |
|
|
| C3 | **Couplage fort** | Partout | Services dépendent directement d'instances globales |
|
|
| C4 | **Imports circulaires potentiels** | 44-53, 2105-2106 | Mix `from models.xxx` et `from app.models.xxx` |
|
|
| C5 | **Duplication de modèles Pydantic** | 550-932 | Modèles dupliqués avec `app/schemas/` existants |
|
|
|
|
### 🟠 AVERTISSEMENTS (À corriger)
|
|
|
|
| ID | Problème | Lignes | Recommandation |
|
|
|----|----------|--------|----------------|
|
|
| W1 | Fonctions trop longues | 3676-3932 (`bootstrap_host`: 256L) | Extraire en sous-fonctions |
|
|
| W2 | Import `re` dupliqué | 688, 706, 5406 | Import unique en tête de fichier |
|
|
| W3 | Syntaxe Pydantic dépréciée | 572-575, 589-592 | `class Config` → `model_config = ConfigDict(...)` |
|
|
| W4 | Print statements | 1995, 2008, 2058... | Utiliser `logging` |
|
|
| W5 | Gestion d'erreur inconsistante | Multiple | Standardiser avec exceptions personnalisées |
|
|
| W6 | Magic strings | 564, 581, 597... | Utiliser des Enums (Status, Level) |
|
|
| W7 | Cache multi-niveau | Multiple | Risque de désynchronisation DB/mémoire |
|
|
| W8 | Méthodes sync/async mélangées | 2141-2146 | `start()` sync vs `start_async()` async |
|
|
| W9 | Exceptions génériques | 2007, 2059... | `except Exception` trop large |
|
|
| W10 | Variables non utilisées | Sporadique | Nettoyer le code mort |
|
|
|
|
### 🔵 INFORMATIFS (Améliorations)
|
|
|
|
| ID | Problème | Recommandation |
|
|
|----|----------|----------------|
|
|
| I1 | Type hints manquants | Ajouter types sur toutes les fonctions |
|
|
| I2 | Docstrings mixtes FR/EN | Uniformiser en français |
|
|
| I3 | Constantes non regroupées | Créer un module `constants.py` |
|
|
| I4 | Tests unitaires insuffisants | Ajouter tests pour chaque service |
|
|
| I5 | Validation Pydantic incomplète | Ajouter validators sur tous les champs critiques |
|
|
|
|
---
|
|
|
|
## 1.4 Analyse de la Complexité
|
|
|
|
### Complexité Cyclomatique Élevée
|
|
|
|
| Fonction | Lignes | Complexité estimée | Action |
|
|
|----------|--------|-------------------|--------|
|
|
| `bootstrap_host` | 256 | Très élevée | Refactoriser en étapes |
|
|
| `_execute_schedule` | 270 | Très élevée | Extraire logique métier |
|
|
| `execute_adhoc_command` | 250 | Élevée | Simplifier gestion erreurs |
|
|
| `execute_ansible_playbook` | 150 | Modérée | Extraire en service |
|
|
| `_markdown_to_pdf_bytes` | 190 | Élevée | Module séparé |
|
|
|
|
### Violations SOLID
|
|
|
|
| Principe | Violation | Exemple |
|
|
|----------|-----------|---------|
|
|
| **S**ingle Responsibility | Oui | Fichier fait tout (routing, services, models, utils) |
|
|
| **O**pen/Closed | Oui | Ajout de fonctionnalité = modification du fichier |
|
|
| **L**iskov Substitution | Non applicable | - |
|
|
| **I**nterface Segregation | Oui | Services trop couplés aux endpoints |
|
|
| **D**ependency Inversion | Oui | Dépendances hardcodées, pas d'injection |
|
|
|
|
---
|
|
|
|
## PHASE 2: PLAN DE REFACTORISATION
|
|
|
|
### 2.1 Nouvelle Structure Proposée
|
|
|
|
```
|
|
projet/
|
|
├── main.py # Point d'entrée uvicorn
|
|
├── app/
|
|
│ ├── __init__.py # Factory create_app()
|
|
│ ├── core/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── config.py # Configuration centralisée
|
|
│ │ ├── constants.py # Constantes et enums
|
|
│ │ ├── exceptions.py # Exceptions personnalisées
|
|
│ │ └── dependencies.py # Injection de dépendances FastAPI
|
|
│ │
|
|
│ ├── models/ # (existant - conserver)
|
|
│ │ ├── __init__.py
|
|
│ │ ├── database.py
|
|
│ │ ├── host.py
|
|
│ │ ├── task.py
|
|
│ │ ├── log.py
|
|
│ │ ├── schedule.py
|
|
│ │ ├── schedule_run.py
|
|
│ │ └── user.py
|
|
│ │
|
|
│ ├── schemas/ # (existant - enrichir)
|
|
│ │ ├── __init__.py
|
|
│ │ ├── host.py # HostRequest, HostUpdateRequest, Host
|
|
│ │ ├── task.py # TaskRequest, Task, TaskLogFile
|
|
│ │ ├── group.py # GroupRequest, GroupUpdateRequest
|
|
│ │ ├── ansible.py # AnsibleExecutionRequest, AdHocCommandRequest
|
|
│ │ ├── schedule.py # Schedule*, ScheduleRun*, ScheduleRecurrence
|
|
│ │ ├── health.py # HealthCheck, SystemMetrics
|
|
│ │ └── common.py # CommandResult, LogEntry
|
|
│ │
|
|
│ ├── crud/ # (existant - conserver)
|
|
│ │ ├── __init__.py
|
|
│ │ ├── host.py
|
|
│ │ ├── task.py
|
|
│ │ ├── log.py
|
|
│ │ ├── schedule.py
|
|
│ │ └── schedule_run.py
|
|
│ │
|
|
│ ├── services/ # (existant - enrichir)
|
|
│ │ ├── __init__.py
|
|
│ │ ├── ansible_service.py # AnsibleService
|
|
│ │ ├── task_log_service.py # TaskLogService
|
|
│ │ ├── adhoc_history_service.py # AdHocHistoryService
|
|
│ │ ├── bootstrap_service.py # BootstrapStatusService + bootstrap_host
|
|
│ │ ├── host_status_service.py # HostStatusService
|
|
│ │ ├── scheduler_service.py # SchedulerService
|
|
│ │ ├── websocket_service.py # WebSocketManager
|
|
│ │ └── hybrid_db.py # HybridDB
|
|
│ │
|
|
│ ├── routes/
|
|
│ │ ├── __init__.py # Router aggregator
|
|
│ │ ├── hosts.py # /api/hosts/*
|
|
│ │ ├── groups.py # /api/groups/*
|
|
│ │ ├── tasks.py # /api/tasks/*
|
|
│ │ ├── logs.py # /api/logs/*
|
|
│ │ ├── ansible.py # /api/ansible/*
|
|
│ │ ├── playbooks.py # /api/playbooks/*
|
|
│ │ ├── schedules.py # /api/schedules/*
|
|
│ │ ├── adhoc.py # /api/adhoc/*
|
|
│ │ ├── bootstrap.py # /api/bootstrap/*
|
|
│ │ ├── health.py # /api/health/*
|
|
│ │ ├── notifications.py # /api/notifications/*
|
|
│ │ ├── help.py # /api/help/*
|
|
│ │ └── websocket.py # /ws
|
|
│ │
|
|
│ ├── utils/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── pdf_generator.py # _markdown_to_pdf_bytes
|
|
│ │ ├── markdown_parser.py # _HelpHtmlToMarkdownParser
|
|
│ │ ├── ssh_utils.py # find_ssh_private_key, run_ssh_command
|
|
│ │ └── helpers.py # Fonctions utilitaires diverses
|
|
│ │
|
|
│ └── middleware/
|
|
│ ├── __init__.py
|
|
│ └── auth.py # verify_api_key (déjà dans auth_service)
|
|
│
|
|
├── tests/ # (existant - enrichir)
|
|
│ ├── __init__.py
|
|
│ ├── test_hosts.py
|
|
│ ├── test_tasks.py
|
|
│ ├── test_schedules.py
|
|
│ └── conftest.py
|
|
│
|
|
└── requirements.txt
|
|
```
|
|
|
|
### 2.2 Mapping Fonctionnalité → Fichier
|
|
|
|
| Fonctionnalité actuelle | Lignes | Nouveau fichier |
|
|
|------------------------|--------|-----------------|
|
|
| Configuration | 55-100 | `app/core/config.py` |
|
|
| Modèles Pydantic Host/Task | 560-800 | `app/schemas/host.py`, `app/schemas/task.py` |
|
|
| Modèles Schedule | 803-932 | `app/schemas/schedule.py` |
|
|
| Modèles Ad-hoc | 718-760 | `app/schemas/ansible.py` |
|
|
| TaskLogService | 937-1568 | `app/services/task_log_service.py` |
|
|
| AdHocHistoryService | 1571-1951 | `app/services/adhoc_history_service.py` |
|
|
| BootstrapStatusService | 1954-2060 | `app/services/bootstrap_service.py` |
|
|
| HostStatusService | 2063-2099 | `app/services/host_status_service.py` |
|
|
| SchedulerService | 2102-2886 | `app/services/scheduler_service.py` |
|
|
| WebSocketManager | 2896-2929 | `app/services/websocket_service.py` |
|
|
| AnsibleService | 2933-3561 | `app/services/ansible_service.py` |
|
|
| Bootstrap SSH functions | 3564-3932 | `app/services/bootstrap_service.py` |
|
|
| HybridDB | 3935-4089 | `app/services/hybrid_db.py` |
|
|
| Endpoints Hosts | 4224-4698 | `app/routes/hosts.py` |
|
|
| Endpoints Groups | 4234-4416 | `app/routes/groups.py` |
|
|
| Endpoints Tasks | 4691-5060 | `app/routes/tasks.py` |
|
|
| Endpoints Logs | 5062-5134 | `app/routes/logs.py` |
|
|
| Endpoints Ansible | 5157-5530 | `app/routes/ansible.py` |
|
|
| Endpoints Playbooks | 5352-5491 | `app/routes/playbooks.py` |
|
|
| Endpoints Ad-hoc | 5533-5782, 5972-6070 | `app/routes/adhoc.py` |
|
|
| Endpoints Bootstrap | 5785-5969 | `app/routes/bootstrap.py` |
|
|
| Endpoints Health | 5902-6112 | `app/routes/health.py` |
|
|
| Endpoints Schedules | 6388-6949 | `app/routes/schedules.py` |
|
|
| Endpoints Notifications | 6952-7022 | `app/routes/notifications.py` |
|
|
| PDF/Markdown utils | 105-548 | `app/utils/pdf_generator.py`, `app/utils/markdown_parser.py` |
|
|
| SSH utils | 3581-3673 | `app/utils/ssh_utils.py` |
|
|
| Startup/Shutdown | 7025-7098 | `app/__init__.py` (create_app) |
|
|
|
|
---
|
|
|
|
## PHASE 3: ORDRE D'EXÉCUTION
|
|
|
|
### Étape 1: Core (config, constants, exceptions, dependencies)
|
|
1. Créer `app/core/__init__.py`
|
|
2. Créer `app/core/config.py` - Centraliser toute la configuration
|
|
3. Créer `app/core/constants.py` - Enums et constantes
|
|
4. Créer `app/core/exceptions.py` - Exceptions personnalisées
|
|
5. Créer `app/core/dependencies.py` - DI FastAPI
|
|
|
|
### Étape 2: Schemas (enrichir les existants)
|
|
1. Enrichir `app/schemas/host.py`
|
|
2. Créer `app/schemas/task.py`
|
|
3. Créer `app/schemas/group.py`
|
|
4. Enrichir `app/schemas/ansible.py`
|
|
5. Créer `app/schemas/schedule.py`
|
|
6. Créer `app/schemas/health.py`
|
|
7. Créer `app/schemas/common.py`
|
|
|
|
### Étape 3: Services (nouveaux fichiers)
|
|
1. Créer `app/services/ansible_service.py`
|
|
2. Créer `app/services/task_log_service.py`
|
|
3. Créer `app/services/adhoc_history_service.py`
|
|
4. Créer `app/services/bootstrap_service.py`
|
|
5. Créer `app/services/host_status_service.py`
|
|
6. Créer `app/services/scheduler_service.py`
|
|
7. Créer `app/services/websocket_service.py`
|
|
8. Créer `app/services/hybrid_db.py`
|
|
|
|
### Étape 4: Utils
|
|
1. Créer `app/utils/__init__.py`
|
|
2. Créer `app/utils/pdf_generator.py`
|
|
3. Créer `app/utils/markdown_parser.py`
|
|
4. Créer `app/utils/ssh_utils.py`
|
|
5. Créer `app/utils/helpers.py`
|
|
|
|
### Étape 5: Routes (routers FastAPI)
|
|
1. Créer `app/routes/__init__.py`
|
|
2. Créer tous les fichiers de routes
|
|
3. Migrer chaque groupe d'endpoints
|
|
|
|
### Étape 6: App Factory et Main
|
|
1. Modifier `app/__init__.py` - create_app()
|
|
2. Créer `main.py` - Point d'entrée
|
|
|
|
### Étape 7: Nettoyage et Tests
|
|
1. Supprimer code dupliqué de `app_optimized.py`
|
|
2. Mettre à jour les imports
|
|
3. Adapter les tests existants
|
|
4. Valider le fonctionnement
|
|
|
|
---
|
|
|
|
## CONTRAINTES TECHNIQUES
|
|
|
|
### À Maintenir
|
|
- ✅ 100% des fonctionnalités existantes
|
|
- ✅ Compatibilité avec les tests existants
|
|
- ✅ Support WebSocket temps réel
|
|
- ✅ Intégration SQLAlchemy async
|
|
- ✅ Intégration APScheduler
|
|
- ✅ Service de notifications ntfy
|
|
|
|
### À Éviter
|
|
- ❌ Imports circulaires
|
|
- ❌ Variables globales (sauf configuration)
|
|
- ❌ Couplage fort entre modules
|
|
- ❌ Breaking changes dans l'API
|
|
|
|
### Conventions
|
|
- PEP 8 stricte
|
|
- Type hints sur toutes les signatures
|
|
- Docstrings en français
|
|
- Logging au lieu de print()
|
|
- Tests unitaires pour nouveaux modules
|
|
|
|
---
|
|
|
|
## ESTIMATION
|
|
|
|
| Phase | Durée estimée | Fichiers |
|
|
|-------|---------------|----------|
|
|
| Étape 1: Core | ~30 min | 5 fichiers |
|
|
| Étape 2: Schemas | ~45 min | 7 fichiers |
|
|
| Étape 3: Services | ~90 min | 8 fichiers |
|
|
| Étape 4: Utils | ~30 min | 5 fichiers |
|
|
| Étape 5: Routes | ~120 min | 13 fichiers |
|
|
| Étape 6: Factory | ~15 min | 2 fichiers |
|
|
| Étape 7: Tests | ~60 min | Adaptation |
|
|
|
|
**Total estimé**: ~6-7 heures de travail
|
|
|
|
---
|
|
|
|
---
|
|
|
|
## IMPLÉMENTATION TERMINÉE ✅
|
|
|
|
### Structure Créée
|
|
|
|
```
|
|
projet/
|
|
├── main.py # ✅ Point d'entrée uvicorn
|
|
├── app/
|
|
│ ├── __init__.py # ✅ Export create_app()
|
|
│ ├── factory.py # ✅ Application factory
|
|
│ ├── core/
|
|
│ │ ├── __init__.py # ✅
|
|
│ │ ├── config.py # ✅ Configuration centralisée
|
|
│ │ ├── constants.py # ✅ Enums et constantes
|
|
│ │ ├── exceptions.py # ✅ Exceptions personnalisées
|
|
│ │ └── dependencies.py # ✅ Injection de dépendances
|
|
│ ├── schemas/
|
|
│ │ ├── common.py # ✅ CommandResult, LogEntry, etc.
|
|
│ │ ├── host_api.py # ✅ Host, HostRequest, etc.
|
|
│ │ ├── group.py # ✅ GroupRequest, etc.
|
|
│ │ ├── ansible.py # ✅ AnsibleExecutionRequest, etc.
|
|
│ │ ├── task_api.py # ✅ Task, TaskRequest, TaskLogFile
|
|
│ │ ├── schedule_api.py # ✅ Schedule, ScheduleRecurrence
|
|
│ │ └── health.py # ✅ HealthCheck
|
|
│ ├── services/
|
|
│ │ ├── websocket_service.py # ✅ WebSocketManager
|
|
│ │ ├── host_status_service.py # ✅ HostStatusService
|
|
│ │ ├── bootstrap_status_service.py # ✅ BootstrapStatusService
|
|
│ │ ├── task_log_service.py # ✅ TaskLogService
|
|
│ │ ├── adhoc_history_service.py # ✅ AdHocHistoryService
|
|
│ │ ├── ansible_service.py # ✅ AnsibleService
|
|
│ │ ├── scheduler_service.py # ✅ SchedulerService
|
|
│ │ └── hybrid_db.py # ✅ HybridDB
|
|
│ ├── routes/
|
|
│ │ ├── __init__.py # ✅ Router aggregator
|
|
│ │ ├── hosts.py # ✅ /api/hosts/*
|
|
│ │ ├── groups.py # ✅ /api/groups/*
|
|
│ │ ├── tasks.py # ✅ /api/tasks/*
|
|
│ │ ├── logs.py # ✅ /api/logs/*
|
|
│ │ ├── ansible.py # ✅ /api/ansible/*
|
|
│ │ ├── playbooks.py # ✅ /api/playbooks/*
|
|
│ │ ├── schedules.py # ✅ /api/schedules/*
|
|
│ │ ├── adhoc.py # ✅ /api/adhoc/*
|
|
│ │ ├── bootstrap.py # ✅ /api/bootstrap/*
|
|
│ │ ├── health.py # ✅ /api/health/*
|
|
│ │ ├── notifications.py # ✅ /api/notifications/*
|
|
│ │ ├── help.py # ✅ /api/help/*
|
|
│ │ └── websocket.py # ✅ /ws
|
|
│ └── utils/
|
|
│ ├── __init__.py # ✅
|
|
│ ├── ssh_utils.py # ✅ SSH & Bootstrap
|
|
│ ├── pdf_generator.py # ✅ Markdown to PDF
|
|
│ └── markdown_parser.py # ✅ HTML to Markdown
|
|
```
|
|
|
|
---
|
|
|
|
## INSTRUCTIONS DE MIGRATION
|
|
|
|
### 1. Tester la nouvelle structure
|
|
|
|
```bash
|
|
# Depuis le répertoire racine du projet
|
|
cd c:\dev\git\python\homelab-automation-api-v2
|
|
|
|
# Démarrer avec le nouveau point d'entrée
|
|
python main.py
|
|
|
|
# Ou avec uvicorn
|
|
uvicorn main:app --host 0.0.0.0 --port 8008 --reload
|
|
```
|
|
|
|
### 2. Vérifier les imports
|
|
|
|
Si des erreurs d'import surviennent, vérifier que tous les modules sont correctement importés dans les fichiers `__init__.py`.
|
|
|
|
### 3. Migration progressive
|
|
|
|
L'ancienne application (`app_optimized.py`) reste fonctionnelle. La migration peut être progressive:
|
|
|
|
1. **Phase 1**: Tester la nouvelle structure en parallèle
|
|
2. **Phase 2**: Basculer le point d'entrée principal
|
|
3. **Phase 3**: Supprimer `app_optimized.py` une fois la validation complète
|
|
|
|
### 4. Points d'attention
|
|
|
|
| Élément | Action requise |
|
|
|---------|----------------|
|
|
| Tests existants | Adapter les imports pour utiliser les nouveaux modules |
|
|
| Docker | Modifier le CMD pour utiliser `main.py` au lieu de `app_optimized.py` |
|
|
| Scripts de démarrage | Mettre à jour `run_dev.sh` et `run_dev.ps1` |
|
|
| Variables d'environnement | Aucun changement requis (même configuration) |
|
|
|
|
### 5. Rollback
|
|
|
|
En cas de problème, revenir à l'ancienne structure:
|
|
```bash
|
|
uvicorn app.app_optimized:app --host 0.0.0.0 --port 8008 --reload
|
|
```
|
|
|
|
---
|
|
|
|
## AMÉLIORATIONS FUTURES
|
|
|
|
### Court terme
|
|
- [ ] Ajouter des tests unitaires pour chaque nouveau module
|
|
- [ ] Compléter la validation des schémas Pydantic
|
|
- [ ] Ajouter la gestion des erreurs avec les exceptions personnalisées
|
|
|
|
### Moyen terme
|
|
- [ ] Implémenter l'injection de dépendances complète (pas de singletons)
|
|
- [ ] Migrer les dernières fonctionnalités de `app_optimized.py`
|
|
- [ ] Ajouter des tests d'intégration
|
|
|
|
### Long terme
|
|
- [ ] Supprimer `app_optimized.py` complètement
|
|
- [ ] Implémenter le versioning de l'API (`/api/v1/`, `/api/v2/`)
|
|
- [ ] Ajouter la documentation OpenAPI personnalisée
|
|
|
|
---
|
|
|
|
*Document généré automatiquement - Refactorisation complétée le 2024-12-14*
|