This article presents a robust solution for monitoring directory changes in real-time using Python's inotify interface combined with multiprocessing to handle file events asynchronously.
The dirwatcher project provides a lightweight file system watcher that monitors a specified directory for changes and automatically triggers actions when file operations occur. Whether you need to process uploaded files, react to file deletions, or handle attribute changes, this solution offers a clean, efficient approach.
GitHub Repository: https://github.com/MourIdri/dirwatcher
Directory monitoring is essential for:
inotify is a Linux kernel subsystem that provides efficient file system event notification. Unlike polling-based approaches that continuously check directories, inotify delivers events immediately when changes occur.
The dirwatcher monitors these key events:
| Event | Meaning | Use Case |
|---|---|---|
IN_CLOSE_WRITE |
File finished writing and closed | File upload complete |
IN_DELETE |
File was deleted | Track file removal |
IN_ATTRIB |
File attributes changed | File ready for processing |
IN_CREATE |
New file created | Track new files |
IN_MODIFY |
File content modified | Detect file changes |
pip install inotify_simple
# or
pip install inotify-adapters
Before running the watcher, configure these settings in wa_dir.py:
# Path to monitor (change to your target directory)
path = "/mnt/c/code_source/GENERAL/HPC/PARA/PATHTEST/"
# Log file name (customize as needed)
fileLogName = 'LOG_directory_operations.log'
# Rotating log handler configuration
maxBytes = 1048576 * 5 # 5MB
backupCount = 7 # Keep 7 backup log files
Proper logging is critical for monitoring applications:
import logging
from logging.handlers import RotatingFileHandler
_DEFAULT_LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
_LOGGER = logging.getLogger(__name__)
def _configure_logging():
_LOGGER.setLevel(logging.INFO)
# Console handler
ch = logging.StreamHandler()
formatter = logging.Formatter(_DEFAULT_LOG_FORMAT)
ch.setFormatter(formatter)
# File handler with rotation
fileLogName = 'LOG_directory_operations.log'
fileHandler = logging.handlers.RotatingFileHandler(
fileLogName,
maxBytes=(1048576 * 5), # 5MB
backupCount=7
)
fileHandler.setFormatter(formatter)
_LOGGER.addHandler(ch)
_LOGGER.addHandler(fileHandler)
Why Rotating Logs?
The core event handler processes file system events:
def PopUpMessage(event):
filepartname = ".filepart"
filelaststate_writting = "IN_CLOSE_WRITE"
filelaststate_deleting = "IN_DELETE"
filelaststate_in_attrib = "IN_ATTRIB"
if event is not None:
(header, type_names, watch_path, filename) = event
_LOGGER.info(
"WD=(%d) MASK=(%d) COOKIE=(%d) LEN=(%d) MASK->NAMES=%s "
"WATCH-PATH=[%s] FILENAME=[%s]",
header.wd, header.mask, header.cookie, header.len, type_names,
watch_path.decode('utf-8'), filename.decode('utf-8')
)
time.sleep(0.5) # Small delay to ensure file operation completes
The watcher intelligently handles partial file uploads:
# File still uploading (contains .filepart extension)
if filepartname in filename:
_LOGGER.info(
'File is still uploading locally - analysis will be done '
'once the file is completely uploaded'
)
# File deletion detected
if filelaststate_deleting in type_names and filepartname not in filename:
_LOGGER.info('File is being deleted from the local repository')
# File attributes added - indicates upload completion
if filelaststate_in_attrib in type_names and filename:
_LOGGER.info('File completely uploaded locally - attributes added')
filepushedforwatsonanalysis = str(filename)
_LOGGER.info(
'File ready for processing: %s' % filepushedforwatsonanalysis
)
Multiprocessing enables non-blocking event handling:
def runInParallel(*fns):
"""Execute multiple functions in parallel processes"""
proc = []
for fn in fns:
p = multiprocessing.Process(target=fn)
p.start()
proc.append(p)
# Wait for all processes to complete
for p in proc:
p.join()
Why Multiprocessing?
def My_main(path):
# Initialize inotify
i = inotify.adapters.Inotify()
DirWatcher = i.add_watch(path)
try:
# Infinite loop watching for events
while True:
# Process each event in the queue
for event in i.event_gen():
# Handle event in separate process
m = multiprocessing.Process(
target=PopUpMessage,
args=(event,)
)
m.start()
finally:
# Cleanup: remove watch
i.remove_watch(b'/PARA')
if __name__ == '__main__':
_configure_logging()
path = "/mnt/c/code_source/GENERAL/HPC/PARA/PATHTEST/"
# Start watcher in main process
N = multiprocessing.Process(target=My_main, args=(path,))
N.start()
python wa_dir.py
The application will:
In another terminal, watch the log file in real-time:
tail -f LOG_directory_operations.log
Extend the watcher to monitor multiple paths:
def watch_multiple_directories():
paths = [
"/path/to/uploads/",
"/path/to/processed/",
"/path/to/archive/"
]
processes = []
for path in paths:
p = multiprocessing.Process(target=My_main, args=(path,))
p.start()
processes.append(p)
# Keep processes running
for p in processes:
p.join()
if __name__ == '__main__':
_configure_logging()
watch_multiple_directories()
Replace or extend PopUpMessage to trigger your own logic:
def process_file(filename):
"""Your custom file processing logic"""
print(f"Processing: {filename}")
# Add your business logic here
# Examples: resize images, extract metadata, validate formats, etc.
def PopUpMessage_Extended(event):
# ... existing code ...
if filelaststate_in_attrib in type_names and filename:
filename_str = filename.decode('utf-8')
_LOGGER.info(f'Processing file: {filename_str}')
process_file(filename_str)
def should_process_file(filename):
"""Filter files by extension"""
valid_extensions = ('.png', '.jpg', '.pdf', '.txt', '.csv')
filename_str = filename.decode('utf-8') if isinstance(filename, bytes) else filename
return any(filename_str.endswith(ext) for ext in valid_extensions)
def PopUpMessage_Filtered(event):
# ... existing code ...
if filelaststate_in_attrib in type_names and filename:
if should_process_file(filename):
_LOGGER.info(f'Processing valid file: {filename}')
# Trigger processing
else:
_LOGGER.info(f'Skipping invalid file: {filename}')
try:
m = multiprocessing.Process(target=PopUpMessage, args=(event,))
m.start()
except Exception as e:
_LOGGER.error(f'Error processing event: {str(e)}')
maxBytes and backupCount# For high-frequency events, consider:
# 1. Batch processing
event_queue = []
BATCH_SIZE = 10
def batch_process(queue):
while len(queue) < BATCH_SIZE:
# Wait for more events
pass
# Process batch together
# 2. Process pool instead of unlimited processes
from multiprocessing import Pool
pool = Pool(processes=4)
| Problem | Solution |
|---|---|
| File still writing | Check for .filepart extension; use IN_CLOSE_WRITE |
| Process explosion | Use process pools with limited workers |
| Memory leak | Monitor child processes; implement cleanup |
| Log bloat | Configure rotating file handlers |
def process_image(filename):
# Resize, optimize, or convert images
pass
def validate_file_format(filename):
# Verify file signatures and formats
pass
def scan_file(filename):
# Run virus/malware scans on uploaded files
pass
def extract_metadata(filename):
# Extract and index file metadata
pass
def backup_file(filename):
# Copy to backup location or cloud storage
pass
# Check current limit
cat /proc/sys/fs/inotify/max_user_watches
# Increase the limit (requires root)
echo 524288 | sudo tee /proc/sys/fs/inotify/max_user_watches
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
Create a systemd service for automatic startup:
[Unit]
Description=Directory Watcher Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/dirwatcher
ExecStart=/usr/bin/python3 /opt/dirwatcher/wa_dir.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable dirwatcher
sudo systemctl start dirwatcher
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY wa_dir.py .
CMD ["python", "wa_dir.py"]
The dirwatcher project demonstrates an efficient, production-ready solution for real-time file system monitoring in Python. By combining inotify for event detection with multiprocessing for concurrent handling, you can build responsive file processing pipelines that scale gracefully.
Key Takeaways:
inotify for efficient, non-polling file monitoringmultiprocessing for concurrent event handlingGitHub Repository: https://github.com/MourIdri/dirwatcher
File system monitoring is a fundamental capability for building event-driven systems and automated workflows. Use this foundation to build custom solutions for your specific needs.