Python Watchdog Created, Modified, and Deleted Events
python watchdog created modified deleted events: Learn how to handle created, modified, and deleted file events with Python's watchdog library, including event objects...
Working with python watchdog created modified deleted events means implementing a FileSystemEventHandler subclass and registering it with an Observer. The three callback methods — on_created, on_modified, and on_deleted — receive event objects that describe what changed and where. The sections below cover the setup, the event objects, and the runtime behavior that trips up most developers.
Setting Up Watchdog to Observe File Changes
The watchdog library exposes two main pieces: an Observer that runs a background thread and watches a directory, and an event handler that receives callbacks when the file system changes. To react to created, modified, and deleted events, you subclass FileSystemEventHandler and override the methods that match the events you care about.
from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class ChangeHandler(FileSystemEventHandler): def on_created(self, event): print(f"created: {event.src_path}") def on_modified(self, event): print(f"modified: {event.src_path}") def on_deleted(self, event): print(f"deleted: {event.src_path}") observer = Observer() observer.schedule(ChangeHandler(), path="/path/to/watch", recursive=True) observer.start()
The schedule method registers the handler for a specific path. The recursive=True argument tells the observer to watch subdirectories as well; without it, only the top-level directory is observed. The observer.start() call launches a daemon thread that dispatches events to your handler. In a long-running process you do not need to join the observer unless you want the main thread to block until it is stopped.
The Event Handler Methods for Created, Modified, and Deleted Events
FileSystemEventHandler defines a set of methods that watchdog calls when the underlying file system reports a change. The three that correspond directly to created, modified, and deleted events are on_created, on_modified, and on_deleted.
Each method receives a single event object as its argument. You do not need to override all three. If your use case only cares about new files appearing, override on_created and leave the others alone; the base implementation of the other methods does nothing.
The methods are called for both files and directories. A directory creation triggers on_created just like a file creation does, and the event object carries an is_directory flag that lets you tell them apart.
Event Objects: What Each Callback Receives
Watchdog does not pass a single generic event to every callback. It creates a specific event subclass depending on what happened and whether the target is a file or a directory.
| Event class | Handler method | Trigger |
|---|---|---|
| FileCreatedEvent | on_created | A file was created |
| FileModifiedEvent | on_modified | A file's contents or metadata changed |
| FileDeletedEvent | on_deleted | A file was removed |
| DirectoryCreatedEvent | on_created | A directory was created |
| DirectoryModifiedEvent | on_modified | A directory's contents changed (files added or removed inside it) |
| DirectoryDeletedEvent | on_deleted | A directory was removed |
All of these inherit from FileSystemEvent, which provides two attributes you will use constantly: src_path, the absolute path of the affected file or directory, and is_directory, a boolean that tells you whether the event concerns a directory.
Because watchdog dispatches on the event type, you can inspect the event object directly:
def on_created(self, event): if event.is_directory: print(f"directory created: {event.src_path}") else: print(f"file created: {event.src_path}")
The src_path is always an absolute path on the platform you are running on. On Windows it uses backslashes; on Linux and macOS it uses forward slashes. If you need to do path manipulation, pass the value through pathlib.Path rather than assuming a separator.
Distinguishing File Events from Directory Events
The is_directory flag is the primary way to filter events. It matters because directory events are often noisier than file events. When a file is created inside a watched directory, you typically receive two events: a FileCreatedEvent for the file and a DirectoryModifiedEvent for the parent directory, because the directory's contents changed. If your handler reacts to every on_modified call, you will see both the file's modification and the directory's modification.
For most file-processing use cases, you want to ignore directory events entirely:
def on_created(self, event): if event.is_directory: return process_file(event.src_path)
This keeps the handler focused on actual files and avoids double-processing when a directory entry changes.
Why Modified Events Fire More Than Once
A common surprise when working with on_modified is that a single save operation in an editor produces multiple modification events. This is not a watchdog bug; it reflects how applications write files. Many editors write to a temporary file and then rename it into place, or write the file in several chunks. Each of those writes can generate a separate modification event.
For example, saving a file in VS Code can produce a FileModifiedEvent, a FileCreatedEvent for a temporary file, and a FileDeletedEvent when the temporary file is removed, depending on the editor's write strategy. Vim and Emacs behave differently again because of their swap and backup files.
If your handler performs work on every modification, you may process the same logical change several times. One approach is to debounce: record the last time you processed a path, and ignore events that arrive within a short window.
import time class DebouncedHandler(FileSystemEventHandler): def __init__(self, debounce_seconds=1.0): self.debounce_seconds = debounce_seconds self._last_processed = {} def on_modified(self, event): if event.is_directory: return now = time.monotonic() last = self._last_processed.get(event.src_path, 0) if now - last < self.debounce_seconds: return self._last_processed[event.src_path] = now process_file(event.src_path)
This does not eliminate duplicate events; it collapses bursts of events into a single action. The right debounce window depends on how your editor writes files and how much latency your use case tolerates.
Keeping the Event Handler Responsive
The event handler runs on the observer's dispatch thread. If on_created or on_modified performs slow work, such as a network request, a database write, or a large file copy, every subsequent event waits until that work finishes. Watchdog dispatches events sequentially, so a blocking handler stalls the entire observation pipeline.
For handlers that do anything beyond lightweight work, push the work onto a queue and process it in a separate worker thread:
import queue import threading event_queue = queue.Queue() class QueuedHandler(FileSystemEventHandler): def on_created(self, event): if not event.is_directory: event_queue.put(event.src_path) def worker(): while True: path = event_queue.get() process_file(path) threading.Thread(target=worker, daemon=True).start()
This decouples event delivery from event processing. The observer thread stays free to dispatch new events, and the worker thread can take as long as it needs without blocking file system observation.
Choosing Between Recursive and Non-Recursive Observation
The recursive argument to schedule controls whether subdirectories are watched. With recursive=True, creating a file in a nested subdirectory triggers on_created with the full path. With recursive=False, only direct children of the watched directory produce events.
Recursive observation is convenient but produces more events, because directory modifications inside the tree also fire. If you only care about a flat directory, such as an upload folder where files are written directly, non-recursive observation reduces noise and avoids processing events from unrelated subdirectories.
There is also a practical cost to recursive observation on large directory trees: the observer must track more paths and receives more events, which increases dispatch overhead. For a directory with thousands of files, weigh whether you actually need to react to changes in every subdirectory before enabling recursion.