Lerne Coding
Python WatchDog: Effiziente Dateisystemüberwachung
21.05.2023

Dateisystemüberwachung mittels Python WatchDog

Inhaltsverzeichnis
[[TABLE OF CONTENTS]]
access_timeGeschätzte Lesezeit ca. Minuten

Dateisystemüberwachung ist bei Automatisierungen mit Python eine sehr interessante Aufgabe, mit der sich viel erledigen lässt. Von Überwachung von Logfiles, bis automatischen Sicherungen ist vieles möglich. Dafür gibt es ein bekanntes Python-Modul, was dich unterstützen kann, solche Automatisierungen zu programmieren WatchDog (in Deutsch so viel wie Wachhund).

Zum Beispiel ist eine Dateisystemüberwachung interessant für:

  • Automatisierte Sicherung von Dateien in Ordnern – als Beispiel du hast einen Ordner Backups, jede Datei, die dort abgelegt wird, soll automatisch auf einen Server mittels SFTP, FTP oder SCP hochgeladen werden.
  • Automatisierte Archivierung, als Beispiel deine Downloads Ordner ist immer voll und du hättest es lieber, dass die Dateien in Ordnern je nach Monat/Jahren sortiert werden. Das lässt sich mit einer Dateisystemüberwachung sehr einfach realisieren.
  • Du hast eine Anwendung, auf deinem Server, die zur Überwachung keine Schnittstellen bereitstellt? Dann könnte eine Lösung sein, die Logfiles bei einer Veränderung zu analysieren.
  • Aufräumen macht nie Spaß, wie wäre es, wenn basierend auf definierten Faktoren dein Desktop Ordner immer aufgeräumt wird, wenn eine neue Datei dort abgelegt wird.

Was ist ein File Observer?

Bei dem Thema Dateisystemüberwachung wirst du schnell über den Begriff File Observer stolpern. Die Hauptessenz ist der Observer zu Deutsch der Beobachter. Wie der Name schon sagt, beobachtet er etwas. Im Fall von einem File Observer wird ein Verzeichnis gescannt in einem sehr kurzen Intervall auf jegliche Veränderungen, das kann das Löschen von Dateien und Ordnern sein oder auch die Veränderung, Erstellung. Oder Alternative wird auf System Events gehört, die vorgeben, ob eine Änderung erfolgt ist. Weshalb von einem EventListener gesprochen werden kann. WatchDog ist also primäre ein Observer von File Events.

Wie funktioniert WatchDog in Python?

WatchDog ist ein FileObserver Modul für Python – dieses arbeitet unter der Haube mit verschiedenen Systemschnittstellen und erlaubt so eine Unterstützung für verschiedenste Betriebssysteme. Es werden Windows, macOS (Darwin), FreeBSD, Linux unterstützt, des Weiteren gibt es, eine unabhängige Methode um auf Events zu horchen.

Folgende Systeme werden unter den unterschiedlichsten Betriebssystemen genutzt, um auf die Events zu hören: Unter Linux (2.6) wird das inotify-System eingesetzt, für das Hören auf Erstellen, Löschen und Ändern. Unter macOS wird das Framework FSEvents verwendet, das Dateisystemänderungen bereitstellt. Zusätzlich kommt unter macOS und FreeBSD noch kqueue zum Einsatz, diese ermöglicht das Überwachen von Änderungen im Dateisystem. Bei Windows wird die Funktion ReadDirectoryChangesW in Verbindung mit I/O-Completion-Ports verwendet, um Dateisystemänderungen zu überwachen. Dabei werden Threads zur Verarbeitung der Änderungen eingesetzt.

Zusätzlich da diese Technologien noch nicht ausreichen wird auch noch eine Methode unterstützt, bei der Verzeichnisschnappschüssen abgefragt werden und vergleichen werden, diese Methode ist allerdings langsam und wird von den Entwicklern nicht empfohlen.

Im Allgemeinen kann man sagen durch diese vielen Schnittstellen, die in diesem Modul implementiert wurden, ist es für einen Nutzer sehr einfach einen sicheren File Watcher zu implementieren und zu verwenden.

Installation von Python WatchDog mit PIP

Nachdem du die Details kennengelernt hast, was ein FileObserver ist und wie WatchDog arbeitet. Möchten wir die Installation mittels PIP betrachten, letztlich muss nur der folgende Befehl ausgeführt werden:

pip install watchdog

Falls du mehr Information über die Paketverwaltung PIP benötigst, schau doch einmal in den Artikel „Wie du PIP Installieren und Verwenden kannst?“.

Erstes Code Beispiel für die Verwendung von WatchDog

Nach der Installation und den ersten Erklärungen möchten wir uns jetzt das erste Codebeispiel anschauen. Für unsere erstes Codebeispiel wollen wir uns einfach nur ausgeben, wenn eine Datei oder ein Ordner modifiziert oder erstellt wurde. Und zwar gibt es ein FileSystemEventHandler dieser implementiert das Handling für eingehende Events und gibt diese an spezifischere Methoden weiter, zu allen Methoden unten stehend weitere Informationen. Im Beispiel vererben wir den FileSystemEventHandler an unsere eigene Handler Klasse, um die für uns relevanten Methoden zu überschreiben, um eine Logik zu implementieren. Im ersten Schritt werden wir uns das ganze nur in die Konsole ausgeben. Spätere werden wir noch ein kleines Projekt mit WatchDog lösen.

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class Handler(FileSystemEventHandler):

    def on_created(self, event):
        print(event)

    def on_modified(self, event):
        print(event)

if __name__ == "__main__":
    path = "."
    event_handler = Handler()

    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    observer.start()

    try:
        while observer.is_alive():
            observer.join(1)
    except KeyboardInterrupt:
        print("Observer stopping")
    finally:
        observer.stop()
        observer.join()

Achte beim Pfade, den du mittels schedule überwachst, ob es wirklich der notwendige Pfad ist und nicht zu viele Verzeichnisse überwacht werden.

Auf verschiedene Ereignisse reagieren – FileSystemEventHandler

Es gibt insgesamt 5 Ereignisse, die wir verwenden können, wenn wir auf Events hören wollen, mittels dem FileSystemEventHandler. Zusätzlich hat dieser noch eine Methode die Überschrieben werden kann, wenn wir die Events an andere Methoden verteilen wollen. Wichtig, alle Methoden werden als Teil eines Handlers verwendet, nach dem folgenden Beispiel.

class Handler(FileSystemEventHandler):
        def on_*****(self, event):
                pass

Eine Datei wurde editiert – Created, Modified, Deleted Event

Die Modified Methode wird bei jeglicher Veränderung abgerufen. Zum Beispiel bei einer Erstellung einer Datei wird auch immer das Modified Event für das Verzeichnis abgerufen.

Die Create und Delete Methode werden bei entsprechender Datei und Verzeichnis Erstellungen und Löschungen abgerufen. Alle Drei Methoden haben dieselben Values zur Verfügung, der Body der Print-Ausgabe funktioniert also gleichermaßen für alle.

def on_created(self, event): ...
def on_modified(self, event): ...
def on_delted(self, event): ...
        print(event) # <DirModifiedEvent: event_type=modified, src_path='/Users/felixschuermeyer/Coding/watchDogTest', is_directory=True>
        print(event.src_path) # /Users/felixschuermeyer/Coding/watchDogTest
        print(event.is_directory) # True
        print(event.event_type) # modified

Eine Datei wurde bewegt – Moved Event

Eine Datei oder ein Verzeichnis, das verschoben wird, löst ein Moved Event aus. Dabei ist zu beachten, dass wir den Scope des Watchers definieren müssen, wenn du eine Datei oder ein Verzeichnis in den Scope verschiebst, löst das Event nicht aus, dann greift das Erstellung-Event. Heißt, du kannst nur Verschiebungen innerhalb des eigenen Scopes erkennen. Zusätzlich zu den anderen Angaben erhältst du Ziel und Ausgangspunkt der Datei (src_path, dest_path).

def on_moved(self, event):
        print(event) # <FileMovedEvent: src_path='/Users/felixschuermeyer/Coding/watchDogTest/hallo.welt', dest_path='/Users/felixschuermeyer/Coding/watchDogTest/Test/hallo.welt', is_directory=False>
        print(event.src_path) # /Users/felixschuermeyer/Coding/watchDogTest/hallo.welt
        print(event.dest_path) # /Users/felixschuermeyer/Coding/watchDogTest/Test/hallo.welt
        print(event.is_directory) # False
        print(event.event_type) # moved

Alle Events Beachten – Any Event

Zusätzlich zu den definierten Events, werden alle Events auch an die on_any_event Methode weitergereicht, diese hat letztlich dieselben Parameter wie die Vorgänger Methoden mit dem Unterschied. Die dispatch Methode des FileSystemEventHandler löst diese Methode immer vor allen anderen aus.

def on_any_event(self, event):
        print(event)
        print(event.src_path)
        print(event.is_directory)
        print(event.event_type)
        if hasattr(event, 'dest_path'):
            print(event.dest_path)

Konfigurationsoptionen von WatchDog in Python

Zusätzlich zum FileSystemEventHandler gibt es auch noch 3 weitere Handler:

  • PatternMatchingEventHandler
    • Es können Patterns definiert werden, um zum Beispiel auf verschiedene Dateitypen zu hören. Es können Verzeichnisse ignoriert werden.
  • RegexMatchingEventHandler
    • Selbiges wie auch bei den Pattern mit dem Unterschied, dass Regex mächtiger ist und du auf spezifische Dateiformate viel besser hören kannst. Etwa das Format soll mit 3 Buchstaben beginnen, danach müssen 5 Zahlen folgen und die Dateiendung muss JPG sein, mit einem Pattern könntest du das nicht so explizit prüfen.
  • LoggingEventHandler
    • Dieser ist als erstes Beispiel und Debugging gedacht, da dieser nur ein Log schreibt, aber nicht ermöglicht etwas Hilfreiches direkt zu implementieren.

Primäre ergänzen die Matching Klassen die dispatch Methode vom FileSystemEventHandler um eigene Logiken für das Matching. Alle standardmäßig vorhanden on_*** Methoden sind weiterhin verfügbar und es kann genau so der PatternMatchingEventHandler vererbt werden wie der FileSystemEventHandler. Mit dem Vorteil, dass die Konfigurationsoptionen fürs Matching direkt vorhanden sind.

Ein EventHandler der nur auf bestimmte Bildformate hört und Verzeichnisse ausschließt, könnte zum Beispiel so definiert werden. Aber Achtung beim Matching wird nur auf die Dateinamen geachtet, eine zuverlässige Methode, um Bildformate zu erfassen ist das nicht, dafür muss der Mime Type und auch der Dateiinhalt geprüft werden, um wirklich sichergehen zu können. Diese ließe sich zum Beispiel mit einer eigenen dispatch Methode und einem eigenen Handler Realisieren. Aber auch hier gilt es wieder aufpassen, da das Lesen von Dateien in die Performance gehen kann.

event_handler = PatternMatchingEventHandler(
        patterns=["*.jpg","*.png","*.jpeg","*.bmp"], 
        ignore_directories=True)

Tipps für die Performance

Die Performance von WatchDog kann optimiert werden durch verschiedenste Schritte.

  • Füge, ein Pattern Matching ein, um frühzeitig bereits abzubrechen bei ungeeigneten Dateien.
  • Sammel alle Events für 10 Sekunden und verarbeite die Events in Batches, um nicht dauerhaft die Rechenleistung zu beanspruchen.
  • Begrenze im schedule den zu überprüfenden Pfad, um so kleiner du den Scope der Überprüfung setzt, um so geringer wird die Performance Auslastung. Falls du zum Beispiel 2 Ordner überprüfen willst, die in einem Hauptordner liegen, ist es ggf. eine Überlegung trotzdem 2 getrennte Observer zu starten, wenn im Hauptordner auch noch viel passiert, dass du vorher rausfiltern musst.
  • Achte auf die Verwendung von Rekursive im schedule brauchst du diese Wirkliche? Oder ist es ein unnötiger Performance-Faktor.

Beispielprojekt: Den Downloads Ordner aufräumen

Als kleines Beispiel wollen wir den Downloads Ordner etwas aufräumen, wobei das Beispiel absolute keinen Anspruch auf Vollständigkeit Besitz, mit der mime Type Prüfung und dem Dateiinhalt lassen sich die Dateien deutlich besser einordnen. Hier geht es darum, einen kleinen praktischen Ausblick zu geben, was sich mit WatchDog realisieren lässt. Je nach Betriebssystem müssen ggf. auch noch temporäre Dateien ausgeschlossen werden, damit erst einmal der Download abgeschlossen werden kann.

In unserem Beispiel haben wir eine Methode „get_folder_name“, die basierend auf der Dateiendung Ordner zuweist. Mit der on_created Methode prüfen wir, ob es sich um eine Datei handelt, dabei werden Verzeichnisse ausgeschlossen, weil wir diese nicht benötigen, da ein Download von Ordnern ohne Archive Format nicht möglich ist. So exestieren auf der obersten Ebene nur unsere Verzeichnisse, die unsere Kategorien wie Bilder, Text, Archive repräsentieren.

Nachdem wir den Ordnernamen ermittelt haben, können wir den Ordner erstellen, falls dieser noch nicht existiert. Anschließenden verschieben wir nur noch mittels shutil die Datei von A nach B. Wenn wir nun den WatchDog Watcher am Laufen haben und neue Dateien im Downloads Ordner erstellt werden, werden diese direkt in den passenden Ordner verschoben.

Alternative zu der Kategoriesierung nach Dateityp könntest du auch mal versuchen, basierend auf dem aktuellen Jahr und Monat ein Ordner zu erstellen und die Dateien direkt korrekt abzulegen. Oder du kannst dich daran versuchen den Dateityp noch besser mittels mimeType zu bestimmen.

import os
import shutil
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class DownloadHandler(FileSystemEventHandler):

    def get_folder_name(self, path):
        _, file_extension = os.path.splitext(path)

        file_extension = file_extension.replace('.','')

        extension_mapping = {
            'jpg': 'image',
            'webp': 'image',
            'bmp': 'image',
            'svg': 'image',
            'txt': 'text',
            'pdf': 'pdf',
            'csv': 'data',
            'zip': 'archive'
        }

        folder_name = extension_mapping.get(file_extension, 'other')

        return folder_name

    def move_file_to_folder(self, path):
        base = './downloads/'

        # Ordner Namen basierend auf der Dateiendung Ermitteln
        foldername = self.get_folder_name(path)
        target_folder = base + foldername
        filename = os.path.basename(path)

        # Erstellen vom Ordner falls dieser noch nicht Exestiert
        if not os.path.exists(target_folder):
            os.mkdir(target_folder)

        target = os.path.join(target_folder, filename)

        # Verschieben der Datei in das Richtige Verzeichniss
        shutil.move(path, target)

    def on_created(self, event):
        if not event.is_directory:
            self.move_file_to_folder(event.src_path)

if __name__ == "__main__":
    observer = Observer()
    observer.schedule(DownloadHandler(), './downloads/', recursive=False)
    observer.start()

    try:
        while observer.is_alive():
            observer.join(1)
    except KeyboardInterrupt:
        print("Observer stopping")
    finally:
        observer.stop()
        observer.join()

Zusammenfassung

Zusammenfassend kann gesagt werden, mittels Erstellungs-, Veränderungs-, Löschungs- und Verschiebungs-Event lässt sich vieles in Python erreichen, wenn es darum geht dynamisch Inhalte im Dateisystem zu verändern. Mittels WatchDog hast du ein mächtiges Tool an der Hand, das dir die Arbeit vereinfacht diese zu realisieren.

Bildquelle - Vielen Dank an die Ersteller:innen für dieses Bild
Kommentare zum Artikel

Es sind noch keine Kommentare vorhanden? Sei der/die Erste und verfasse einen Kommentar zum Artikel "Python WatchDog: Effiziente Dateisystemüberwachung"!

Kommentar schreiben

Verwante Beiträge
close