# 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`](./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` (mode `strict=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.md`](./TODO.md) pour les 15 jalons (M1-M15). --- ## 4. Commandes essentielles ### Environnement ```bash # Activer le virtualenv source .venv/bin/activate # Installer les dépendances de développement pip install -e ".[dev]" ``` ### Qualité de code ```bash # 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 ```bash # Exécuter les tests pytest # Exécuter les tests avec couverture pytest --cov ``` ### CLI ```bash # Exécuter en mode dry-run (simulation) pronote-sync --dry-run ``` --- ## 5. Conventions de code ### Typage - **Strict** : `mypy` en mode `strict=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 utilisent `frozen=True`. ### Architecture - **Injection de dépendances** : Utiliser `typing.Protocol` et une **composition root** dans `pipeline/run.py`. **Interdiction** des singletons globaux. - **Erreurs** : Conserver une seule hiérarchie dans `pronote_sync/errors.py` ; ne pas créer de doublon dans `pipeline/steps/errors.py`. ### Style - **Longueur de ligne** : 100 caractères maximum (configuré dans `ruff`). - **Imports** : Triés automatiquement par `ruff` (intègre `isort`). ### 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 utiliser `pronotepy` uniquement si iCal lève une exception. - En mode source explicite (`ical` ou `pronotepy`), ne pas changer silencieusement de source. - En mode `auto`, si iCal et `pronotepy` é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) et `PRONOTE_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_ENT` vers une fonction de `pronotepy.ent` au moyen d'une liste fermée. - Exposer séparément les cours et les devoirs dans le client ; filtrer les devoirs `pronotepy` sur 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 utilise `pronotepy.qrcode_login(qr_code, pin, uuid)` avec les paramètres `PRONOTE_QR_CODE_FILE` (chemin du JSON QR) et `PRONOTE_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` (permissions `0600`, 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 une `PronoteAPIError`). 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), une `PronoteAuthRotationError` est levée. Cette erreur se propage sans wrapping à travers `PronoteFetcher` et `fetch_step` jusqu'à `PipelineRunner.run()`, qui : - journalise l'erreur (expurgée) ; - envoie une notification XMPP actionnable si le canal est disponible et `dry_run` est inactif ; - retourne un résultat dégradé `(None, errors)`. - `PronoteAuthRotationError` est 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 avec `except Exception` sans la re-léver d'abord. - Le fichier `.pronote_auth_state.json` ne 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-compatible` réutilise `OpenAISynthesisProvider` avec un `base_url` personnalisé ; aucun nouveau provider n'est créé. - `AI_BASE_URL` et `AI_MODEL` sont requis ; `AI_API_KEY` est requis (MVP). - L'URL doit utiliser `https` sauf si `AI_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 (`ValueError` catché). - Aucune manipulation automatique de `/v1` n'est effectuée. - Configuration incomplète ou invalide → `None` avec 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.md` utilisant `Args:`/`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 : ```python 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()`, et `redact_exception()` depuis `utils/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 utiliser `raise ... 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.example` comme template. --- ## 7. Plan de développement Le projet suit un plan séquentiel en **15 jalons** (M1-M15) décrits dans [`TODO.md`](./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`](./GUIDE_DEV_PYTHON.md) | Spécification complète (architecture, modèles, parsing, déploiement) | | [`TODO.md`](./TODO.md) | Plan de développement détaillé (15 jalons) |