# Backend API FastAPI, Python 3.14, SQLAlchemy asynchrone sur `asyncpg`. Source dans `apps/backend`. ## Couches La doctrine est posée dans [`apps/backend/README.md`](../../apps/backend/README.md) et [`TESTING.md`](../../apps/backend/TESTING.md) : `endpoints` appelle `services`, qui appelle `repositories`, qui seuls touchent les `models`. Le sens de dépendance ne s'inverse jamais. Dans les faits, trois de ces couches sont des dossiers vides. ```mermaid flowchart TB ep["endpoints
2 routes"] sc["schemas
2 modèles Pydantic"] sv["services
vide"] rp["repositories
vide"] md["models
vide"] db[("PostgreSQL")] ep --> sc ep -.-> sv sv -.-> rp rp -.-> md ep -->|"SQL brut, état actuel"| db rp -.-> db ``` Le trait plein de `endpoints` vers la base n'est pas une erreur de dessin : `/health/ready` exécute aujourd'hui son `SELECT` directement, sans repository. C'est acceptable pour une sonde d'infrastructure, qui vérifie la base elle-même et non une donnée métier. Ce raccourci ne doit pas servir de modèle au premier endpoint métier. `app/models/__init__.py` ne contient qu'un avertissement, qui mérite d'être connu avant la première migration : tout modèle absent de ce module reste invisible d'un `alembic revision --autogenerate`, qui produirait alors un `drop` de sa table. ## Démarrage Point d'entrée : **une factory**, `uvicorn app.main:create_app --factory`. Aucune configuration n'est lue à l'import du module, ce qui rend l'application testable et les migrations indépendantes de l'environnement d'exécution. ```mermaid sequenceDiagram participant U as uvicorn --factory participant F as create_app participant S as get_settings participant A as FastAPI U->>F: create_app() F->>S: Settings depuis .env et variables APP_* S-->>F: resolved F->>F: configure_logging(resolved) F->>A: FastAPI, docs fermés si prod F->>A: CORSMiddleware, seulement si allowed_origins F->>A: Instrumentator, expose /metrics F->>A: include_router, préfixe /api/v1 A-->>U: application ``` **Le `lifespan` n'ouvre aucune connexion.** Au démarrage il journalise le nom, la version et l'environnement ; à l'arrêt il libère l'engine. L'engine lui-même est construit paresseusement au premier appel de `get_engine()`, mis en cache par `lru_cache`. Conséquence directe : une API qui démarre ne prouve rien sur la base, la première connexion réelle a lieu au premier `GET /api/v1/health/ready`. C'est ce qui rend cette sonde indispensable. ## Configuration `Settings` est un `BaseSettings` Pydantic, lu depuis `.env` avec le préfixe `APP_`. | Variable | Défaut | Rôle | |---|---|---| | `APP_SECRET_KEY` | **aucun** | Secret applicatif, `SecretStr` | | `DATABASE_URL` | **aucun** | Chaîne de connexion, `postgresql+asyncpg://...` | | `APP_ENV` | `local` | `local`, `dev`, `staging` ou `prod` | | `APP_DEBUG` | `false` | Active aussi l'écho SQL de l'engine | | `APP_LOG_LEVEL` | `INFO` | | | `APP_CORS_ORIGINS` | `""` | Liste séparée par des virgules. Vide, aucun middleware CORS n'est posé | | `APP_API_PREFIX` | `/api/v1` | | | `APP_DATABASE_POOL_SIZE` | `5` | | | `APP_DATABASE_MAX_OVERFLOW` | `10` | | Deux pièges : - **`DATABASE_URL` ne prend pas le préfixe `APP_`.** C'est le seul réglage dans ce cas, par `validation_alias`, pour rester compatible avec la convention d'Alembic et des hébergeurs. - **`APP_SECRET_KEY` et `DATABASE_URL` n'ont pas de valeur par défaut.** L'application refuse de démarrer si l'un manque. C'est délibéré : mieux vaut un échec au démarrage qu'un service qui tourne avec un secret de démonstration. Deux fichiers d'environnement, deux usages : `.env` à la racine alimente `docker-compose.yml`, `apps/backend/.env` alimente l'API lancée sur le poste. ## Routes exposées | Méthode | Chemin | Dans l'OpenAPI | Rôle | |---|---|---|---| | GET | `/api/v1/health/live` | oui | Le processus répond. Ne touche pas la base | | GET | `/api/v1/health/ready` | oui | La base répond **et** l'extension TimescaleDB est chargée | | GET | `/metrics` | non | Format Prometheus, exposé par l'instrumentator | | GET | `/docs`, `/redoc`, `/openapi.json` | non | Désactivés quand `APP_ENV=prod` | Aucune route métier n'existe à ce jour. ### `/health/ready` Cette sonde porte une garde décrite dans l'[ADR 0001](../adr/0001-postgresql-timescaledb.md) : un bootstrap de base sauté ne se voit pas au démarrage de l'API, elle le rend visible. ```mermaid sequenceDiagram participant C as Client participant R as readiness participant E as get_engine participant D as PostgreSQL C->>R: GET /api/v1/health/ready R->>E: session, engine créé au premier appel R->>D: SELECT extversion FROM pg_extension WHERE extname = 'timescaledb' alt base injoignable D--xR: SQLAlchemyError ou OSError R-->>C: 503 Base de donnees injoignable else extension absente D-->>R: NULL R-->>C: 503 Extension TimescaleDB absente else D-->>R: version de l'extension R-->>C: 200 status ready end ``` ## Sécurité Voir la vue consolidée dans [00-vue-ensemble.md](00-vue-ensemble.md). Côté backend : - **Aucune authentification, aucune autorisation.** Les deux routes sont publiques. Le premier endpoint métier imposera de trancher ce point. - Le CORS n'autorise que les origines listées, et n'existe pas si la liste est vide. - `/docs`, `/redoc` et `/openapi.json` disparaissent en production. - Le conteneur tourne en utilisateur non-root, avec un `HEALTHCHECK` sur `/api/v1/health/live`. - Ni limitation de débit, ni journalisation des accès, ni en-têtes de sécurité. ## Observabilité - Journalisation par `dictConfig` : format console en développement, JSON dès `APP_ENV=prod`. `sqlalchemy.engine` est forcé à `WARNING` pour ne pas noyer les journaux. - `/metrics` au format Prometheus. **Aucun collecteur ne le lit** : `monitoring/` est vide. ## Tests Conventions, gabarits et arborescence : [`apps/backend/TESTING.md`](../../apps/backend/TESTING.md). Deux points structurants y sont fixés : les doubles passent par `app.dependency_overrides` et jamais par `unittest.mock`, et les tests qui touchent la vraie base portent le marqueur `integration`, exclu par défaut. ## Questions ouvertes - **Authentification et autorisation** : quel mécanisme, quelle granularité. - **Pagination et fenêtrage** des lectures de séries temporelles, qui conditionnent la forme des endpoints métier. - **Politique de versionnement de l'API** au-delà du préfixe `/api/v1`.