Export caldav with logs

This commit is contained in:
Laurent Chedanne committed 2026-10-15 15:19:23 +02:00
1 parent af94d397f5
commit c05fc432eb
5 files changed
+836 -49

No files matched your search

+235
View File
@@ -0,0 +1,235 @@
# Export CalDAV vers CSV
Le script [`crealead-caldav.py`](../crealead-caldav.py) récupère des événements
d'agendas accessibles en CalDAV, notamment Nextcloud, et produit un fichier CSV
par calendrier configuré. Ces fichiers peuvent ensuite être utilisés par
`csv-to-timemanager.py`.
## Pré-requis
- Python 3, avec `pip` et le module `venv`. L'environnement Docker du dépôt
utilise Python 3.9.2.
- Les dépendances de [`requirements.txt`](../requirements.txt), notamment
`caldav`, `icalendar` et `pytz`.
- Un serveur CalDAV accessible depuis la machine d'exécution et un compte
autorisé à lire les calendriers à exporter.
- Des droits de lecture sur `config.json` et d'écriture sur les fichiers CSV
ainsi que sur le dossier `logs/`, situé à côté du script.
- Bash pour utiliser le lanceur `exec-export-caldav.sh`.
Depuis la racine du dépôt, préparer l'environnement :
```bash
python3 -m venv env
source env/bin/activate
python -m pip install -r requirements.txt
```
L'environnement Docker fourni constitue une autre façon d'exécuter le script
et les tests. Sa construction est décrite dans
[`support/docker-dev/README.md`](../support/docker-dev/README.md).
## Usage
### Configuration minimale
Créer un fichier `config.json` à la racine du dépôt. Exemple pour exporter les
événements d'octobre 2026 :
```json
{
"caldav_url": "https://cloud.example.com/remote.php/dav/",
"username": "utilisateur",
"password": "mot-de-passe-application",
"year_start": 2026,
"month_start": 10,
"day_start": 1,
"year_end": 2026,
"month_end": 11,
"day_end": 1,
"calendars": [
{
"calendar_name": "Temps de travail",
"csv_name": "csv/utilisateur.csv"
}
]
}
```
| Champ | Rôle |
| --- | --- |
| `caldav_url` | URL du serveur CalDAV. |
| `username`, `password` | Identifiants du compte qui peut lire les calendriers. |
| `year_start`, `month_start`, `day_start` | Date de début de la recherche, à minuit. Les valeurs sont des nombres entiers. |
| `year_end`, `month_end`, `day_end` | Date de fin de la recherche, à minuit. Pour couvrir un mois, utiliser le premier jour du mois suivant. |
| `calendars` | Liste des calendriers à exporter. |
| `calendars[].calendar_name` | Texte contenu dans le nom du calendrier distant, avec respect de la casse. Il doit correspondre à un seul calendrier. |
| `calendars[].csv_name` | Chemin du fichier CSV à écrire. Un chemin relatif est résolu depuis le répertoire de lancement. |
Ajouter une entrée dans `calendars` pour chaque calendrier à exporter.
Les champs `db_*`, `user_name` et `api_password`, utilisés par d'autres scripts
du dépôt, ne sont pas nécessaires à cet export.
`config.json` est ignoré par Git. Remplacer les valeurs d'exemple par les
informations du serveur et du compte utilisés.
### Lancement
Lancer les commandes depuis la racine du dépôt : le script lit
`./config.json`, et le lanceur recherche `env/bin/activate` dans le répertoire
courant. Créer au préalable les dossiers de destination des CSV ; le script
ne les crée pas.
```bash
mkdir -p csv
bash ./exec-export-caldav.sh
```
Le lanceur active l'environnement `env/`, puis exécute le script. Il est aussi
possible de lancer directement Python :
```bash
env/bin/python crealead-caldav.py
```
Le script ne propose pas d'arguments en ligne de commande. La période et les
calendriers sont définis dans `config.json`.
### Fichiers produits et traitement des événements
Chaque CSV existant est remplacé lors de l'export. Les lignes sont triées par
date et heure de début. Le séparateur est `;` et les colonnes sont :
```text
Titre;Date debut;Heure debut;Date fin;Heure fin;Categories;Emplacement;Description
```
Les événements récurrents sont demandés au serveur avec `expand=True`.
Le lecteur iCalendar prend en charge les paramètres des propriétés, comme
`TZID` et `VALUE=DATE`, ainsi que les textes répartis sur plusieurs lignes.
Les heures sont converties vers `Europe/Paris`. Une heure sans fuseau est
interprétée dans ce fuseau ; une date seule est exportée à minuit.
Si `DTEND` est absent, `DURATION` peut fournir la date de fin.
Les événements sans propriété `SUMMARY` sont ignorés. Ceux dont les dates
de début ou de fin sont absentes ou invalides sont également ignorés. Chaque
exclusion laisse un avertissement détaillé dans le journal, y compris en
l'absence de `SUMMARY`. Un document iCalendar illisible ou une ressource sans
composant `VEVENT` est signalé puis ignoré. Les autres événements restent
exportés. Si un calendrier configuré est introuvable ou correspond à plusieurs
calendriers distants, le script affiche un message et s'arrête avant l'écriture
des CSV.
En cas d'erreur d'écriture d'un CSV, le script s'arrête avec une exception.
Il laisse un avertissement pour chaque événement du calendrier en cours,
dont le fichier peut être incomplet, et des calendriers restant à écrire.
Les fichiers déjà écrits avec succès sont conservés.
### Journalisation
Les sorties standard et d'erreur sont affichées dans le terminal et copiées
dans `logs/crealead-caldav.log`, à côté du script. Le dossier est créé
automatiquement. Le journal utilise UTF-8, conserve les exécutions précédentes
et enregistre les traces d'exception en cas de plantage pendant le traitement.
Il n'y a pas de rotation automatique du journal. `logs/` est ignoré par Git.
Chaque nouvelle ligne du journal est préfixée par l'heure d'écriture en UTC,
au format ISO 8601 `YYYY-MM-DDTHH:MM:SSZ`, puis par `[info]` :
```text
[2026-10-15T12:34:56Z]: [info] - csv/utilisateur.csv ...
```
Cela s'applique aussi aux messages écrits en plusieurs fragments et à chaque
ligne d'une trace d'exception. L'horodatage est ajouté dans le fichier de
journal, sans modifier les messages ordinaires du terminal.
Tout événement non exporté est signalé avec `[warning][event]`, à la place de
`[info]`. Le message indique le calendrier distant (ou son nom configuré),
le fichier CSV, le titre, le début, la fin, l'UID et la raison de l'exclusion :
```text
[2026-10-15T12:34:56Z]: [warning][event] - Calendrier='Temps de travail' ; CSV='csv/utilisateur.csv' ; titre='Réunion' ; début='2026-10-15T08:00:00Z' ; fin='indisponible' ; UID='reunion-1' ; événement non exporté : dates de début/fin absentes ou invalides (date absente ou invalide)
```
L'heure entre crochets est celle du message ; les champs `début` et `fin`
décrivent le moment de l'événement. Les dates et heures décodées sont affichées
en UTC, les dates de journée entière restent des dates seules. En cas de
document illisible ou de date invalide, le script récupère les champs bruts
disponibles. Une métadonnée absente ou irrécupérable vaut `indisponible`.
Les retours à la ligne dans les métadonnées et le motif sont échappés pour
garder un avertissement sur une seule ligne. Ces avertissements sont aussi
visibles dans le terminal.
## Tests
Les tests se trouvent dans
[`tests/test_crealead_caldav.py`](../tests/test_crealead_caldav.py) et utilisent
`unittest`, fourni avec Python. Aucune dépendance de test supplémentaire
n'est nécessaire.
### Cas couverts
Les 13 tests de `ExportEventsTests` vérifient :
- La lecture de `DTSTART` et `DTEND` avec `TZID`, leur conversion vers Paris
et le tri chronologique des lignes.
- La conversion des dates UTC en été et en hiver, ainsi que celle d'un autre
fuseau horaire.
- Les événements sur une journée entière (`VALUE=DATE`), les heures sans
fuseau et le calcul de la fin à partir de `DURATION`.
- L'exclusion des événements sans propriété `SUMMARY`, des dates manquantes
ou invalides et des documents iCalendar illisibles, sans bloquer les
événements valides, ainsi que la présence d'un avertissement contextualisé
dans le journal pour chaque événement écarté.
- La récupération des métadonnées de chaque événement dans un document
illisible, la conservation d'une date brute invalide et le signalement
d'une ressource sans composant `VEVENT`.
- La conservation des deux-points dans les textes, des paramètres de texte,
des catégories et des lignes repliées, ainsi que le nettoyage de la
description HTML encodée et le contenu du CSV écrit.
Les 9 tests de `LogOutputTests` vérifient :
- La création de `logs/` à côté du script, la copie des deux sorties et
l'écriture immédiate des messages, y compris les accents.
- L'ajout au journal existant et le choix du nom du journal d'après celui du
script, y compris lorsqu'une exécution se termine sans retour à la ligne.
- L'horodatage en UTC, le préfixe `[info]` de chaque ligne, les écritures en
plusieurs fragments, les messages sur plusieurs lignes et les lignes vides.
- Le préfixe `[warning][event]` sans `[info]`, les métadonnées de l'événement,
l'échappement des retours à la ligne et la séparation d'un avertissement
après un message ordinaire inachevé.
- La sauvegarde des traces d'exception et la restauration des sorties après
une erreur.
- La conservation du code de sortie d'un `SystemExit`, sans ajouter de trace
d'exception, et la sauvegarde de son éventuel message.
Le test de `ExportFailureTests` provoque une erreur d'écriture CSV. Il vérifie
qu'un avertissement est enregistré pour les événements du calendrier en
échec et des calendriers restant à écrire, et que le premier CSV déjà écrit
est conservé.
Ces tests utilisent le vrai lecteur `icalendar`, des événements synthétiques
et des dossiers temporaires pour les CSV et les journaux. Le module CalDAV
et les réponses du serveur sont simulés : aucun accès réseau, compte,
`config.json` ou calendrier réel n'est nécessaire. Ils vérifient que la
recherche demande `expand=True`, mais ne valident pas l'expansion des
récurrences par un serveur réel ni l'import dans TimeManager.
### Exécution
Depuis la racine du dépôt, avec les dépendances installées dans `env/` :
```bash
env/bin/python -B -m unittest discover -s tests -v
```
L'option `-B` évite de créer des fichiers de cache Python. La suite contient
actuellement 23 tests ; une exécution réussie se termine par `OK`.
Avec l'image Docker du dépôt déjà construite :
```bash
docker run --rm -v "$(pwd)":/app -w /app cal-to-time-manager:dev -B -m unittest discover -s tests -v
```