Chaque test reecrivait sa classe de session et sa fonction d'override, soit trois fois le meme decor pour un seul endpoint. FakeSession et la fixture fake_session portent ce decor, make_settings fabrique une Settings dont les valeurs priment sur l'environnement.
Backend EnerVision
API FastAPI exposant les series temporelles energetiques.
| Element | Choix |
|---|---|
| Python | 3.14 |
| Gestionnaire | uv (uv.lock fait foi) |
| Framework | FastAPI + Uvicorn |
| Persistance | SQLAlchemy 2 async + asyncpg + Alembic |
| Lint/format | ruff |
| Typage | mypy en mode strict |
| Tests | pytest + pytest-asyncio + httpx |
Installation
cp .env.example .env
uv sync --all-groups
APP_SECRET_KEY et DATABASE_URL n'ont pas de valeur par defaut : l'application refuse
de demarrer sans elles.
DATABASE_URL pointe sur localhost:5433, le port publie par le service db du
docker-compose.yml racine. Demarrer la base depuis la racine avec make db-up.
Commandes
Depuis la racine du monorepo, via le Makefile : make install, make dev, make lint,
make format, make typecheck, make test, make check, make docker-build.
Directement depuis ce dossier :
uv run uvicorn app.main:create_app --factory --reload --port 8000
uv run ruff check . # lint
uv run ruff format . # format
uv run mypy app # typage strict
uv run pytest # tests + couverture
uv run pytest -m integration # tests exigeant une base joignable
pytest ecarte par defaut les tests marques integration, pour que make check reste
jouable sans Docker. Ces tests visent la base enervision_test, creee par
db/init/110-test-database.sql au premier demarrage du conteneur.
L'application est exposee par une factory (create_app) et non par un objet module :
aucune configuration n'est lue a l'import, ce qui rend les tests et les migrations
independants de l'environnement.
Structure
app/
├── api/
│ ├── deps.py Dependances FastAPI partagees (session, settings)
│ └── v1/
│ ├── router.py Agregation des routes de la version 1
│ └── endpoints/ Un module par ressource exposee
├── core/
│ ├── config.py Settings Pydantic, source unique de configuration
│ └── logging.py Journalisation console en local, JSON en production
├── db/
│ ├── base.py Base declarative SQLAlchemy
│ └── session.py Engine et sessions asynchrones
├── models/ Modeles SQLAlchemy
├── schemas/ Modeles Pydantic d'entree et de sortie
├── repositories/ Acces aux donnees, une classe par agregat
├── services/ Regles metier, orchestrent les repositories
└── main.py Factory applicative
tests/ Miroir de app/
alembic/ Migrations du schema applicatif
Le sens de dependance est unique : endpoints vers services vers repositories vers
models. Un endpoint ne touche jamais une session directement.
Routes
| Route | Role |
|---|---|
/api/v1/health/live |
Sonde de vivacite, aucune dependance externe |
/api/v1/health/ready |
Sonde de disponibilite, verifie la base et TimescaleDB |
/metrics |
Metriques au format Prometheus |
/docs, /openapi.json |
Documentation, desactivee quand APP_ENV=prod |
Migrations
uv run alembic revision --autogenerate -m "libelle"
uv run alembic upgrade head
L'URL de connexion vient de DATABASE_URL, pas de alembic.ini.
La premiere revision ne cree aucune table : elle refuse de s'appliquer si l'extension
TimescaleDB manque, ce qui arrive quand db/init n'a pas ete joue. Le DDL propre a
TimescaleDB qui ne depend pas du schema applicatif vit dans db/, pas ici.
Image Docker
Build multi-stage, dependances resolues par uv depuis uv.lock, execution sous un
utilisateur non root, sonde de sante integree.
docker build -t enervision-backend:local .
docker run --rm -p 8000:8000 --env-file .env enervision-backend:local