ファイルやディレクトリの変更をリアルタイムに検知!
はじめに:Watchdogとは? 🤔
Watchdogは、Pythonでファイルシステムのイベント(ファイルの作成、削除、変更、移動など)を監視するための強力なライブラリです。指定したディレクトリやファイルを監視し、変更が発生した際に特定の処理を実行させることができます。
このライブラリはクロスプラットフォーム対応であり、Windows, macOS, Linuxなど、主要なオペレーティングシステムで動作します。OS固有のAPI(Linuxのinotify, macOSのFSEvents, WindowsのReadDirectoryChangesWなど)を抽象化し、統一されたインターフェースを提供するため、開発者はOSの違いを意識することなくファイル監視機能を実装できます。もしOS固有のAPIが利用できない場合でも、定期的にファイルの状態をチェックするポーリング方式の監視もサポートしています。
Watchdogは、以下のような様々な用途で活用されています。
- ファイルの自動同期やバックアップ
- 開発中のソースコード変更時の自動リロード(Webサーバーなど)
- ログファイルのリアルタイム監視と分析
- 設定ファイルの変更検知とアプリケーションへの自動反映
- 特定ディレクトリへのファイルアップロード時の自動処理
このブログ記事では、Watchdogの基本的な使い方から、より高度な機能、実際のユースケース、注意点まで、幅広く解説していきます。さあ、Watchdogの世界を探検しましょう!🚀
基本的な使い方:最初の監視スクリプト 🔰
インストール
Watchdogはpipを使って簡単にインストールできます。ターミナルまたはコマンドプロンプトで以下のコマンドを実行してください。
pip install watchdog
コアコンポーネント:ObserverとEventHandler
Watchdogの基本的な動作は、主に2つのコンポーネントによって成り立っています。
- Observer: ファイルシステムのイベントを監視するスレッドです。OSの機能を利用して効率的に変更を検知します。どのObserverを使用するかは、通常Watchdogが自動で最適なものを選択しますが、明示的に指定することも可能です。
- EventHandler: ファイルシステムでイベントが発生したときに、実際に何らかのアクションを実行するクラスです。
watchdog.events.FileSystemEventHandler
を継承して、特定のイベント(作成、削除、変更、移動)に対応するメソッドをオーバーライドします。
簡単な監視スクリプトの例
以下は、指定したディレクトリ(ここでは `./monitoring_target`)で発生した全てのファイルシステムイベントをコンソールに出力する簡単な例です。
import sys
import time
import logging
from watchdog.observers import Observer
from watchdog.events import LoggingEventHandler, FileSystemEventHandler
# カスタムイベントハンドラを作成することも可能
class MyEventHandler(FileSystemEventHandler):
def on_created(self, event):
print(f"ファイル作成検知! -> {event.src_path}")
def on_deleted(self, event):
print(f"ファイル削除検知! -> {event.src_path}")
def on_modified(self, event):
# is_directory=True の場合、ファイルではなくディレクトリの属性変更なども検知する
if not event.is_directory:
print(f"ファイル変更検知! -> {event.src_path}")
def on_moved(self, event):
print(f"ファイル移動検知! -> from {event.src_path} to {event.dest_path}")
if __name__ == "__main__":
# ログ設定 (Watchdog内部のログも見たい場合)
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
# 監視対象ディレクトリ
path = "./monitoring_target" # sys.argv[1] if len(sys.argv) > 1 else '.'
# イベントハンドラのインスタンスを作成
# event_handler = LoggingEventHandler() # 標準のログ出力ハンドラ
event_handler = MyEventHandler() # 自作のハンドラ
# Observerのインスタンスを作成
observer = Observer()
# イベントハンドラと監視対象ディレクトリ、再帰的監視するかどうかを指定して監視を開始
observer.schedule(event_handler, path, recursive=True) # recursive=Trueでサブディレクトリも監視
# 監視を開始(別スレッドで実行される)
observer.start()
print(f"'{path}' ディレクトリの監視を開始しました... (Ctrl+Cで終了)")
try:
# メインスレッドは無限ループで待機(Observerは別スレッドで動作)
while True:
time.sleep(1)
except KeyboardInterrupt:
# Ctrl+Cが押されたら監視を停止
observer.stop()
print("監視を停止しました。")
# Observerスレッドが完全に終了するのを待つ
observer.join()
print("プログラムを終了します。")
このスクリプトを実行する前に、カレントディレクトリに `monitoring_target` という名前のディレクトリを作成してください。スクリプトを実行し、`monitoring_target` ディレクトリ内でファイルの作成、削除、編集、名前変更などを行うと、コンソールに対応するメッセージが出力されるはずです。
FileSystemEventHandler
を継承したクラスでは、以下の主要なイベントに対応するメソッドをオーバーライドできます。
on_any_event(event)
: 全てのイベントタイプで呼び出されます。on_created(event)
: ファイルまたはディレクトリが作成されたときに呼び出されます。on_deleted(event)
: ファイルまたはディレクトリが削除されたときに呼び出されます。on_modified(event)
: ファイルまたはディレクトリが変更されたときに呼び出されます。(ファイルの内容変更、タイムスタンプ変更、属性変更など)on_moved(event)
: ファイルまたはディレクトリが移動または名前変更されたときに呼び出されます。移動元(event.src_path
)と移動先(event.dest_path
)の情報が含まれます。
各メソッドに渡される event
オブジェクトには、イベントに関する詳細情報(イベントタイプ、発生したパス、ディレクトリかどうかなど)が含まれています。
主な属性は以下の通りです。
event.event_type
: イベントの種類 (e.g., ‘created’, ‘deleted’, ‘modified’, ‘moved’)event.is_directory
: イベントがディレクトリで発生したかどうか (True/False)event.src_path
: イベントが発生したファイル/ディレクトリのパスevent.dest_path
: (移動イベントの場合のみ) 移動先のパス
イベントハンドラの詳細 🧐
Watchdogは、単にイベントを検知するだけでなく、柔軟なイベント処理を可能にするための様々なイベントハンドラを提供しています。
FileSystemEventHandler
これは最も基本的なイベントハンドラで、全てのイベントタイプに対応するメソッド(on_created
, on_deleted
など)を持ちます。これを継承し、必要なメソッドをオーバーライドすることで、独自の処理を実装します。先ほどの例で使用した MyEventHandler
がこれにあたります。
PatternMatchingEventHandler
特定のファイル名パターン(Unixシェル形式のワイルドカード)にマッチするファイルやディレクトリのイベントのみを処理したい場合に便利です。例えば、*.py
や *.txt
ファイルに対する変更のみを検知したい場合などに使用します。
from watchdog.events import PatternMatchingEventHandler
class PythonFileHandler(PatternMatchingEventHandler):
def __init__(self):
# patterns: マッチさせたいパターン (複数指定可)
# ignore_patterns: 無視したいパターン (複数指定可)
# ignore_directories: ディレクトリのイベントを無視するか (デフォルト: False)
# case_sensitive: パターンマッチングで大文字小文字を区別するか (デフォルト: False)
super().__init__(patterns=["*.py", "*.pyc"],
ignore_patterns=["*.tmp", "*/.git/*"],
ignore_directories=True,
case_sensitive=False)
def on_modified(self, event):
print(f"Pythonファイル変更検知! -> {event.src_path}")
def on_created(self, event):
print(f"Pythonファイル作成検知! -> {event.src_path}")
# Observerの設定時にこのハンドラを使用する
# observer.schedule(PythonFileHandler(), path, recursive=True)
patterns
引数で監視したいパターン、ignore_patterns
で無視したいパターンを指定できます。ignore_directories=True
とすると、ディレクトリ自体のイベント(作成、削除、変更)は無視され、ファイルに対するイベントのみが処理対象となります。
RegexMatchingEventHandler
より複雑なパターンマッチングが必要な場合は、正規表現を使用する RegexMatchingEventHandler
が利用できます。使い方は PatternMatchingEventHandler
と似ていますが、パターンとして正規表現文字列を指定します。
import re
from watchdog.events import RegexMatchingEventHandler
class ImageFileHandler(RegexMatchingEventHandler):
def __init__(self):
# 正規表現のリストを指定
regexes = [re.compile(r".*\.(jpg|jpeg|png|gif)$", re.IGNORECASE)]
super().__init__(regexes=regexes, ignore_directories=True)
def on_created(self, event):
print(f"画像ファイル作成検知! -> {event.src_path}")
def on_modified(self, event):
print(f"画像ファイル変更検知! -> {event.src_path}")
# Observerの設定時にこのハンドラを使用する
# observer.schedule(ImageFileHandler(), path, recursive=True)
正規表現を使うことで、ワイルドカードよりも柔軟で強力なフィルタリングが可能になります。
LoggingEventHandler
これは、発生した全てのイベントをPythonの標準 `logging` モジュールを使ってログに出力するシンプルなハンドラです。デバッグや動作確認に便利です。基本的な使い方で最初にコメントアウトしていたのがこのハンドラです。
カスタムイベントハンドラの組み合わせ
これらのハンドラを組み合わせたり、さらにカスタマイズしたりすることも可能です。例えば、特定のパターンにマッチするファイルに対しては特別な処理を行い、それ以外のファイルはログに記録する、といった複雑な処理フローも実装できます。dispatch(event)
メソッドをオーバーライドすることで、イベントの振り分けロジックを完全に自作することもできます。
Observerの種類と選択肢 📡
Watchdogは、ファイルシステムの変更を監視するために、OSが提供するネイティブな機能を利用しようとします。これにより、効率的かつ低遅延な監視が可能になります。しかし、利用できる機能はOSによって異なります。Watchdogはこれらの違いを吸収し、プラットフォームに応じた最適なObserverを提供します。
通常、watchdog.observers.Observer()
を呼び出すと、実行環境のOSに最適なObserverクラスが自動的に選択されます。しかし、特定のObserverを明示的に使用したい場合や、各Observerの特性を理解しておきたい場合のために、主要なObserverを紹介します。
Observerクラス名 | 対応OS | 利用するOS機能 | 特徴 |
---|---|---|---|
InotifyObserver |
Linux 2.6.13+ | inotify | 推奨 (Linux)。カーネルレベルでの効率的なイベント通知。低CPU負荷。 |
FSEventsObserver |
macOS | FSEvents API | 推奨 (macOS)。OSネイティブの効率的なイベント通知。 |
KqueueObserver |
macOS, BSD | kqueue / kevent | macOSやBSD系のシステムで利用可能。FSEventsが使えない場合の代替。 |
ReadDirectoryChangesWObserver |
Windows | ReadDirectoryChangesW API | 推奨 (Windows)。OSネイティブの効率的なイベント通知。 |
PollingObserver |
全OS | なし (定期的な状態スキャン) | 非推奨 (代替手段がない場合)。OSのネイティブ機能が利用できない環境(一部のネットワークファイルシステムなど)でのフォールバック。定期的にファイルの状態をチェックするため、CPU負荷が高く、イベント検知に遅延が生じる可能性がある。 |
PollingObserver
は、最後の手段として用意されています。ネイティブな監視APIが利用できない場合(例えば、NFSやSMBなどの一部のネットワークファイルシステム上)でもファイル監視を可能にしますが、パフォーマンスの観点からは可能な限り避けるべきです。PollingObserver
を使用する場合、コンストラクタでポーリング間隔(timeout
)を指定できます。
from watchdog.observers.polling import PollingObserver
# ポーリング間隔を5秒に設定
observer = PollingObserver(timeout=5)
# ... observer.schedule() など ...
基本的には Observer()
を使うだけで問題ありませんが、特定の環境で問題が発生した場合や、パフォーマンスチューニングを行いたい場合に、これらのObserverの存在を知っておくと役立ちます。
高度な機能とTips ✨
再帰的監視 (Recursive Monitoring)
基本的な使い方で既に触れましたが、observer.schedule()
メソッドの recursive
引数を True
に設定することで、指定したディレクトリだけでなく、その配下にある全てのサブディレクトリも監視対象に含めることができます。これは非常に一般的な使い方です。
observer.schedule(event_handler, path, recursive=True) # サブディレクトリも監視
複数ディレクトリの監視
一つのObserverインスタンスで、複数の異なるディレクトリを監視することができます。それぞれのディレクトリに対して observer.schedule()
を呼び出すだけです。異なるディレクトリに異なるイベントハンドラを割り当てることも可能です。
observer = Observer()
handler1 = MyEventHandler1()
handler2 = MyEventHandler2()
observer.schedule(handler1, "/path/to/dir1", recursive=True)
observer.schedule(handler2, "/path/to/another/dir", recursive=False) # こちらは再帰しない
observer.start()
# ...
監視のスケジューリング解除・追加
監視を開始した後でも、特定の監視を解除したり、新たに追加したりできます。observer.schedule()
は監視対象を追加し、observer.unschedule()
は解除します。unschedule_all()
ですべての監視を解除できます。
# 監視のスケジュール
watch = observer.schedule(event_handler, path)
# ... しばらく監視 ...
# 特定の監視を解除
observer.unschedule(watch)
# 新しい監視を追加
new_watch = observer.schedule(new_handler, new_path)
# 全ての監視を解除
# observer.unschedule_all()
schedule
メソッドは、スケジュールされた監視を表す `ObservedWatch` オブジェクトを返します。このオブジェクトを unschedule
メソッドに渡すことで、特定の監視だけを解除できます。
スレッドセーフティ
Observerはバックグラウンドスレッドで動作し、イベントハンドラのメソッドもそのスレッドから呼び出されます。そのため、イベントハンドラ内で共有リソース(変数、ファイル、ネットワーク接続など)にアクセスする場合は、スレッドセーフティに注意が必要です。必要に応じて、ロック(threading.Lock
など)を使用して排他制御を行ってください。
watchmedo コマンドラインユーティリティ 🛠️
Watchdogには watchmedo
という便利なコマンドラインツールが同梱されています。Pythonスクリプトを書かなくても、コマンドラインからファイルシステムの監視や、イベント発生時のコマンド実行などが可能です。
基本的な使い方は以下の通りです。
# 指定ディレクトリ内のイベントをログに出力 (Ctrl+Cで停止)
# -R: 再帰的に監視
watchmedo log -R ./my_project
# ファイルが変更されたら特定のコマンドを実行
# ${watch_src_path} などの変数が利用可能
# --patterns: 監視対象のパターン
# --ignore-patterns: 無視するパターン
# --recursive: 再帰的に監視
# --debounce-interval: 短時間に連続するイベントをまとめる間隔(秒)
watchmedo shell-command \
--patterns="*.py;*.html" \
--ignore-patterns="*.tmp" \
--recursive \
--command='echo "ファイル変更: ${watch_src_path}" && python my_script.py ${watch_src_path}' \
. # カレントディレクトリを監視
# 指定したPythonスクリプトを自動リロード (開発時に便利)
watchmedo auto-restart \
--directory="." \
--pattern="*.py" \
--recursive \
-- python my_app.py
watchmedo
は、簡単なタスクであればPythonコードを書く手間を省けるため、非常に便利です。watchmedo --help
や watchmedo <subcommand> --help
で詳細なオプションを確認できます。
利用可能なサブコマンドには以下のようなものがあります。
log
: イベントをコンソールにログ出力します。shell-command
: イベント発生時に指定したシェルコマンドを実行します。auto-restart
: ファイル変更時に指定したプロセスを自動的に再起動します。tricks
: YAML/JSONファイルで定義された一連のルールに基づいて、イベント発生時に様々なアクション(ファイルの移動、コマンド実行など)を実行します。(より高度な設定が可能)
ユースケース:どんな時に役立つ? 💡
Watchdogの応用範囲は広いです。具体的なユースケースをいくつか紹介します。
- ファイルの自動同期・バックアップ: 特定のディレクトリ(例: Dropboxフォルダ、作業フォルダ)を監視し、ファイルが作成・変更されたら自動的に別の場所(バックアップサーバ、クラウドストレージなど)にコピーまたは同期します。
-
開発時の自動リロード:
Webアプリケーションフレームワーク(Flask, Djangoなど)の開発中に、Pythonファイルやテンプレートファイルが変更されたことを検知し、自動的に開発サーバーを再起動させます。これにより、コード変更後に手動でサーバーを再起動する手間が省けます。多くのフレームワークの開発サーバーには、既にこの機能が組み込まれているか、Watchdogを利用して実装されています。
watchmedo auto-restart
もこの目的で使えます。 - ログファイルのリアルタイム監視: サーバーアプリケーションなどが出力するログファイルを監視し、新しいログエントリが追加されたら(ファイルが変更されたら)、特定のキーワードを検出したり、エラー発生時に通知を送ったりするシステムを構築できます。
- 設定ファイルの変更検知: アプリケーションが使用する設定ファイル(例: `config.ini`, `settings.yaml`)を監視し、ファイルが変更されたら自動的に設定を再読み込みしてアプリケーションの動作に反映させます。
- アップロードディレクトリの監視: ユーザーがファイルをアップロードするディレクトリを監視し、新しいファイルが作成されたら、そのファイルを処理する(例: 画像のリサイズ、データのバリデーション、データベースへの登録など)ワークフローを自動化します。
- コンテンツ管理システム (CMS) との連携: CMSのコンテンツディレクトリを監視し、ファイルが追加・変更されたら、検索インデックスを更新したり、キャッシュをクリアしたりします。
これらの例のように、ファイルシステムの状態変化をトリガーとして何らかのアクションを実行したい場合に、Watchdogは非常に有効なツールとなります。
注意点とベストプラクティス ⚠️
Watchdogは非常に便利ですが、利用する上でいくつか注意すべき点があります。
-
イベントの重複や遅延:
OSやファイルシステム、WatchdogのObserverの実装によっては、短時間のうちに同じファイルに対して複数のイベント(特に `modified` イベント)が発生することがあります。また、イベントの検知にはわずかな遅延が生じる可能性があります。イベントハンドラの実装では、このような重複や遅延を考慮し、冪等性(同じ操作を何度繰り返しても結果が変わらないこと)を意識したり、短時間の連続イベントを無視するデバウンス処理を入れたりすることが有効な場合があります。
watchmedo
の--debounce-interval
オプションも参考にしてください。 -
リソース消費:
特に
PollingObserver
を使用する場合、定期的にファイルシステムをスキャンするため、CPUリソースやディスクI/Oを消費します。監視対象のファイル数が多い場合や、ポーリング間隔が短い場合は、パフォーマンスへの影響が大きくなる可能性があります。可能な限りネイティブなObserver(Inotify, FSEventsなど)が選択されるようにするか、監視対象を絞り込む、ポーリング間隔を調整するなどの対策が必要です。ネイティブObserverでも、監視対象が非常に多い場合はリソース消費に注意が必要です。 -
エラーハンドリング:
イベントハンドラの処理中にエラーが発生すると、Observerスレッド全体が停止してしまう可能性があります(デフォルトの挙動では停止しませんが、ハンドラ内でキャッチされない例外があると問題に繋がります)。ハンドラのメソッド内では、予期せぬエラーが発生しても処理が継続できるように、適切に
try...except
ブロックを使ってエラーハンドリングを行うことが重要です。 -
一時ファイルやエディタの挙動:
テキストエディタやその他のアプリケーションは、ファイルを保存する際に一時ファイルを作成・削除したり、ファイルを複数回書き換えたりすることがあります。これにより、予期しないイベント(例: 削除イベントや複数回の変更イベント)が発生する可能性があります。
PatternMatchingEventHandler
やRegexMatchingEventHandler
を使って一時ファイルを無視したり、イベントハンドラ側でファイルの状態を再確認したりするなどの工夫が必要になる場合があります。 -
ネットワークファイルシステム (NFS, SMBなど):
ネットワークファイルシステム上での監視は、OSのネイティブ機能がうまく働かないことが多く、
PollingObserver
が使われることがあります。その場合、前述のリソース消費や遅延の問題が発生しやすくなります。また、ネットワークの状態によってはイベントの取りこぼしが発生する可能性も考慮する必要があります。 - 大きなファイルの扱い: 非常に大きなファイルが変更された場合、変更イベントが発生してから実際にファイルへの書き込みが完了するまでには時間がかかることがあります。イベントハンドラでファイルをすぐに読み込もうとすると、不完全なデータや古いデータを読んでしまう可能性があります。ファイルサイズや更新時刻を確認したり、少し待機したりするなどの対策が必要になる場合があります。
これらの点に留意し、状況に応じて適切な設定やエラーハンドリングを行うことで、Watchdogをより安定して効果的に利用することができます。
まとめ 🎉
Pythonライブラリ Watchdog は、ファイルシステムのイベントを監視するための非常に強力で柔軟なツールです。クロスプラットフォームに対応し、OSネイティブの効率的な監視機能を利用できるため、様々な自動化タスクやリアルタイム処理の実装に役立ちます。
基本的な使い方から、パターンマッチング、正規表現によるフィルタリング、watchmedo
コマンドラインツール、そして注意点まで解説してきました。この情報が、皆さんのプロジェクトでWatchdogを活用する一助となれば幸いです。
ファイル監視が必要になった際には、ぜひWatchdogの導入を検討してみてください! Happy coding! 💻
コメント