Ajoute le mode d'authentification PRONOTE_AUTH_MODE=qr_token comme alternative au mode password pour les instances Pronote utilisant HubEduConnect/EduConnect où l'authentification par mot de passe échoue (CAPTCHA, MFA, flux SAML). Nouveaux éléments : - PronoteSettings : auth_mode, qr_code_file, qr_pin (SecretStr) - PronoteAuthState : persistance du token rotatif dans .pronote_auth_state.json (écriture atomique, permissions 0600, symlink-safe via O_EXCL|O_NOFOLLOW) - PronoteClient._connect_qr_token() : token_login avec creds persistés, qrcode_login pour l'enrôlement initial, export_credentials persisté après chaque login réussi - PronoteAuthRotationError : levée en cas d'échec de rotation du token, propagée sans wrapping à travers PronoteFetcher et fetch_step jusqu'à PipelineRunner.run() qui notifie via XMPP (si canal disponible et dry_run inactif) - _is_pronotepy_configured() mode-aware : qr_token ne requiert que PRONOTE_URL - _collect_auth_secrets() : redaction des secrets explicites (token, PIN, jeton QR) dans tous les logs du chemin d'authentification Documentation : - .env.example : PRONOTE_AUTH_MODE, PRONOTE_QR_CODE_FILE, PRONOTE_QR_PIN - AGENTS.md : contrat d'authentification QR code / token - Wiki GuidePronote : section enrôlement, exécutions suivantes, ré-enrôlement Tests (686 passés, couverture 94.87%) : - 5 tests config QR, 9 tests auth_state, 10 tests client QR, 3 tests propagation, 4 tests intégration rotation end-to-end, 4 tests fallback mode-aware - Tests de non-fuite : sentinelles distinctes pour token, PIN, jeton QR Co-authored-by: coder/litellm/coder <coder@agents.invalid>
18 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). - 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 — le fichier doit être mis à jour après chaque run. - 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) |
9. Agents OpenCode
Cette section s'applique uniquement lorsque le travail est exécuté avec le système d'agents d'OpenCode.
Les rôles d'agents disponibles pour ce projet sont les suivants :
@architect: Arbitrages d'architecture et choix techniques structurants. Ne produit pas de code.@coder: Écrit et modifie du code, de la configuration et des scripts. Ne valide pas (ruff, mypy, pytest) — c'est le rôle de@verifier. Ne diagnostique pas — c'est le rôle de@debugger.@debugger: Reproduit un symptôme et établit sa cause profonde. Ne modifie pas le code.@explorer: Explore le dépôt en lecture seule. Ne modifie rien, n'exécute pas de commandes.@orchestrator: Compréhension globale, définition des jalons, coordination et garantie du résultat. N'écrit pas de code.@planner: Transforme une demande complexe en unités exécutables. Ne dirige aucun technicien.@reviewer: Revues indépendantes de correction, régression, contrats et maintenabilité. Ne modifie pas le code.@security-auditor: Audit indépendant d'une surface de sécurité. Ne modifie pas le code.@tech-writer: Rédige et maintient la documentation. N'écrit pas de code applicatif.@test-engineer: Conçoit, écrit et exécute des tests ciblés. N'écrit pas de code de production.@ui-designer: Conçoit et implémente les interfaces Web et terminal.@verifier: Vérifie indépendamment le comportement livré, les régressions et le respect des conventions (ruff, mypy, pytest, bandit, idempotence, mode dégradé). Ne modifie pas le code.@web-explorer: Recherche et extrait des sources Web vérifiables. Ne modifie pas le dépôt.
Note
: Ne pas utiliser
@coderpour les tâches de documentation (@tech-writer) ni pour les tests (@test-engineer).
Séparation des rôles
| Type de tâche | Agent responsable | Ne pas confier à |
|---|---|---|
| Écrire/modifier du code | @coder |
@verifier, @explorer |
| Valider (ruff, mypy, pytest, bandit) | @verifier |
@coder |
| Diagnostiquer un bug | @debugger |
@coder |
| Écrire un test | @test-engineer |
@coder |
| Rédiger de la documentation | @tech-writer |
@coder |
| Explorer le dépôt (lecture) | @explorer |
@coder, @verifier |
| Arbitrage technique structurant | @architect |
@coder, @planner |
| Revue de code | @reviewer |
@coder, @verifier |
| Audit de sécurité | @security-auditor |
@coder, @verifier |
| Recherche web | @web-explorer |
@explorer |
| Découpage de travail complexe | @planner |
@coder |
10. Workflow de modification
- Lire la demande,
TODO.md, les sections pertinentes deGUIDE_DEV_PYTHON.md,git statuset les fichiers concernés. - Préserver les changements existants de l'utilisateur.
- Pour une correction, reproduire d'abord le défaut avec un test automatisé lorsque c'est raisonnable.
- Faire une modification étroite et cohérente, en respectant les conventions du projet
(idempotence, modes explicites stricts et repli iCal →
pronotepyuniquement en modeauto). - Faire vérifier le comportement par
@verifier(ruff, mypy, pytest, bandit) et les cas d'erreur, notamment :- Succès de la synchronisation Pronote → CalDAV/XMPP.
- Repli d'iCal vers
pronotepyen modeauto, sans repli dans les modes explicites. - Distinction entre résultat vide et échec des sources.
- Gestion des erreurs explicites sans fuite dans les causes ou tracebacks.
- Mettre à jour la documentation et les exemples dans le même changement si leur comportement public évolue.
- Cocher dans
TODO.mduniquement les éléments entièrement réalisés et validés. - Terminer avec un handoff concis : fichiers modifiés, validations exécutées, limites et prochaine étape.
Pour les changements larges ou risqués : Produire d'abord un audit ou un aperçu. Règle de commit : Ne pas committer sans autorisation explicite. Une autorisation de commit ne vaut pas autorisation de push.
11. Branches et commits
- Une évolution cohérente se fait sur une branche dédiée.
- Nommer les branches selon le format
<type>/<sujet-en-kebab-case>, où<type>est l'un des suivants :feature,fix,docs,choreourefactor. - Partir de l'état validé de la branche principale (
mainoudev), sauf demande explicite. - Garder un commit atomique par objectif vérifiable. Utiliser les préfixes conventionnels pour les messages de commit :
feat:pour une nouvelle fonctionnalité.fix:pour une correction de bug.docs:pour une mise à jour de documentation.chore:pour une tâche de maintenance.refactor:pour une refactorisation de code.
- Quand un agent a contribué au changement, ajouter un trailer Git standard au commit :
Co-authored-by: <harness>/<modèle> <adresse@agents.invalid> - Avant un commit autorisé, vérifier :
git status(fichiers modifiés attendus).- Le diff complet (
git diff). - L'absence de secret ou de configuration locale dans le diff.
- Les validations pertinentes (
ruff,mypy,pytest,bandit). git diff --check(pas de problèmes d'espaces blancs).
- Après le commit, rapporter :
- Le hash du commit.
- Le contenu du commit.
- Les validations exécutées.
Règle absolue : Ne jamais pousser (
git push) sans demande distincte et explicite.
12. Définition de terminé
Un changement est considéré comme terminé lorsque :
- Le cas nominal et les échecs pertinents sont testés (ex. : synchronisation réussie, repli
iCal →
pronotepyen modeauto, modes explicites stricts, erreurs explicites). - Les outils de validation (
ruff,mypy,pytest,bandit) passent sans erreur. - Aucun secret ni configuration locale n'apparaît dans le diff ou les fichiers suivis.
- La documentation reste cohérente avec le code (ex. : mise à jour des exemples, des contrats ou des décisions d'architecture).
- Le handoff distingue clairement :
- Ce qui a été vérifié localement (ex. : tests unitaires, linter).
- Ce qui nécessite encore une vérification manuelle (ex. : tests d'intégration avec un serveur CalDAV réel).
13. Versionnage et releases
Politique de versionnage
Le projet suit Semantic Versioning (semver.org v2.0.0). Phase actuelle : 0.x (pré-1.0.0).
| Changement | Incrément |
|---|---|
| Défaut constaté au déploiement | Patch (0.1.Z) — correction rétrocompatible |
Ajout ou cassure en phase 0.x |
Minor (0.Y.0) |
| Déploiement réel validé | 1.0.0 |
Règle absolue de validation
Aucune montée de version (tag + release) ne peut être effectuée sans validation préalable en environnement réel. Les tests automatisés et la revue de code ne suffisent pas ; le correctif ou la fonctionnalité doit avoir été testé avec succès sur le serveur de production (ou un environnement équivalent) avant de tagger.
Procédure de release
- Valider en environnement réel : le correctif ou la fonctionnalité est testé sur le serveur de production.
- Mettre à jour
pyproject.toml: incrémenter le champversionà la nouvelle version. - Mettre à jour
CHANGELOG.md: ajouter une entrée sous le format Keep a Changelog avec la nouvelle version et la date. - Committer : un commit
chore: monter en version x.y.zregroupe les mises à jour depyproject.tomletCHANGELOG.md. - Tagger : créer un tag annoté
vx.y.zsur le commit de version. - Pousser le tag :
git push origin vx.y.z. - Créer la release sur Gitea avec le changelog correspondant.
Cohérence des versions
Les trois sources de version doivent toujours être synchronisées au moment d'un tag :
- Le tag Git (
vx.y.z) pyproject.toml(version = "x.y.z")CHANGELOG.md(## [x.y.z] - YYYY-MM-DD)
Rappel : Ne jamais créer un tag sans avoir d'abord mis à jour
pyproject.tomletCHANGELOG.md.