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:
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.
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.
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?“.
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.
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
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 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
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)
Zusätzlich zum FileSystemEventHandler gibt es auch noch 3 weitere Handler:
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)
Die Performance von WatchDog kann optimiert werden durch verschiedenste Schritte.
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.schedule
brauchst du diese Wirkliche? Oder ist es ein unnötiger Performance-Faktor.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()
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.
Hinterlasse mir gerne einen Kommentar zum Artikel und wie er dir weitergeholfen hat beziehungsweise, was dir helfen würde das Thema besser zu verstehen. Oder hast du einen Fehler entdeckt, den ich korrigieren sollte? Schreibe mir auch dazu gerne ein Feedback!
Es sind noch keine Kommentare vorhanden? Sei der/die Erste und verfasse einen Kommentar zum Artikel "Python WatchDog: Effiziente Dateisystemüberwachung"!