Examples#

This page showcases various complete, runnable examples demonstrating different features of the watchdog API.

Pattern Matching Filter#

To filter file system events using glob patterns (for example, only observing changes to .py or .pyc files), you can use the watchdog.events.PatternMatchingEventHandler:

 1import logging
 2import sys
 3import time
 4
 5from watchdog.events import FileSystemEvent, PatternMatchingEventHandler
 6from watchdog.observers import Observer
 7
 8logging.basicConfig(level=logging.DEBUG)
 9
10
11class MyEventHandler(PatternMatchingEventHandler):
12    def on_any_event(self, event: FileSystemEvent) -> None:
13        logging.debug(event)
14
15
16event_handler = MyEventHandler(
17    patterns=["**/*.py", "**/*.pyc"], ignore_patterns=["version.py"], ignore_directories=True
18)
19observer = Observer()
20observer.schedule(event_handler, sys.argv[1], recursive=True)
21observer.start()
22try:
23    while True:
24        time.sleep(1)
25finally:
26    observer.stop()
27    observer.join()

Logging Trick#

Watchdog also includes built-in “tricks” (pre-implemented event handlers). For instance, the watchdog.tricks.LoggerTrick automatically logs events. Here is how you can use it:

 1import sys
 2import time
 3
 4from watchdog.observers import Observer
 5from watchdog.tricks import LoggerTrick
 6
 7event_handler = LoggerTrick()
 8observer = Observer()
 9observer.schedule(event_handler, sys.argv[1], recursive=True)
10observer.start()
11try:
12    while True:
13        time.sleep(1)
14finally:
15    observer.stop()
16    observer.join()

Debouncing Events#

Text editors and build tools can emit several events for a single logical change. Use watchdog.utils.event_debouncer.EventDebouncer to collect a burst of events and run an expensive action once the watched directory has been quiet for a short interval:

 1from __future__ import annotations
 2
 3import logging
 4import sys
 5import time
 6
 7from watchdog.events import FileSystemEvent, FileSystemEventHandler
 8from watchdog.observers import Observer
 9from watchdog.utils.event_debouncer import EventDebouncer
10
11logging.basicConfig(level=logging.INFO)
12
13
14def rebuild(events: list[FileSystemEvent]) -> None:
15    """Run an expensive action once after a burst of file changes."""
16    logging.info("Rebuilding after %d filesystem events", len(events))
17
18
19class DebouncedEventHandler(FileSystemEventHandler):
20    def __init__(self, debouncer: EventDebouncer) -> None:
21        self.debouncer = debouncer
22
23    def on_any_event(self, event: FileSystemEvent) -> None:
24        if not event.is_directory:
25            self.debouncer.handle_event(event)
26
27
28path = sys.argv[1] if len(sys.argv) > 1 else "."
29
30debouncer = EventDebouncer(debounce_interval_seconds=1, events_callback=rebuild)
31event_handler = DebouncedEventHandler(debouncer)
32observer = Observer()
33observer.schedule(event_handler, path, recursive=True)
34
35debouncer.start()
36observer.start()
37try:
38    while True:
39        time.sleep(1)
40finally:
41    observer.stop()
42    debouncer.stop()
43    observer.join()
44    debouncer.join()

Organizing Files by Type#

A common task is to keep a busy directory (such as a Downloads folder) tidy by automatically moving newly created files into category subfolders based on their file extension:

 1"""Organize files in a watched directory into category subfolders.
 2
 3When a new file appears in the watched directory it is immediately moved
 4into a subfolder based on its file extension (for example ``.png`` files
 5are moved into ``images/`` and ``.mp4`` files into ``videos/``).
 6
 7Usage::
 8
 9    python file_organizer.py [path]
10
11The directory to watch defaults to the current directory.
12"""
13
14from __future__ import annotations
15
16import logging
17import shutil
18import sys
19import time
20from pathlib import Path
21
22from watchdog.events import FileSystemEvent, FileSystemEventHandler
23from watchdog.observers import Observer
24
25logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s", datefmt="%H:%M:%S")
26
27#: Map a file extension to the subfolder it should be moved into.
28CATEGORIES = {
29    ".gif": "images",
30    ".jpeg": "images",
31    ".jpg": "images",
32    ".png": "images",
33    ".mkv": "videos",
34    ".mov": "videos",
35    ".mp4": "videos",
36    ".docx": "documents",
37    ".pdf": "documents",
38    ".txt": "documents",
39}
40
41
42class FileOrganizerHandler(FileSystemEventHandler):
43    def __init__(self, base_dir: Path) -> None:
44        self.base_dir = base_dir
45
46    def on_created(self, event: FileSystemEvent) -> None:
47        if event.is_directory:
48            return
49        source = Path(event.src_path)
50        category = CATEGORIES.get(source.suffix.lower(), "misc")
51        target_dir = self.base_dir / category
52        target_dir.mkdir(exist_ok=True)
53        target = target_dir / source.name
54        counter = 1
55        while target.exists():
56            target = target_dir / f"{source.stem}_{counter}{source.suffix}"
57            counter += 1
58        shutil.move(str(source), str(target))
59        logging.info("Moved %s -> %s", source.name, target)
60
61
62path = sys.argv[1] if len(sys.argv) > 1 else "."
63base_dir = Path(path).resolve()
64
65event_handler = FileOrganizerHandler(base_dir)
66observer = Observer()
67observer.schedule(event_handler, str(base_dir), recursive=False)
68observer.start()
69try:
70    while True:
71        time.sleep(1)
72finally:
73    observer.stop()
74    observer.join()