Réagir à un changement sur un fichier avec watchdog


Il y a des tas de choses qu’on peut vouloir faire au moment où un fichier change :

  • Faire un backup;
  • Lancer les tests unittaires;
  • Démarrer un build;
  • Recharger un contenu;
  • Envoyer un email;
  • Afficher une notification;
  • Mettre à jour un listing.

Et ce n’est même pas dur à faire en Python grace à la lib watchdog :

pip install watchdog

D’abord, on créer un handler, une classe qui va contenir le code à lancer quand il arrive quelque chose à nos fichiers :

from watchdog.events import FileSystemEventHandler
 
class MonHandler(FileSystemEventHandler):
 
    # cette méthode sera appelée à chaque fois qu'un fichier
    # est modifié
    def on_modified(self, event):
        print("Ah, le fichier %s a été modifé" % event.src_path)
 
    # On peut aussi implémenter les méthodes suivantes :
    # - on_any_event(self, event)
    # - on_moved(self, event)
    # - on_created(self, event)
    # - on_deleted(self, event)
    # - on_modified(self, event)

Ensuite on créé un observer, qui va lancer un thread dans lequel il va… observer :

from watchdog.observers import Observer
 
observer = Observer()
# Surveiller récursivement tous les événements du dossier /tmp
# et appeler les méthodes de MonHandler quand quelque chose
# se produit
observer.schedule(MonHandler(), path='/tmp', recursive=True)

On démarre tout ça :

import time
observer.start()
 
# L'observer travaille dans un thread séparé donc on fait une 
# boucle infinie pour maintenir le thread principal
# actif dans cette démo mais dans un vrai programme,
# vous mettez votre taff ici.
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    # Ctrl + C arrête tout
    observer.stop()
# on attend que tous les threads se terminent proprement
observer.join()

Pour les ones shot, watchdog vient avec la commande watchmedo qui permet de lancer un commande shell en cas d’événement :

# lancer 'echo lefichier' pour chaque fichier python ou texte modifié de mon dossier utilisateur 
watchmedo shell-command --patterns="*.py;*.txt" --recursive --command='echo "${watch_src_path}"' /home/sam

Personnellement j’aime le lundi.


Télécharger le code de l’article

15 thoughts on “Réagir à un changement sur un fichier avec watchdog

  • Marc

    Très pratique, je l’utilise dans un cadre professionnel.

    Attention cependant :

    – étant multi-plateformes, son comportement est beaucoup influencé par le système d’exploitation (par exemple la copie d’un fichier sous Windows entraîne une création puis une modification)

    – il ne garantie en rien la bonne complétion de vos fichiers, il lèvera un événement au premier octet écrit, c’est à vous de contrôler le fichier en amont avant de le traiter.

    On l’a utilisé dans tous les sens, donc si besoin => indexerror

  • khamaileon

    Est-ce que Watchdog est la meilleure solution pour lancer des tests unitaires dans un projet Django ?

  • François

    J’utilisais watchdog pour faire des tests d’intégration lors de l’écriture de ma thèse en latex. A chaque sauvegarde, ça lancait make et récupérait le valeur de retour. Si xelatex ne compilait pas, il m’affichait une pop-up. Un sacré gain de temps, car on trouve plus rapidement une errreur de syntaxe si elle se trouve deux lignes au dessus.

    • Paradox

      Tu peux expliciter un peu ta méthode ? Je ne suis pas sûr de voir comment tu la mettais en pratique.

  • Sam Post author

    @khamaileon : je n’ai aucune idée de ce que signifie “meilleure solution”.

    @Paradox : il n’y a rien à faire, la méthode est appelée automatiquement à chaque fois qu’un fichier est modifié. event.src_path contient le chemin du fichier modifié, tu fais ensuite ce que bon te semble avec.

  • ThomasG77

    Si “Linux only”, quel est l’intérêt par rapport à son cousin Pyinotify https://github.com/seb-m/pyinotify?

    Je connais Watchdog comme Pyinotify mais j’ai plutôt utilisé Pyinotify et je vois pas trop la différence sauf l’aspect multi-plateforme.

    • Sam Post author

      @ThomasG77: c’est pareil. La doc de watchdog est un peu mieux, et comme tu le dis, watchdog est crossplateform. Sinon c’est kiffkiff.

  • Kikoololmdr

    Et un petit code en exemple pour avoir la notification des modifications de fichiers entre le moment où le programme s’est arrêté et où il a été redémarré :

    https://gist.github.com/serge-kilimoff/8163233

    Je l’ai écrit il y a un moment, je ne sais pas si ça fonctionne toujours, c’est plutôt considérer comme une base de code pour un truc plus poussé (de mémoire, je l’avais fais pour reconstruire automatiquement du css avec clevercss).

  • Sam Post author

    Sympas ! Mais faut pas avoir trop de fichiers j’imagine sinon ça doit ramouiller.

  • khamaileon

    @Sam @LeMeteore

    Je me suis mal exprimé. Par meilleure solution j’entendais facile à mettre en place, rapide d’exécution, solution éprouvée, documentée, …

    @LeMeteore

    On utiliser aussi Grunt et Gulp pour le front. Je cherche donc à trouver un équivalent Python.

    Merci à @Sam pour le post

  • Sam Post author

    @khamaileon: question trop vague, on ne connait pas tes besoins, les points de comparaison avec les autres solutions, les contraintes…. Mais d’une manière générale, watchdog est juste là pour faire la moitié du boulot : lancer un truc au changement d’un fichier. Pour du build, il faut aussi la notion de tache (comme par exemple le fait pydoit).

  • khamaileon

    @Sam: Ok c’est limpide pour moi maintenant. Au final je lance simplement mes tests à chaque modification de fichiers avec la commande watchmedo: watchmedo shell-command –patterns=”*.py” –recursive –command=’python manage.py test -v2′

    Thx

  • hiramash

    @Sam

    “Mais faut pas avoir trop de fichiers j’imagine sinon ça doit ramouiller.”

    En fait c’est beaucoup plus lourd que ça.

    J’ai utilisé “inotify” (c’était pas du Python à l’époque, mais j’attaquais en batch) et je l’ai recommandé à un collègue sysadmin. Enchanté, il l’a mis sur un serveur de gestion de mises à jour et un jour…le serveur s’est effondré car il y avait près de 2000 mises à jour simultanées.

    Il aurait fallu que les routines déclenchées appellent la mise en tampon avec “at” ; bref, mettre en file d’attente les tâches déclenchées par des événements…

    En outre, j’ai voulu m’en servir sur un serveur Samba, pour qu’il purge un répertoire sous certaines conditions, et me lance des “copy” et des “rsync” sous d’autres conditions. Las ! Inotify, ça ne MARCHE PAS sur un répertoire Windows…(normal, hein? Le système de fichiers Win ne fournissant pas les mêmes événements fichiers que sous Linux)

    Donc, le concept de portabilité sous Python, c’est pas un gadget, c’est une vraie bouée de sauvetage face aux lacunes des OS les uns par rapport aux autres. Autre exemple pour le troll : “virtualenv” et “fakeroot”…

    Vala, vala,

    Long life to Python !

  • Geoffrey OnRails

    Non seulement comme le dit Marc, “il ne garantie en rien la bonne complétion de vos fichiers, il lèvera un événement au premier octet écrit, c’est à vous de contrôler le fichier en amont avant de le traiter”, mais sur Windows c’est même pire que ça.

    “Changed – raised whenever changes are made to the size, system attributes, last write time, last access time, or security permissions of a directory or file. ” https://msdn.microsoft.com/en-us/library/ch2s8yd7%28v=vs.90%29.aspx

    Et plus loin dans la doc : “Time stamps are updated at various times and for various reasons. The only guarantee about a file time stamp is that the file time is correctly reflected when the handle that makes the change is closed. ” https://msdn.microsoft.com/en-us/library/ms724290%28VS.85%29.aspx

    Aucune garantie sur quand l’event va être levé, sur combien il va y en avoir, tout particulièrement lorsqu’il s’agit de grosses modifications de gros fichiers.

    En bref, c’est bien utile, mais il faut être bien conscient des limitations, et elles sont rarement indiquées clairement dans le doc, malheureusement.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.