Cause racine : pronotepy peut rafraîchir (rotater) le token en mémoire pendant l'exécution via refresh() automatique après une PronoteAPIError. L'ancien code ne persistait les credentials qu'après le login initial, pas après les opérations de données. Le token roté en mémoire était perdu → au run suivant, token_login échouait avec le token périmé (KeyError 'dataSec'). Correction : - PronoteClient._persist_credentials() : méthode centralisée qui persiste export_credentials() après chaque opération réussie (get_lessons, get_homeworks, get_messages, get_informations) - Le token rafraîchi par le serveur pendant l'exécution est maintenant toujours persisté, même si le pipeline échoue ensuite Documentation : - .env.example : variables QR plus visibles (exemple qr_token décommentable) - AGENTS.md : QR code depuis le site web Pronote (pas l'app mobile), persistance après chaque opération de données - Wiki GuidePronote : procédure corrigée (site web, pas app Android/iOS), mention de la persistance après chaque opération Tests : 5 nouveaux tests de persistance (691 passés, couverture 94.92%)
11 KiB
11 KiB
Guide pour les agents et contributeurs — pronote-sync
Langue de travail : Français (ce fichier et toute la documentation projet).
1. Description du projet
pronote-sync est un pipeline Python qui synchronise les données de Pronote (système de gestion scolaire) vers CalDAV (agendas) et XMPP (notifications).
Spécification complète : Voir
GUIDE_DEV_PYTHON.md.
2. Stack technique
Langage et dépendances
- Python : ≥ 3.13.5 (actuellement 3.14 dans
.venv/) - Dépendances principales :
pydantic>=2.0,pydantic-settings,icalendar,caldav,slixmpp,pronotepy,feedparser,beautifulsoup4,requests,httpx,openai
Outils de développement
- Linter :
ruff(longueur de ligne : 100) - Typage :
mypy(modestrict=true) - Tests :
pytest(couverture ≥ 90%),pytest-mock,responses,aioresponses - Sécurité :
bandit - Pre-commit : hooks configurés pour
ruff,mypy,bandit
Environnement
- Virtualenv :
.venv/(à activer avant toute opération) - Fichier de configuration :
.env(à partir de.env.example)
3. Structure du projet
pronote_sync/
├── config/ # Configuration et paramètres
├── errors.py # Hiérarchie canonique des erreurs du pipeline
├── models/ # Modèles de données (Pydantic v2)
├── sources/ # Connecteurs (Pronote, iCal, etc.)
├── sync/ # Logique de synchronisation
├── synthesis/ # Génération de synthèses (IA)
├── channels/ # Canaux de sortie (CalDAV, XMPP)
├── pipeline/ # Orchestration du pipeline
├── utils/ # Utilitaires (redaction, logging, etc.)
├── cli/ # Interface en ligne de commande
└── __init__.py
tests/
├── fixtures/ # Données de test anonymisées
├── unit/ # Tests unitaires
└── integration/ # Tests d'intégration
Plan de développement : Voir
TODO.mdpour les 15 jalons (M1-M15).
4. Commandes essentielles
Environnement
# Activer le virtualenv
source .venv/bin/activate
# Installer les dépendances de développement
pip install -e ".[dev]"
Qualité de code
# Linter (ruff)
ruff check .
# Formattage (ruff)
ruff format .
# Vérification des types (mypy)
mypy .
# Analyse de sécurité (bandit)
bandit -r pronote_sync/
# Pre-commit (tous les hooks)
pre-commit run --all-files
Tests
# Exécuter les tests
pytest
# Exécuter les tests avec couverture
pytest --cov
CLI
# Exécuter en mode dry-run (simulation)
pronote-sync --dry-run
5. Conventions de code
Typage
- Strict :
mypyen modestrict=true. Tous les paramètres, retours et variables locales doivent être typés. - Pydantic v2 : Tous les modèles de données héritent de
BaseModel. Les modèles de contrat utilisentfrozen=True.
Architecture
- Injection de dépendances : Utiliser
typing.Protocolet une composition root danspipeline/run.py. Interdiction des singletons globaux. - Erreurs : Conserver une seule hiérarchie dans
pronote_sync/errors.py; ne pas créer de doublon danspipeline/steps/errors.py.
Style
- Longueur de ligne : 100 caractères maximum (configuré dans
ruff). - Imports : Triés automatiquement par
ruff(intègreisort).
Tests
- Sans réseau : Utiliser uniquement des mocks (
pytest-mock,responses,aioresponses). - Fixtures : Les données de test doivent être anonymisées et stockées dans
tests/fixtures/.
Comportement
- Idempotence : Deux exécutions identiques sans changement externe doivent produire le même résultat.
- Mode dégradé :
- Si l'IA échoue → retourner un message sans synthèse.
- En mode source
auto, essayer iCal puis utiliserpronotepyuniquement si iCal lève une exception. - En mode source explicite (
icaloupronotepy), ne pas changer silencieusement de source. - En mode
auto, si iCal etpronotepyéchouent → lever une erreur critique explicite. - Une liste vide est un succès valide ; elle ne doit pas être assimilée à une panne.
Contrat des sources Pronote
PRONOTE_URL(connexion API) etPRONOTE_ICAL_URL(flux iCal sensible) sont deux paramètres distincts ; ne pas déduire l'un de l'autre.- Le compte actuellement visé est un compte parent : utiliser
pronotepy.ParentClient(pronote_url, username, password, ent=ent_function). - Résoudre le slug
PRONOTE_ENTvers une fonction depronotepy.entau moyen d'une liste fermée. - Exposer séparément les cours et les devoirs dans le client ; filtrer les devoirs
pronotepysur la date cible. - Les récupérations critiques agenda/devoirs propagent une erreur expurgée ; seuls les messages et informations non critiques peuvent se dégrader en liste vide avec warning.
- Réutiliser un téléchargement/parsing iCal pour l'agenda et les devoirs pendant un même run, sans cache global ni persistant.
Contrat d'authentification QR code / token
- Le mode d'authentification est sélectionné par
PRONOTE_AUTH_MODE:password(défaut) : authentification classique via URL, identifiant, mot de passe et ENT.qr_token: authentification par QR code puis token persistant (pour les instances Pronote utilisant HubEduConnect/EduConnect où l'authentification par mot de passe échoue).
- En mode
qr_token, le premier login utilisepronotepy.qrcode_login(qr_code, pin, uuid)avec les paramètresPRONOTE_QR_CODE_FILE(chemin du JSON QR) etPRONOTE_QR_PIN(PIN SecretStr). Le QR code est obtenu depuis le site web Pronote (espace parent → paramètres → QR code), pas depuis l'application mobile. - Après chaque login réussi, les credentials exportées par
pronotepy.export_credentials()sont persistées dans.pronote_auth_state.json(permissions0600, format JSON versionné, écriture atomique). Le token rotate à chaque session et peut également être rafraîchi pendant l'exécution (refresh automatique pronotepy après unePronoteAPIError). Les credentials sont persistées après chaque login réussi et après chaque opération de données réussie (agenda, devoirs, messages, informations) pour garantir la persistance du token valide. - Les logins suivants utilisent
pronotepy.token_login(**credentials)avec le token persisté. - En cas d'échec de
token_login(token expiré/invalide), unePronoteAuthRotationErrorest levée. Cette erreur se propage sans wrapping à traversPronoteFetcheretfetch_stepjusqu'àPipelineRunner.run(), qui :- journalise l'erreur (expurgée) ;
- envoie une notification XMPP actionnable si le canal est disponible et
dry_runest inactif ; - retourne un résultat dégradé
(None, errors).
PronoteAuthRotationErrorest re-levée telle quelle (except PronoteAuthRotationError: raise) dans toutes les couches d'enveloppement du chemin critique (fetch_agenda, fetch_homework, fetch_step). Ne pas l'attraper avecexcept Exceptionsans la re-léver d'abord.- Le fichier
.pronote_auth_state.jsonne doit jamais être committé (couvert par.gitignore). Son contenu (token vivant) ne doit jamais apparaître dans les logs, les messages d'erreur ou les notifications XMPP.
Contrat du provider openai-compatible
- Le provider
openai-compatibleréutiliseOpenAISynthesisProvideravec unbase_urlpersonnalisé ; aucun nouveau provider n'est créé. AI_BASE_URLetAI_MODELsont requis ;AI_API_KEYest requis (MVP).- L'URL doit utiliser
httpssauf siAI_ALLOW_INSECURE_HTTP=true. - Les credentials dans l'URL (
user:pass@host) sont refusés. - Les paramètres sensibles dans la query string sont refusés, y compris ceux sans valeur (
?token). - Les URL malformées ou sans hostname sont rejetées (
ValueErrorcatché). - Aucune manipulation automatique de
/v1n'est effectuée. - Configuration incomplète ou invalide →
Noneavec avertissement (mode dégradé) ; la factory ne lève jamais d'exception. - La factory ne fait aucun appel réseau ; les avertissements utilisent
redact_url().
Documentation (docstrings)
- Obligatoire : Toute fonction, méthode et classe publique doit avoir une docstring.
- Format : Utiliser le format Sphinx/reST (pas Google ou NumPy) pour une compatibilité native avec Sphinx.
- Priorité : Les blocs historiques de
GUIDE_DEV_PYTHON.mdutilisantArgs:/Returns:sont illustratifs ; le format Sphinx/reST défini ici prévaut pour le code de production. - Contenu :
- Une ligne de résumé courte (une phrase).
- Une description étendue optionnelle.
- Les paramètres avec
:param nom:. - Le retour avec
:return:et:rtype:. - Les exceptions avec
:raises TypeException:.
- Modules : Chaque module doit avoir une docstring au niveau module.
- Objectif : Générer une documentation PDF via LaTeX avec Sphinx.
Exemple :
def fetch_ical(url: str) -> str:
"""Récupère le contenu d'un flux iCal Pronote.
:param url: URL du flux iCal (avec token ``icalsecurise``).
:return: Contenu brut du flux iCal.
:rtype: str
:raises requests.RequestException: Si la requête HTTP échoue.
"""
6. Sécurité et secrets
Règles absolues
- Aucun secret en clair : Ni dans le code, ni dans les logs, ni dans les erreurs, ni dans les fixtures.
- Masquage : Utiliser systématiquement
redact_url(),redact_secrets(), etredact_exception()depuisutils/redaction.py. - Chaînage d'exceptions : Ne jamais conserver comme
__cause__ou__context__une exception externe brute susceptible de contenir un secret. Journaliser la version expurgée puis utiliserraise ... from None, ou chaîner une cause elle-même expurgée. - Tests de non-fuite : Vérifier les messages, les logs,
__cause__,__context__et le traceback complet avec des sentinelles distinctes pour chaque secret.
Bonnes pratiques
- Types sécurisés : Les mots de passe et clés API doivent utiliser
pydantic.SecretStr. - Fichier
.env: Jamais committé (couvert par.gitignore). Utiliser.env.examplecomme template.
7. Plan de développement
Le projet suit un plan séquentiel en 15 jalons (M1-M15) décrits dans TODO.md.
- Chaque jalon a des critères d'acceptation à valider avant de passer au suivant.
- Ordre logique : scaffolding → config → modèles → sources → sync → synthèse → canaux → pipeline → CLI → tests → déploiement → docs
8. Référence
| Document | Rôle |
|---|---|
GUIDE_DEV_PYTHON.md |
Spécification complète (architecture, modèles, parsing, déploiement) |
TODO.md |
Plan de développement détaillé (15 jalons) |