refactor(config): migrer les endpoints Pronote
Co-authored-by: Codex <codex@antoineve.me>
This commit is contained in:
+29
-22
@@ -259,8 +259,8 @@ Le projet utilise **`pydantic-settings`** pour valider et charger la configurati
|
||||
|
||||
| Variable | Description | Exemple (anonymisé) | Type |
|
||||
|------------------------------|-----------------------------------------------------------------------------|---------------------------------------------|---------------|
|
||||
| `PRONOTE_URL` | URL de la page Pronote utilisée par `pronotepy` (page parent). | `https://college.ent/pronote/parent.html` | `str` |
|
||||
| `PRONOTE_ICAL_URL` | URL du flux iCal Pronote (contient `icalsecurise`). | `https://college.ent/pronote/ical/...` | `SecretStr` |
|
||||
| `PRONOTE_ENDPOINT__URL` | URL de la page Pronote via l'endpoint commun. | `https://college.ent/pronote/parent.html` | `ExternalEndpoint` |
|
||||
| `PRONOTE_ICAL_ENDPOINT__URL` | URL du flux iCal Pronote via l'endpoint commun (contient `icalsecurise`). | `https://college.ent/pronote/ical/...` | `ExternalEndpoint` |
|
||||
| `PRONOTE_USERNAME` | Identifiant Pronote (si `pronotepy` utilisé). | `parent.dupont` | `str` |
|
||||
| `PRONOTE_PASSWORD` | Mot de passe Pronote (si `pronotepy` utilisé). | `SecretStr` (masqué) | `SecretStr` |
|
||||
| `PRONOTE_ENT` | Slug ENT supporté, résolu vers une fonction de `pronotepy.ent`. | `monbureaunumerique` | `str` |
|
||||
@@ -277,17 +277,22 @@ Le projet utilise **`pydantic-settings`** pour valider et charger la configurati
|
||||
> `XMPP_RECIPIENT` a été renommé en `XMPP_TO` dans l'implémentation (aligné avec §10.2.1).
|
||||
> Des variables XMPP supplémentaires ont été ajoutées : `XMPP_ENABLED`, `XMPP_HOST`, `XMPP_PORT`, `XMPP_RESOURCE`, `XMPP_TLS_MODE`, `XMPP_TIMEOUT`, `XMPP_CONNECT_TIMEOUT`, `XMPP_CLEANUP_TIMEOUT`.
|
||||
> `XMPP_USE_TLS` reste supporté mais est **obsolète** (remplacé par `XMPP_TLS_MODE`).
|
||||
> Une section `BLOG_ENABLED` et `BLOG_RSS_URL` a été ajoutée dans `.env.example`.
|
||||
> Une section `BLOG_ENABLED` et `BLOG_ENDPOINT__URL` a été ajoutée dans `.env.example`.
|
||||
> `BLOG_RSS_URL` reste temporairement accepté avec un avertissement de dépréciation.
|
||||
> `CALDAV_URL` reste temporairement accepté avec un avertissement de dépréciation ; utiliser
|
||||
> `CALDAV_ENDPOINT__URL` pour la nouvelle configuration.
|
||||
> `PRONOTE_URL` et `PRONOTE_ICAL_URL` restent temporairement acceptés avec un avertissement de
|
||||
> dépréciation ; utiliser respectivement `PRONOTE_ENDPOINT__URL` et `PRONOTE_ICAL_ENDPOINT__URL`.
|
||||
|
||||
Les variables Pronote sont obligatoires selon les sources activées :
|
||||
|
||||
- la source iCal exige `PRONOTE_ICAL_URL` ;
|
||||
- la source `pronotepy` exige `PRONOTE_URL`, `PRONOTE_USERNAME` et
|
||||
- la source iCal exige `PRONOTE_ICAL_ENDPOINT__URL` ;
|
||||
- la source `pronotepy` exige `PRONOTE_ENDPOINT__URL`, `PRONOTE_USERNAME` et
|
||||
`PRONOTE_PASSWORD` ;
|
||||
- `PRONOTE_ENT` reste optionnel pour une connexion directe, mais, s'il est fourni, son slug doit
|
||||
appartenir à une liste fermée et être résolu vers la fonction correspondante de `pronotepy.ent`.
|
||||
|
||||
`PRONOTE_URL` et `PRONOTE_ICAL_URL` sont deux contrats distincts : l'un ne doit jamais être déduit
|
||||
`PRONOTE_ENDPOINT__URL` et `PRONOTE_ICAL_ENDPOINT__URL` sont deux contrats distincts : l'un ne doit jamais être déduit
|
||||
de l'autre. Le cas d'usage actuel est un compte parent ; le client à construire est donc
|
||||
`pronotepy.ParentClient`. Une généralisation à plusieurs profils ne sera ajoutée qu'en présence
|
||||
d'un besoin réel et testé.
|
||||
@@ -323,8 +328,8 @@ d'un besoin réel et testé.
|
||||
|
||||
```ini
|
||||
# --- Pronote ---
|
||||
PRONOTE_URL=https://college.ent/pronote/parent.html
|
||||
PRONOTE_ICAL_URL=https://college.ent/pronote/ical/Edt_Jean.ics?icalsecurise=REPLACE_ME&version=2024
|
||||
PRONOTE_ENDPOINT__URL=https://college.ent/pronote/parent.html
|
||||
PRONOTE_ICAL_ENDPOINT__URL=https://college.ent/pronote/ical/Edt_Jean.ics?icalsecurise=REPLACE_ME&version=2024
|
||||
PRONOTE_USERNAME=parent.dupont
|
||||
PRONOTE_PASSWORD=your_secure_password
|
||||
PRONOTE_ENT=monbureaunumerique
|
||||
@@ -335,7 +340,7 @@ PRONOTE_HOMEWORK_SOURCE=auto
|
||||
PRONOTE_MESSAGES_SOURCE=pronotepy
|
||||
|
||||
# --- CalDAV ---
|
||||
CALDAV_URL=https://caldav.example.com/calendars/user/pronote/
|
||||
CALDAV_ENDPOINT__URL=https://caldav.example.com/calendars/user/pronote/
|
||||
CALDAV_ALLOW_INSECURE_HTTP=false
|
||||
CALDAV_USERNAME=user@example.com
|
||||
CALDAV_PASSWORD=your_caldav_password
|
||||
@@ -389,7 +394,7 @@ LOG_LEVEL=INFO
|
||||
> L'implémentation utilise le style moderne de Pydantic v2 : `model_config = ConfigDict(frozen=True)` au lieu de `class Config`, pas de `json_encoders` (la sérialisation ISO est native en v2), `str | None` au lieu de `Optional[str]`, `list[str]` au lieu de `List[str]`.
|
||||
> `AISettings.enabled` a pour valeur par défaut `False`.
|
||||
> `XmppSettings` est entièrement défini en §10.2.3 avec tous les champs optionnels (valeurs par défaut) pour que `Settings()` fonctionne sans `.env`.
|
||||
> `BlogSettings` a été ajouté (§5 bis.9.2) avec `enabled=False` et `rss_url` par défaut.
|
||||
> `BlogSettings` a été ajouté (§5 bis.9.2) avec `enabled=False` et un endpoint RSS par défaut.
|
||||
> `sync_past_days` et `sync_future_days` sont dans `AppSettings`, et non `CalDAVSettings`.
|
||||
> `CalDAVSettings.calendar_path` a pour valeur par défaut `"/pronote-sync/"`.
|
||||
> `XmppSettings.resource` a pour valeur par défaut `"pronote-sync"`.
|
||||
@@ -403,9 +408,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class PronoteSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="PRONOTE_", env_file=".env", extra="ignore")
|
||||
url: str | None = None
|
||||
ical_url: SecretStr | None = None
|
||||
model_config = SettingsConfigDict(env_prefix="PRONOTE_", env_file=".env", env_nested_delimiter="__", extra="ignore")
|
||||
endpoint: ExternalEndpoint | None = None
|
||||
ical_endpoint: ExternalEndpoint | None = None
|
||||
username: str | None = None
|
||||
password: SecretStr | None = None
|
||||
ent: str | None = None
|
||||
@@ -2702,7 +2707,7 @@ Utilisation de la bibliothèque [`caldav`](https://pypi.org/project/caldav/) (Py
|
||||
#### API réelle (`caldav>=1.3.0`)
|
||||
|
||||
- **Connexion** : `caldav.DAVClient(url, username, password)` — les paramètres proviennent
|
||||
de `CalDAVSettings` (`CALDAV_URL`, `CALDAV_USERNAME`, `CALDAV_PASSWORD`).
|
||||
de `CalDAVSettings` (`CALDAV_ENDPOINT__URL`, `CALDAV_USERNAME`, `CALDAV_PASSWORD`).
|
||||
- **Résolution du calendrier** : `DAVClient.principal()` puis `principal.calendars()` ;
|
||||
sélectionner le calendrier dont l'URL correspond à **`CalDAVSettings.calendar_path`**
|
||||
(ex: `/pronote-sync/`). La résolution ne se fait **pas** par nom de calendrier :
|
||||
@@ -4607,7 +4612,7 @@ def get_channel(settings: XmppSettings, dry_run: bool = False) -> Channel | None
|
||||
- **XMPP** : Si l'envoi échoue → **logger l'erreur** mais continuer le pipeline.
|
||||
- **Erreurs critiques** :
|
||||
- **Aucune source disponible** (iCal + pronotepy échouent) → **échec explicite** avec message clair.
|
||||
- **Configuration invalide** (ex: `PRONOTE_ICAL_URL` manquant) → **échec explicite**.
|
||||
- **Configuration invalide** (ex: `PRONOTE_ICAL_ENDPOINT__URL` manquant) → **échec explicite**.
|
||||
|
||||
### 11.2 Hiérarchie des erreurs
|
||||
|
||||
@@ -5338,12 +5343,14 @@ def mock_xmpp_channel():
|
||||
def sample_settings():
|
||||
"""Retourne une configuration de test."""
|
||||
from pydantic import SecretStr
|
||||
from pronote_sync.config.settings import Settings, PronoteSettings, CalDAVSettings, XmppSettings, AISettings, AppSettings
|
||||
from pronote_sync.config.settings import (
|
||||
AISettings, AppSettings, CalDAVSettings, ExternalEndpoint, PronoteSettings, Settings, XmppSettings,
|
||||
)
|
||||
|
||||
return Settings(
|
||||
pronote=PronoteSettings(
|
||||
url="https://test.ent/pronote/parent.html",
|
||||
ical_url=SecretStr("https://test.ent/pronote/ical/test.ics"),
|
||||
endpoint=ExternalEndpoint(url=SecretStr("https://test.ent/pronote/parent.html")),
|
||||
ical_endpoint=ExternalEndpoint(url=SecretStr("https://test.ent/pronote/ical/test.ics")),
|
||||
username="test_user",
|
||||
password=SecretStr("test_password"),
|
||||
ent="monbureaunumerique",
|
||||
@@ -5431,7 +5438,7 @@ def test_pipeline_full(mock_requests_get, mock_caldav_client, mock_ai_provider,
|
||||
|
||||
# Configurer le client CalDAV
|
||||
caldav_client = CalDAVClient(
|
||||
url=sample_settings.caldav.url,
|
||||
url=sample_settings.caldav.endpoint.url if sample_settings.caldav.endpoint else None,
|
||||
username=sample_settings.caldav.username,
|
||||
password=sample_settings.caldav.password,
|
||||
allow_insecure_http=sample_settings.caldav.allow_insecure_http,
|
||||
@@ -5626,7 +5633,7 @@ TOTAL 1000 10 99%
|
||||
| **Risque** | **Mesure de mitigation** | **Vérification** | **Statut** |
|
||||
|-------------------------------------|----------------------------------------------------------------------------------------|-------------------------------------------|------------|
|
||||
| Tokens dans le code | Utiliser `pydantic-settings` + `SecretStr` pour les variables d'environnement. | `grep -r "icalsecurise\|password\|api_key" src/` | ❌ Interdit |
|
||||
| Tokens dans les logs | Masquage systématique via `RedactingFormatter` (voir [Section 4.2](#42-implémentation)). | Tests avec `PRONOTE_ICAL_URL` contenant un token. | ✅ Obligatoire |
|
||||
| Tokens dans les logs | Masquage systématique via `RedactingFormatter` (voir [Section 4.2](#42-implémentation)). | Tests avec `PRONOTE_ICAL_ENDPOINT__URL` contenant un token. | ✅ Obligatoire |
|
||||
| Tokens dans les erreurs | Masquage dans les messages d'erreur (voir `redact_url` et `redact_secrets`). | Tests avec URLs contenant des tokens. | ✅ Obligatoire |
|
||||
| Tokens dans les fixtures | **Anonymiser** toutes les fixtures (pas de tokens réels). | Vérification manuelle des fixtures. | ✅ Obligatoire |
|
||||
| Tokens dans les commits Git | Utiliser `.gitignore` pour `.env` et `pre-commit` pour bloquer les secrets. | `git grep "icalsecurise\|password" -- .` (contenu suivi courant) | ❌ Interdit |
|
||||
@@ -5945,10 +5952,10 @@ Exemple de ligne cron (exécution tous les jours à 18h) :
|
||||
|---------------------------------------|------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
|
||||
| Échec de la récupération iCal | Token `icalsecurise` expiré ou invalide. | Régénérer le token depuis Pronote. |
|
||||
| Échec de la connexion Pronote (`pronotepy`) | Identifiants incorrects ou ENT non supporté. | Vérifier `PRONOTE_USERNAME`, `PRONOTE_PASSWORD`, `PRONOTE_ENT`. |
|
||||
| Échec de la connexion CalDAV | URL, identifiant ou mot de passe CalDAV incorrect, ou HTTP non autorisé pour l'hôte. | Vérifier `CALDAV_URL`, `CALDAV_USERNAME`, `CALDAV_PASSWORD`, `CALDAV_ALLOW_INSECURE_HTTP`. |
|
||||
| Échec de la connexion CalDAV | URL, identifiant ou mot de passe CalDAV incorrect, ou HTTP non autorisé pour l'hôte. | Vérifier `CALDAV_ENDPOINT__URL`, `CALDAV_USERNAME`, `CALDAV_PASSWORD`, `CALDAV_ALLOW_INSECURE_HTTP`. |
|
||||
| Échec de la connexion XMPP | Identifiant ou mot de passe XMPP incorrect. | Vérifier `XMPP_JID`, `XMPP_PASSWORD`. |
|
||||
| Échec de la synthèse IA | Clé API IA invalide ou modèle non disponible. | Vérifier `AI_API_KEY`, `AI_BASE_URL`, `AI_MODEL`. |
|
||||
| Aucun cours récupéré | Flux iCal vide ou `pronotepy` non configuré. | Vérifier `PRONOTE_ICAL_URL` ou les identifiants `pronotepy`. |
|
||||
| Aucun cours récupéré | Flux iCal vide ou `pronotepy` non configuré. | Vérifier `PRONOTE_ICAL_ENDPOINT__URL` ou les identifiants `pronotepy`. |
|
||||
| Doublons dans les devoirs | Problème de déduplication. | Vérifier la logique de déduplication (voir [Section 5.1.4](#514-déduplication-des-devoirs)). |
|
||||
| Synchronisation CalDAV lente | Trop d'événements à synchroniser. | Réduire `SYNC_PAST_DAYS` ou `SYNC_FUTURE_DAYS`. |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user