Pythonでファイルが更新されたらイベントフックするWatchdogの使い方

Watchdogでファイルが更新されたらイベントフックする【Python】

この記事ではPythonの Watchdog モジュールをつかって、ファイルの変更(作成、削除、更新など)を監視し、ファイルが更新されたら登録した関数を呼び出す方法をご紹介いたします。

はじめに

このサイトはPythonで作った自作の静的サイトジェネレータを使ってMarkdownファイルをHTMLへ変換して書き出しています。

これまで、文章を書き換えるたびにターミナルから手動でコンパイルを実行していました。頻繁にブラウザで見栄えをチェックしようとすると、ターミナルでのコンパイル実行作業がワンクッション挟むため煩わしい部分がありました。

そこでタイトルの通り、ファイルの更新を監視して変更があったらコンパイルできないか考えました。オープンソースの静的サイトジェネレータならば当たり前についている自動コンパイル機能ですが、自前で用意しようとするとちょっと考えちゃいますよね。

最初に考えたのは、該当ファイル群のパスを paths = glob.glob(root_dir + "/**/*.md", recursive=True) で検索しファイルに登録されている更新時間をmtime = os.stat(path).st_mtime で取得して、パスをキーでハッシュ化、csvファイルに保存して前回の更新履歴と比較する方法を試してみました。これを2〜3秒に一度まわしてたのですが、CPU負荷が高くなってしまい効率が悪い感じです。

どうにかならないかと悩んでいたところ、カーネルレベルでファイルの状態を監視してくれるAPIがあることを知りました。Pythonでもたとえば watchdog というライブラリを使えば、その機能を実現できるということでさっそく試してみました。

Watchdogのインストール

みてるでー
みてるでー

pipを使ってWatchdogライブラリをインストールします。ここではPython3を使いましたので、pip3でインストールします。

$ pip3 install watchdog

Watchdogでファイルが更新されたらイベントフックするプログラム例

インストールできたら適当な名前でPythonファイルを作成します。 watchdog.py という名前にしてしまうと、名前がバッティングして実行できませんのでご注意ください。

ファイルの更新があれば、登録したコールバック関数が呼び出されます。

"""
Hook on_modified event and call a callback function when files is updated.
"""
import time
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler


class WatchdogHandler(PatternMatchingEventHandler):
    def __init__(self, callback, patterns):
        super(WatchdogHandler, self).__init__(patterns=patterns)
        self.callback = callback

    def __callback_handler(self, func,*args):
        return func(*args)

    # def on_moved(self, event):
    #     print(event)
    #     self.__callback_handler(self.callback)

    # def on_created(self, event):
    #     print(event)
    #     self.__callback_handler(self.callback)

    # def on_deleted(self, event):
    #     print(event)
    #     self.__callback_handler(self.callback)

    def on_modified(self, event):
        print(event)
        self.__callback_handler(self.callback)


def watch(path, callback, extensions):
    event_handler = WatchdogHandler(callback, extensions)
    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


def say_hello():
    print("hello")

if __name__ == "__main__":
    dir_to_watch = "/somewhere"
    extensions = ["*.md"]
    watch(dir_to_watch, say_hello, extensions)

プログラムはそんなに難しくありませんでした。PatternMatchingEventHandler を実装したハンドラー(WatchdogHandler)をつくり、イベントをフックしたい関数でコールバック関数を実行させています。

Observerでファイルを監視するディレクトリと拡張子を指定します。

ここでは、/somewhere ディレクトリ下にある.mdファイルの更新を監視するように命じていますね。

on_modified 関数で受け取れる event にはイベントタイプや、ディレクトリか否か、ファイルパスが格納されています。このように。

<FileModifiedEvent: event_type=modified, src_path='/somewhere/python-watchdog-event.md', is_directory=False>

たとえば event.src_path でファイルのパスを知ることができます。

Watchdogモジュールのおかげで、やりたいことがカンタンに実現できました。これならもっと早く導入しておくべきでしたね。みなさんも機会があればぜひ使ってみてください。

記事に関するご質問などがあれば、
Twitter または お問い合わせ までご連絡ください。
Python学習にオススメの本をご紹介!
Pandasでデータサイエンスはじめよう!
関連記事