From 1a679ea1c7de9fae72017ac8828eb15413b0302f Mon Sep 17 00:00:00 2001 From: Antoine Van Elstraete Date: Thu, 13 Aug 2026 15:21:51 +0200 Subject: [PATCH] refactor: migrer datetime.utcnow() vers datetime.now(UTC) Co-authored-by: OpenAI/GPT-5.6-terra --- AGENTS.md | 3 ++- app/models.py | 8 +++++--- docs/onboarding.md | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ff909d4..4583fa7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,7 +78,8 @@ Flask app using the factory pattern (`create_app()` in `app/__init__.py`). The D - **Pas de migration de schéma** : l'app utilise `db.create_all()` uniquement (pas d'Alembic). Tout changement de modèle nécessite de supprimer `instance/worklog.db` en dev, ou une migration manuelle en prod. - **Barème kilométrique** : les tranches dans `config.toml` sont à mettre à jour manuellement chaque année (section `[bareme_kilometrique.YYYY]`). -- **`datetime.utcnow()` déprécié** : Les modèles utilisent actuellement `datetime.utcnow` (générant un avertissement sur Python 3.12+ et obsolète sur Python 3.14+). Il est formellement recommandé d'utiliser `datetime.now(UTC)` (avec `from datetime import UTC`) à la place pour toute nouvelle manipulation de date/heure ou lors de la prochaine évolution des modèles. +- **Métadonnées et horodatages (`created_at` / `updated_at`)** : L'application utilise `datetime.now(UTC)` pour enregistrer ces métadonnées. Bien que les valeurs soient émises en UTC, SQLite et SQLAlchemy restituent par défaut des objets `datetime` naïfs (sans `tzinfo`). Ces champs sont actuellement informatifs et non exploités par la logique métier. En cas de besoin ultérieur d'exploitation de ces métadonnées avec fuseau, les pistes incluent la conservation explicite du fuseau (`DateTime(timezone=True)`) ou la stricte convention documentée « naïf = UTC ». +- **Calculs de durée métier et transitions DST** : Le calcul de la durée des plages horaires de travail (`TimeSlot` via `total_minutes()`) est totalement indépendant des métadonnées et repose sur des heures murales (locales). Une plage horaire traversant un changement d'heure saisonnier (passage heure d'été/hiver / DST) soulève un enjeu métier spécifique (gestion des durées d'heures locales) qui nécessiterait, le cas échéant, une représentation dédiée ou une politique métier spécifique. - **Filtres Jinja2** (définis dans `app/__init__.py`) : `{{ date | date_fr }}` pour les dates en français ; `{{ day_type | day_type_fr }}` pour les libellés de types de jours (WORK→Travail, TT→Télétravail, etc.). - **`db.get_engine()` deprecated** en Flask-SQLAlchemy 3.x → utiliser `db.engine`. - **Migration `_migrate_db`** : vérifier l'existence de la table avant `ALTER TABLE` — SQLite peut avoir un fichier DB sans tables (ex: premier démarrage avec `instance/worklog.db` vide). diff --git a/app/models.py b/app/models.py index 8554627..0f3238a 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,4 @@ -from datetime import date, datetime, time +from datetime import UTC, date, datetime, time import sqlalchemy as sa import sqlalchemy.orm as so @@ -27,9 +27,11 @@ class WorkEntry(db.Model): motor_vehicle_id: so.Mapped[str | None] = so.mapped_column(sa.String(64), nullable=True) day_type: so.Mapped[str] = so.mapped_column(sa.String(16), nullable=False, default="WORK") comment: so.Mapped[str | None] = so.mapped_column(sa.Text, nullable=True) - created_at: so.Mapped[datetime] = so.mapped_column(sa.DateTime, default=datetime.utcnow) + created_at: so.Mapped[datetime] = so.mapped_column( + sa.DateTime, default=lambda: datetime.now(UTC) + ) updated_at: so.Mapped[datetime] = so.mapped_column( - sa.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow + sa.DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC) ) time_slots: so.Mapped[list["TimeSlot"]] = so.relationship( diff --git a/docs/onboarding.md b/docs/onboarding.md index 4fcda35..55af268 100644 --- a/docs/onboarding.md +++ b/docs/onboarding.md @@ -262,8 +262,9 @@ sudo systemctl restart tableau-de-bord-pro - En **développement** : Tu peux simplement supprimer le fichier `instance/worklog.db` pour qu'il soit recréé au prochain démarrage (attention, cela supprime tes données de test). - En **production** : Tu dois écrire une migration manuelle dans la fonction `_migrate_db` située dans `app/__init__.py`. Cette fonction s'exécute au démarrage de l'application, vérifie l'existence des colonnes via SQLite, et applique les instructions `ALTER TABLE` nécessaires de manière sécurisée. -### Dépréciations à surveiller -- **`datetime.utcnow()`** : Cette méthode est dépréciée depuis Python 3.12. Il est formellement recommandé d'utiliser `datetime.now(UTC)` (avec `from datetime import UTC`) à la place. Lors d'une prochaine évolution majeure des modèles, il faudra remplacer les occurrences existantes. +### Horodatages de métadonnées et calculs de durée métier +- **Métadonnées (`created_at`, `updated_at`)** : L'application utilise `datetime.now(UTC)` pour enregistrer ces métadonnées. Bien que les valeurs soient émises en UTC, SQLite et SQLAlchemy stockent et restituent par défaut des objets `datetime` naïfs (sans `tzinfo`). Ces champs sont actuellement informatifs et non exploités par la logique métier. Si un besoin de lecture ou de manipulation de ces métadonnées avec fuseau émergeait, les pistes incluent l'utilisation de types `DateTime(timezone=True)` ou la stricte convention documentée « naïf = UTC ». +- **Calculs de durée métier et transitions DST** : Le calcul de la durée des plages horaires de travail (`TimeSlot` via `total_minutes()`) est totalement indépendant des métadonnées et repose sur des heures murales (locales). Une plage horaire traversant un changement d'heure saisonnier (passage heure d'été/hiver / DST) soulève un enjeu métier spécifique (gestion des durées d'heures locales) qui nécessiterait, le cas échéant, une représentation dédiée ou une politique métier spécifique. - **`db.get_engine()`** : Déprécié dans Flask-SQLAlchemy 3.x. Utilise toujours `db.engine` à la place. ---