Scaffold: préparation du projet pronote-sync

- Guide de développement (GUIDE_DEV_PYTHON.md) : spécification complète
- TODO.md : 15 jalons de développement (M1-M15) avec étapes et critères d'acceptation
- AGENTS.md : guide de contribution pour agents et développeurs
- pyproject.toml : configuration projet (dépendances, ruff, mypy strict, pytest)
- .gitignore : exclusion venv, bytecode, .env, caches, state files
- .env.example : template des variables d'environnement (placeholders)
- Structure du package pronote_sync/ (14 sous-packages avec __init__.py)
- tests/ avec conftest.py et fixtures/
- Environnement virtuel .venv/ (Python 3.14)
This commit is contained in:
2026-09-05 18:45:43 +02:00
commit a2efd61c4e
23 changed files with 6771 additions and 0 deletions

37
.env.example Normal file
View File

@@ -0,0 +1,37 @@
# --- Pronote ---
PRONOTE_ICAL_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
# Sources (auto = essayer iCal d'abord, puis pronotepy)
PRONOTE_AGENDA_SOURCE=auto
PRONOTE_HOMEWORK_SOURCE=auto
PRONOTE_MESSAGES_SOURCE=pronotepy
# --- CalDAV ---
CALDAV_URL=https://caldav.example.com/calendars/user/pronote/
CALDAV_USERNAME=user@example.com
CALDAV_PASSWORD=your_caldav_password
# Fenêtre de synchronisation (jours)
SYNC_PAST_DAYS=7
SYNC_FUTURE_DAYS=30
# --- Agenda théorique ---
THEORETICAL_AGENDA_PATH=./data/theoretical.ics
# --- XMPP ---
XMPP_JID=user@example.com
XMPP_PASSWORD=your_xmpp_password
XMPP_RECIPIENT=parent@example.com
# --- IA (optionnelle) ---
AI_ENABLED=true
AI_BASE_URL=https://api.openai.com/v1
AI_API_KEY=your_ai_api_key
AI_MODEL=gpt-4o-mini
# --- Divers ---
DRY_RUN=false
LOG_LEVEL=INFO

53
.gitignore vendored Normal file
View File

@@ -0,0 +1,53 @@
# --- Python bytecode ---
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
# --- Virtual environments ---
.venv/
venv/
env/
ENV/
# --- Distribution / build ---
build/
dist/
*.egg-info/
*.egg
pip-wheel-metadata/
# --- Testing / coverage ---
.pytest_cache/
.coverage
.coverage.*
htmlcov/
.tox/
.mypy_cache/
.ruff_cache/
coverage.xml
# --- Environment files ---
.env
.env.*
!.env.example
# --- IDE files ---
.vscode/
.idea/
*.swp
*.swo
*~
# --- OS files ---
.DS_Store
Thumbs.db
# --- Project-specific state files ---
.blog_rss_state.json
.caldav_sync_state.json
*.state.json
# --- Logs ---
*.log

158
AGENTS.md Normal file
View File

@@ -0,0 +1,158 @@
# 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 (actuellement 3.14 dans `.venv/`)
- **Dépendances principales** :
`pydantic>=2.0`, `pydantic-settings`, `icalendar`, `caldav`, `slixmpp`, `pronotepy`, `feedparser`, `requests`, `httpx`
### 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
├── 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.
### 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**.
- Si `pronotepy` échoue → fallback vers le parsing **iCal**.
- Si tout échoue → lever une **erreur explicite**.
---
## 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`.
### 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) |

6133
GUIDE_DEV_PYTHON.md Normal file

File diff suppressed because it is too large Load Diff

276
TODO.md Normal file
View File

@@ -0,0 +1,276 @@
# TODO — Développement de `pronote-sync` (Pronote → CalDAV + XMPP)
> Plan de développement dérivé du `GUIDE_DEV_PYTHON.md`. Package Python : `pronote_sync`.
> Pipeline : récupération Pronote (iCal / pronotepy) → blog RSS → comparaison agenda théorique → sync CalDAV → synthèse IA → message XMPP.
> Contraintes transverses : Python ≥ 3.13.5, Pydantic v2 + pydantic-settings, injection par `typing.Protocol`, tests sans réseau, idempotence, mode dégradé, masquage systématique des secrets, coverage ≥ 90 %.
---
## M1. Échafaudage et outillage — Priorité : Haute
Mettre en place le dépôt, l'environnement, l'arborescence du package et la chaîne d'outils (lint/type/test/pré-commit).
- [ ] Créer l'environnement virtuel Python (≥ 3.13.5) et l'activer (`python -m venv venv`).
- [ ] Créer `pyproject.toml` d'après l'Annexe C (projet `pronote-sync`, `requires-python = ">=3.13.5"`, dépendances, extras `dev` et `ai-litellm`, script console `pronote-sync`).
- [ ] Configurer ruff (`line-length = 100`, `target-version = "py313"`, règles E/W/F/I/B/C4/UP), mypy (`strict`), bandit et coverage (`fail_under = 90`) dans `pyproject.toml`.
- [ ] Créer `.gitignore` (venv, `__pycache__`, `.env`, `.blog_rss_state.json`, `.coverage`, artefacts `.ics` temporaires).
- [ ] Créer l'arborescence `pronote_sync/` avec `__init__.py` dans chaque package (`config`, `models`, `sources/pronote`, `sources/blog`, `sources/theoretical`, `sync`, `synthesis`, `channels`, `pipeline/steps`, `cli`, `utils`, `tests`).
- [ ] Ajouter la configuration pre-commit (ruff, mypy, bandit, detect-secrets, `trailing-whitespace`, `end-of-file`).
- [ ] Installer le projet en mode éditable : `pip install -e ".[dev]"`.
- [ ] Créer le commit initial (scaffold + `.gitignore`, sans aucun secret).
### Critères d'acceptation
- Le package `pronote_sync` est importable sans erreur.
- `ruff check .`, `mypy .` et `pytest` s'exécutent sans erreur d'import.
- `pre-commit run --all-files` réussit.
- Le dépôt ne contient aucun secret (vérifié par detect-secrets).
---
## M2. Configuration et gestion des secrets — Priorité : Haute
Implémenter la configuration Pydantic Settings, le masquage des secrets et la journalisation sûre.
- [ ] Créer `config/settings.py` : `PronoteSettings`, `CalDAVSettings`, `XmppSettings`, `AISettings`, `AppSettings`, `Settings` (§3.2) avec `SecretStr` et prefixes d'env.
- [ ] Créer `config/env.py` pour le chargement du `.env` (`SettingsConfigDict(env_file=".env")`).
- [ ] Créer `.env.example` complet (toutes variables obligatoires §3.1.1 + optionnelles §3.1.2).
- [ ] Créer `utils/redaction.py` : `redact_url`, `redact_secrets`, `redact_exception` (§4.2.1).
- [ ] Créer `utils/logging.py` : `setup_logging` + `RedactingFormatter` masquant les secrets dans messages et args (§4.2.2).
- [ ] Créer `utils/uid.py` : `normalize_uid` (suppression des suffixes temporels des UID Pronote).
- [ ] Vérifier qu'aucun `SecretStr` n'est affiché en clair via `str()`/`print`.
### Critères d'acceptation
- `from pronote_sync.config.settings import settings` fonctionne et charge `.env`.
- `redact_secrets("...icalsecurise=TOKEN...")` masque le token et l'URL.
- Les logs ne contiennent jamais de token, mot de passe ou clé API (même en DEBUG).
---
## M3. Modèles de données Pydantic — Priorité : Haute
Définir tous les modèles de domaine, immuables pour les contrats, mutables pour les résultats de travail.
- [ ] Créer `models/agenda.py` : `Status`, `LessonStatus`, `HomeworkBlock`, `Lesson` (frozen), `SchoolEventKind`, `SchoolEvent`, `TheoreticalLesson`.
- [ ] Créer `models/homework.py` : `Homework` (frozen, `id` = hachage stable).
- [ ] Créer `models/message.py` : `MessageType`, `Message` (frozen).
- [ ] Créer `models/diff.py` : `AgendaChangeType`, `AgendaChange` (frozen), `AgendaDiff` (frozen).
- [ ] Créer `models/pronote.py` : `PronoteData` (mutable).
- [ ] Créer `models/sync.py` : `CalDAVSyncStatus`, `CalDAVSyncPlan`, `CalDAVSyncResult` (mutable).
- [ ] Créer `models/synthesis.py` : `SynthesisInput`, `SynthesisResult`.
- [ ] Créer `models/blog.py` : `BlogArticle` (frozen), `ExternalInfo` (§5 bis.5/6).
- [ ] Créer `models/xmpp.py` : `XmppMessage` (frozen) intégrant `external_info`.
- [ ] Créer `models/__init__.py` ré-exportant tous les modèles.
- [ ] Valider la sérialisation JSON (datetime/date en ISO) pour chaque modèle.
### Critères d'acceptation
- Chaque modèle s'instancie et se sérialise en JSON valide.
- `Lesson`, `Homework`, `Message`, `AgendaChange`, `AgendaDiff`, `XmppMessage`, `BlogArticle` sont `frozen=True`.
- `PronoteData` et `CalDAVSyncResult` sont mutables ; tous les modèles importables via `models/__init__.py`.
---
## M4. Sources Pronote (iCal + pronotepy + repli) — Priorité : Haute
Récupérer et normaliser l'agenda, les devoirs et les messages Pronote, avec repli entre iCal et pronotepy.
- [ ] Créer `sources/pronote/ical.py` : `fetch_ical(url)` (HTTP via `requests`, erreurs redactées) et parsing iCal → `Lesson`/`Homework`/`SchoolEvent` (`icalendar`).
- [ ] Extraire les blocs de devoirs (`HomeworkBlock`) depuis `DESCRIPTION` et dédupliquer les devoirs (clé normalisée par date).
- [ ] Détecter les statuts (`CANCELLED`/`MOVED`) via `CATEGORIES` et `STATUS:CANCELLED`.
- [ ] Créer `sources/pronote/client.py` : client `pronotepy` (messages, informations, discussions, sondages, et devoirs en repli) avec masquage des erreurs.
- [ ] Créer `sources/pronote/fallback.py` : sélection de source selon `PRONOTE_*_SOURCE` (auto/ical/pronotepy) et `PronoteFetcher` unifiant `fetch_agenda`/`fetch_homework`/`fetch_messages`.
- [ ] Implémenter le repli : iCal échoue → pronotepy ; pronotepy échoue → iCal ; les deux échouent → `PipelineCriticalError`.
- [ ] Normaliser les UID via `utils/uid.normalize_uid` pour la stabilité des événements.
### Critères d'acceptation
- `fetch_ical` parse `tests/fixtures/pronote-4e.ics` en leçons/devoirs/événements corrects (cours annulé détecté).
- Le client pronotepy récupère messages/devoirs (mocké).
- Le repli bascule correctement et lève une erreur critique si aucune source disponible.
- Aucun secret dans les messages d'erreur de fetch.
---
## M5. Source blog (RSS) — Priorité : Moyenne
Récupérer le flux RSS du blog du collège, parser et dédupliquer les articles.
- [ ] Créer `sources/blog/rss.py` : `BlogRSSClient.fetch_and_parse(known_guids)` avec `feedparser` (§5 bis.7.1).
- [ ] Parser les dates (RFC 822 / ISO 8601) et convertir le HTML en texte brut (`BeautifulSoup` + `html.unescape`).
- [ ] Créer `sources/blog/state.py` (ou `sync/blog_state.py`) : `BlogRSSState` (JSON : `known_guids`, `etag`, `last_modified`).
- [ ] Implémenter la déduplication par GUID et le cache HTTP (`If-Modified-Since` / `etag`).
- [ ] Gérer un flux invalide (`bozo`) et les exceptions sans fuite de secret (retour `[]`/warning).
### Critères d'acceptation
- `fetch_and_parse` renvoie les nouveaux articles triés par date décroissante, sans doublons.
- L'état persiste les GUID connus entre deux appels.
- Un flux invalide ne plante pas le pipeline (warning non bloquant).
---
## M6. Source agenda théorique — Priorité : Moyenne
Lire l'agenda théorique (iCal ou CSV) via une interface de provider extensible.
- [ ] Créer `sources/theoretical/provider.py` : protocole `TheoreticalAgendaProvider` (§8.2).
- [ ] Créer `sources/theoretical/file.py` : lecture fichier iCal/CSV → liste de `TheoreticalLesson` (§8.3).
- [ ] Normaliser les matières et créneaux pour le matching déterministe.
- [ ] Supporter les deux formats (iCal et CSV) derrière la même interface.
### Critères d'acceptation
- `file.py` lit `tests/fixtures/theoretical.ics` et `theoretical.csv` en `TheoreticalLesson`.
- Le provider renvoie une liste stable et déterministe (tri par identifiant).
---
## M7. Synchronisation CalDAV — Priorité : Haute
Synchroniser différentiellement les événements Pronote vers le calendrier CalDAV, de façon idempotente.
- [ ] Créer `sync/caldav.py` : `CalDAVClient` (connexion, liste/ajout/MAJ/suppression, marqueur `X-PRONOTE-SYNC-MANAGED: v1`).
- [ ] Créer `sync/state.py` : état local de sync (SQLite ou JSON) assurant l'idempotence (UID connus).
- [ ] Calculer le `CalDAVSyncPlan` (to_add / to_update / to_remove) par UID stable.
- [ ] Implémenter la sync différentielle : conserver les cours annulés (`STATUS:CANCELLED`), ne pas supprimer.
- [ ] Garantir l'idempotence (2 exécutions identiques → même `CalDAVSyncResult`).
- [ ] Réutiliser `BlogRSSState` pour l'état blog si pertinent (sinon `sync/blog_state.py`).
### Critères d'acceptation
- Le plan de sync est correctement calculé (PronoteData vs état local).
- Un run dry-run n'écrit rien ; deux runs identiques donnent un résultat identique.
- Les événements annulés restent (`STATUS:CANCELLED`) et sont marqués `MANAGED`.
---
## M8. Comparaison avec l'agenda théorique (diff) — Priorité : Haute
Comparer l'agenda réel et l'agenda théorique pour générer les ajouts/suppressions/modifications.
- [ ] Créer `sync/diff.py` : `AgendaComparator` avec matching déterministe (jour + créneau avec tolérance + matière normalisée).
- [ ] Générer `AgendaDiff` / `AgendaChange` (added / removed / modified).
- [ ] Appliquer la politique de départage : tri par UID stable puis comparaison exacte ; première correspondance en cas de multi-match (§8.4).
- [ ] Gérer l'absence de fichier théorique (diff vide, non bloquant).
### Critères d'acceptation
- La comparaison produit les bons `added`/`removed`/`modified`.
- Le matching est déterministe (même entrée → même résultat).
- Sans `THEORETICAL_AGENDA_PATH`, retourne un diff vide sans erreur.
---
## M9. Synthèse IA — Priorité : Moyenne
Générer une synthèse optionnelle via un fournisseur IA, avec mode dégradé strict.
- [ ] Créer `synthesis/provider.py` : protocole `SynthesisProvider.generate → Optional[SynthesisResult]` (ne lève jamais d'exception).
- [ ] Créer `synthesis/openai.py` : `OpenAISynthesisProvider` (httpx, prompt système FR, max 800 car., timeout 30 s, temp 0.3).
- [ ] Créer `synthesis/litellm.py` : `LiteLLMSynthesisProvider` (optionnel, extra `ai-litellm`).
- [ ] Créer `synthesis/__init__.py` : factory `get_synthesis_provider(settings)` (OpenAI par défaut, litellm si `AI_PROVIDER=litellm`).
- [ ] Mode dégradé : clé absente / timeout / exception → retour `None` (le pipeline continue sans synthèse).
- [ ] Respecter les contraintes (3-5 phrases, ton sobre, pas d'emoji dans le texte IA).
### Critères d'acceptation
- `generate` retourne une synthèse ≤ 800 car. conforme au prompt système.
- Clé absente ou erreur réseau → `None` (aucune exception propagée).
- La factory renvoie le bon provider ; litellm derrière l'extra optionnel.
---
## M10. Canal XMPP — Priorité : Haute
Construire et envoyer le message XMPP structuré via un compte bot dédié (message direct, pas de PubSub).
- [ ] Créer `channels/protocol.py` : protocole `Channel` (méthode d'envoi).
- [ ] Créer `channels/xmpp.py` : `XmppChannel` (slixmpp, message direct, compte bot dédié).
- [ ] Implémenter `_format_message(XmppMessage)` : synthèse + liste brute des devoirs + changements + messages + infos blog (emojis 📌📅📚💬 autorisés).
- [ ] Gérer les erreurs XMPP (reconnexion, timeout) avec masquage des secrets, non bloquant (`PipelineWarning`).
- [ ] Créer `channels/__init__.py` : factory de canaux.
### Critères d'acceptation
- `XmppChannel.send` envoie un message direct formaté (slixmpp mocké en test).
- Erreur XMPP → `PipelineWarning`, jamais d'exception non gérée.
- Aucun secret dans les logs XMPP.
---
## M11. Orchestration du pipeline — Priorité : Haute
Composer et orchestrer toutes les étapes avec gestion d'erreurs dégradée et mode dry-run.
- [ ] Créer `pipeline/steps/errors.py` : `ErrorSeverity`, `PipelineError`, `PipelineWarning`, `PipelineCriticalError`.
- [ ] Créer les étapes `pipeline/steps/` : `fetch.py`, `normalize.py`, `compare.py`, `caldav_sync.py`, `synthesis.py`, `send.py`, `fetch_blog.py`.
- [ ] Créer `pipeline/run.py` : `PipelineRunner` (composition root) orchestrant fetch → normalize → fetch_blog → compare → caldav_sync → synthesis → send.
- [ ] Gérer les erreurs dégradées (continuer sauf critique) et renvoyer `(PronoteData, erreurs + warns)`.
- [ ] Implémenter le mode `dry_run` (aucune écriture CalDAV/XMPP).
- [ ] Câbler l'injection des dépendances (Protocol + composition root), sans singleton global.
### Critères d'acceptation
- Le pipeline complet s'exécute de bout en bout (mocks) dans le bon ordre.
- Une erreur non critique (ex : synthèse IA) n'empêche pas l'envoi XMPP.
- `dry_run=True` n'effectue aucune écriture ; aucune source disponible → erreur critique explicite.
---
## M12. Point d'entrée CLI — Priorité : Haute
Exposer le lancement du pipeline via une interface en ligne de commande.
- [ ] Créer `cli/main.py` : `main()` (point d'entrée `pronote-sync`), args `--dry-run`, `--log-level`.
- [ ] Initialiser les logs (`setup_logging`) et charger `settings` au démarrage.
- [ ] Construire la composition root et lancer `PipelineRunner.run()`.
- [ ] Gérer le code de retour et l'affichage des erreurs (redactées).
### Critères d'acceptation
- `pronote-sync --dry-run --log-level DEBUG` s'exécute sans effet de bord.
- Le script console est installable (`[project.scripts]` dans `pyproject.toml`).
- Les erreurs affichées ne contiennent aucun secret.
---
## M13. Tests et couverture — Priorité : Haute
Couvrir l'ensemble du code par des tests sans réseau, avec fixtures anonymisées, jusqu'à ≥ 90 %.
- [ ] Créer `tests/fixtures/` : `pronote-4e.ics`, `pronote-6e.ics`, `theoretical.ics`, `theoretical.csv`, `blog_rss.xml` (anonymisés, sans `icalsecurise`).
- [ ] Créer `tests/conftest.py` : fixtures partagées (sample_lesson, sample_cancelled_lesson, sample_homework, sample_school_event, sample_message, sample_pronote_data…).
- [ ] Écrire `tests/unit/` : `test_models`, `test_parsing` (iCal), `test_uid`, `test_redaction`, `test_diff`, `test_sync`.
- [ ] Écrire `tests/integration/` : `test_pipeline`, `test_caldav` (mocké), `test_xmpp` (mocké).
- [ ] Écrire `tests/e2e/test_cli.py` : exécution CLI en dry-run.
- [ ] Tests sans réseau (mocks `responses`/`aioresponses`/`pytest-mock`) ; couverture ≥ 90 %.
- [ ] Ajouter un test négatif : les messages d'erreur ne fuient pas de secrets (`icalsecurise`, clés API, mots de passe).
### Critères d'acceptation
- `pytest` passe et `pytest --cov` atteint ≥ 90 % (`fail_under = 90`).
- Aucun test ne fait de requête réseau réelle.
- Le test de non-fuite de secrets passe.
---
## M14. Déploiement — Priorité : Moyenne
Mettre en production de façon supervisée (planification, rotation des logs, vérification des secrets).
- [ ] Créer une unité systemd (`pronote-sync.service` + timer) ou une ligne cron (exécution quotidienne).
- [ ] Créer `logrotate.d/pronote_sync` (daily, rotate 7, compress, delaycompress).
- [ ] Ajouter un script de vérification des secrets (§13.6) exécuté avant chaque déploiement.
- [ ] Documenter la supervision (logs, alertes en cas d'échec) et la maintenance (maj dépendances, dry-run avant MAJ).
- [ ] Vérifier `pip check` et tester le dry-run avant mise en production.
### Critères d'acceptation
- Le service/timer systemd (ou cron) lance le pipeline quotidiennement.
- `logrotate` est configuré et valide.
- Le dry-run passe en pré-production ; le script de secrets ne donne pas de faux négatif.
---
## M15. Documentation et revue finale — Priorité : Moyenne
Rédiger la documentation utilisateur et finaliser le projet.
- [ ] Créer `README.md` (installation, configuration `.env`, usage CLI, systemd/docker, limites, RGPD).
- [ ] Documenter l'architecture (pipeline, modules) en résumé.
- [ ] Ajouter `CHANGELOG` initial et la licence (MIT).
- [ ] Revue finale : cohérence avec le guide, aucun secret documenté en clair.
- [ ] (Optionnel) Configurer GitHub Actions CI/CD (pytest + bandit + ruff + mypy) d'après §Prochaines étapes.
### Critères d'acceptation
- `README.md` permet d'installer et de lancer le projet sans le guide.
- La CI exécute tests + lint + sécurité.
- Aucun secret dans la documentation.

0
pronote_sync/__init__.py Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

114
pyproject.toml Normal file
View File

@@ -0,0 +1,114 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "pronote-sync"
version = "0.1.0"
description = "Synchronisation Pronote → CalDAV + XMPP"
license = {text = "MIT"}
requires-python = ">=3.13"
authors = [
{name = "Votre Nom", email = "votre@email.com"}
]
keywords = ["pronote", "caldav", "xmpp", "sync", "school"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Office/Business :: Scheduling",
"Topic :: Communications :: Chat",
"Topic :: Utilities",
]
dependencies = [
"requests>=2.31.0",
"icalendar>=5.0.0",
"caldav>=1.3.0",
"slixmpp>=1.8.0",
"pydantic>=2.0.0",
"pydantic-settings>=2.0.0",
"pronotepy>=2.15.0",
"openai>=1.0.0",
"httpx>=0.25.0",
"feedparser>=6.0.0",
"beautifulsoup4>=4.12.0",
]
[project.optional-dependencies]
ai-litellm = ["litellm>=1.0"]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"pytest-mock>=3.0.0",
"requests-mock>=1.11.0",
"aioresponses>=0.7.0",
"bandit>=1.7.0",
"safety>=2.0.0",
"pre-commit>=3.0.0",
"detect-secrets>=1.4.0",
"ruff>=0.4.0",
"mypy>=1.8.0",
]
[project.scripts]
pronote-sync = "pronote_sync.cli.main:main"
[project.urls]
Homepage = "https://github.com/votre-utilisateur/pronote-sync"
Documentation = "https://github.com/votre-utilisateur/pronote-sync#readme"
Repository = "https://github.com/votre-utilisateur/pronote-sync"
Issues = "https://github.com/votre-utilisateur/pronote-sync/issues"
[tool.setuptools.packages.find]
where = ["."]
include = ["pronote_sync*"]
[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-v --tb=short"
[tool.coverage.run]
source = ["pronote_sync"]
branch = true
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise NotImplementedError",
"if TYPE_CHECKING:",
]
fail_under = 90
[tool.bandit]
exclude_dirs = ["tests", "venv"]
skips = ["B101"] # Ignorer les assertions (utilisées dans les tests)
[tool.ruff]
line-length = 100
target-version = "py313"
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
ignore = [
"E501", # line too long (géré par line-length)
]
[tool.mypy]
python_version = "3.13"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
strict = true

0
tests/__init__.py Normal file
View File

0
tests/conftest.py Normal file
View File

0
tests/fixtures/__init__.py vendored Normal file
View File