Watchdog : dès qu'un fichier arrive, ton script Python passe à l'action

Watchdog : dès qu'un fichier arrive, ton script Python passe à l'action

De quoi avez-vous besoin

Version de Python

3.x

Packages

  • {"nom": "watchdog", "version": "6.0.0"}

Difficulté

Intermédiaire

Un script qui dort en attendant qu'un fichier tombe dans un dossier, c'est exactement ce que fait Watchdog. Pas de polling toutes les deux secondes, pas de cron qui scanne un répertoire à l'aveugle : la bibliothèque s'appuie directement sur le système de fichiers pour te prévenir à l'instant où un fichier est créé, modifié ou supprimé. Tes automatisations réagissent en quelques millisecondes, sans gaspiller de CPU.

La méthode naïve, c'est la boucle : toutes les deux secondes, lister les fichiers d'un dossier, comparer avec la liste précédente, et agir si quelque chose a bougé. Ça marche, mais ça consomme du CPU en permanence, ça rate les fichiers créés puis supprimés entre deux passages, et ça complique le code pour rien. Watchdog délègue ce travail au noyau : inotify sur Linux, FSEvents sur macOS, ReadDirectoryChangesW sur Windows. Ton script ne se réveille que quand il y a réellement du nouveau.

Watchdog est un standard du monde Python. On le retrouve dans les chaînes de facturation, les pipelines de traitement d'images ou les serveurs qui reconstruisent un site dès qu'un article est modifié. Le principe se lit en une minute et le code tient en une vingtaine de lignes.

1. Installe Watchdog

bash
pip install watchdog

Rien d'autre à configurer : Watchdog est une bibliothèque autonome, sans service à lancer ni dépendance exotique. Un environnement virtuel reste recommandé, comme pour tout projet Python.

2. Le squelette : voir les événements défiler

On commence par le minimum vital. On crée un gestionnaire qui hérite de FileSystemEventHandler, on surcharge la méthode on_created, puis on branche un Observer sur le dossier de ton choix.

python
import time
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class GestionnaireFichiers(FileSystemEventHandler):
    def on_created(self, event):
        if event.is_directory:
            return
        print(f"[+] Nouveau fichier : {Path(event.src_path).name}")

def main():
    dossier = Path.home() / "inbox"
    dossier.mkdir(exist_ok=True)

    observateur = Observer()
    observateur.schedule(GestionnaireFichiers(), str(dossier), recursive=False)
    observateur.start()
    print(f"Surveillance de {dossier} ... Ctrl+C pour arreter")

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observateur.stop()
    observateur.join()

if __name__ == "__main__":
    main()

Lance ce script, ouvre un second terminal et dépose un fichier dans inbox : son nom s'affiche immédiatement. L'Observer tourne dans son propre thread, ton programme principal reste libre. Ctrl+C pour arrêter proprement.

3. Les quatre événements qui comptent

on_created n'est que le début. FileSystemEventHandler expose quatre méthodes que tu peux surcharger selon le cas :

python
class GestionnaireFichiers(FileSystemEventHandler):
    def on_created(self, event): ...   # un fichier apparait
    def on_modified(self, event): ...  # un fichier change
    def on_deleted(self, event): ...   # un fichier disparait
    def on_moved(self, event): ...     # un fichier est renomme ou deplace

Chaque event expose src_path (le chemin concerné) et is_directory (vrai si c'est un dossier). Pour on_moved, tu récupères aussi dest_path.

4. Une vraie réaction : trier les fichiers par extension

Afficher des noms, c'est bien. Déclencher une action, c'est mieux. Le script suivant déplace chaque nouveau fichier vers un sous-dossier selon son extension : images, tableaux, documents.

python
import time
import shutil
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

DOSSIERS = {
    ".jpg": "images", ".jpeg": "images", ".png": "images",
    ".csv": "tableaux", ".xlsx": "tableaux",
    ".pdf": "documents", ".txt": "documents",
}

def attendre_fichier_complet(chemin, patience=5.0, intervalle=0.3):
    taille_prec = -1
    debut = time.time()
    while time.time() - debut < patience:
        if not chemin.exists():
            return False
        taille = chemin.stat().st_size
        if taille == taille_prec:
            return True
        taille_prec = taille
        time.sleep(intervalle)
    return False

class TrieurFichiers(FileSystemEventHandler):
    def on_created(self, event):
        if event.is_directory:
            return
        chemin = Path(event.src_path)
        cible = DOSSIERS.get(chemin.suffix.lower())
        if cible is None:
            return
        attendre_fichier_complet(chemin)
        dossier = chemin.parent / cible
        dossier.mkdir(exist_ok=True)
        shutil.move(str(chemin), str(dossier / chemin.name))
        print(f"Deplace : {chemin.name} -> {cible}/")

def main():
    dossier = Path.home() / "inbox"
    dossier.mkdir(exist_ok=True)
    observateur = Observer()
    observateur.schedule(TrieurFichiers(), str(dossier), recursive=False)
    observateur.start()
    print(f"Surveillance de {dossier} ...")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observateur.stop()
    observateur.join()

if __name__ == "__main__":
    main()

Dépose un CSV, un PNG et un PDF dans inbox : chacun atterrit dans le bon sous-dossier sans que tu lèves le petit doigt.

5. Le piège du fichier à moitié écrit

Sur un gros fichier, on_created peut se déclencher avant la fin de l'écriture. Lis-le trop tôt et tu récupères une version tronquée. La parade : attendre que sa taille se stabilise. C'est le rôle de la fonction attendre_fichier_complet glissée dans le script précédent : elle compare la taille deux fois de suite et rend la main quand plus rien ne bouge. La patience (5 secondes par défaut) évite de bloquer éternellement sur un transfert interrompu.

6. Filtrer avant de réagir

Inutile de traiter tous les fichiers qui passent. PatternMatchingEventHandler restreint la surveillance à certains motifs, et RegexMatchingEventHandler va encore plus loin avec des expressions régulières. Tu gagnes en clarté et tu évites de déclencher tout un pipeline sur un fichier temporaire.

python
from watchdog.events import PatternMatchingEventHandler

class TrieurFichiers(PatternMatchingEventHandler):
    patterns = ["*.csv", "*.xlsx", "*.jpg", "*.png", "*.pdf"]

    def on_created(self, event):
        chemin = Path(event.src_path)
        # ... même logique de tri que dans l'étape 4 ...

Remplace FileSystemEventHandler par PatternMatchingEventHandler, définis patterns, et ton handler n'est appelé que si le nom du fichier correspond. Les extensions non listées sont simplement ignorées.

Pour surveiller aussi les sous-dossiers, passe recursive=True à observateur.schedule. Prudence : si ton handler déplace un fichier vers un sous-dossier lui-même surveillé, tu risques de déclencher on_created en boucle. Garde un dossier d'entrée plat tant que tu n'as pas besoin du contraire.

Enfin, protège ta réaction : enveloppe la logique de on_created dans un try/except. Si le traitement d'un fichier plante, l'Observer continue de tourner et les événements suivants sont traités normalement. Une exception non attrapée ne fait pas tomber la boucle de surveillance, mais mieux vaut logger l'erreur plutôt que de la laisser passer en silence.

7. Des idées pour brancher l'IA dessus

Une fois le mécanisme en place, la réaction devient un simple appel de fonction. Quelques pistes concrètes : lancer une reconnaissance de texte sur chaque image qui arrive (pytesseract), nourrir un modèle sur chaque nouveau CSV (Pandas), indexer chaque PDF dans une base vectorielle (ChromaDB), ou notifier un canal via un bot Telegram. Watchdog ne fait qu'une chose, mais il la fait au bon moment : te prévenir pile quand un fichier est prêt.

Watchdog remplace avantageusement la boucle while True qui scanne un dossier en continu : moins de CPU, une latence quasi nulle et un code plus lisible. Le squelette tient en vingt lignes et se déploie partout où un fichier qui tombe doit déclencher un travail. Branche ton traitement dedans, et ton dossier devient une vraie boîte de réception automatique.

K

Auteur

Krugz

Développeur fullstack

Développeur fullstack qui tente, tant bien que mal, de suivre l'actualité de l'IA au milieu d'une marée d'informations imbuvables et des milliers d'outils et de modèles qui sortent chaque jour.