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()