qtatsuの週報

初心者ですわぁ

watchdog、おまいだったのか。いつも、ファイルを監視してくれていたのは…【Python】

前書き

この記事はJSL(日本システム技研) Advent Calendar 2021のカレンダーの12/6(月)の記事です。

みなさん、watchdogというライブラリをご存知でしょうか。

watchdogは「番犬」「監視人」という意味があるようです。指定したフォルダ配下を監視し、ファイルの作成/変更/削除/移動イベントを感知してくれます。

他のライブラリからも利用されるため、お手元の環境でpip freeze | grep watchdogを実行すると出てくるかもしれません。

この記事では以下の観点からwatchdogを紹介したいと思います。

  1. 【watchdog】が使われているライブラリの紹介
  2. watchdogを自分でも使ってみよう!(サンプル)

参考リンク

Watchdog公式

Flask, Werkzeug公式

pytest-watch公式

参考にさせていただいた記事

環境

バージョン
MacOS Big Sur 11.1
Python3 3.9.1
watchdog 2.1.6
pytest 6.2.5
pytest-watch 4.2.0

watchdogが使われているライブラリ

まずはwatchdogを利用しているライブラリを紹介します。

普段は意識しませんが、「勝手にリロードしてくれる」系の動作はwatchdogを使っていることがあります。(もちろん、自前で実装しているライブラリも多いですが).

Flask

Welcome to Flask — Flask Documentation (2.0.x)

Webアプリケーションフレームワークとして有名なFlaskです。

アイコンの画像が何物かわからないので、知っている人がいたら教えて欲しいです。(少なくともフラスコには見えないので、僕は勝手にシシトウだと思っています。)

Flaskには開発用サーバがついており、アプリケーションのファイルを編集すると、自動でサーバーがrestartして変更を反映するという機能を持っています。

こちらの公式ドキュメントにありますが、せっかくなので実際にやってみましょう。

  1. プロジェクト立ち上げ〜仮想環境にFlaskをインストール.
$ mkdir myproject
$ cd myproject

$ python3.9 -m venv env
$ source env/bin/activate
$ pip install --upgrade pip
$ pip install Flask
  1. sample.pyを作成し、以下のコードを記述します。
from flask import Flask
app = Flask(__name__)

@app.route('/')
def gongitsune():
    return 'ごん、おまいだったのか....'
  1. 環境変数を設定します。FLASK_ENV=developmentがリロードを有効にする設定です。
$ export FLASK_APP=sample.py   # 先程作成した.pyファイル.
$ export FLASK_ENV=development # debugモード有効化
  1. 開発用サーバーを立ち上げます。
$ python -m flask run 

さて、するとwatchdogがリロードをしてくれるはずですね!

sample.pyファイルを変更するとメッセージが出てきます。

....
* Restarting with stat
....

あれ?watchdogは...???

Flask公式ドキュメントのこの辺りを見ると、watchdogは自分でpip installする必要があると書かれています。

statはデフォルトでonになっているリローダです。watchdogの方がより高速で効率が良いとdocには書いてあります。

Watchdog provides a faster, more efficient reloader for the development server.

余談ですが、正確に言うとFlaskに使われているWerkzeugでstag/watchdogが採用されています

Werkzeugは, WSGI準拠のアプリケーション作成を助けてくれるライブラリです。

Serving WSGI Applications — Werkzeug Documentation (2.0.x)

上記リンクによると、watchdogはstatよりリロードが速く、また効率についてはバッテリーの消費が少ないということらしいです。

前置きが長くなりましたが、リローダをstat→watchdogに切り替えてみましょう!

ドキュメントによると、pipでインストールすれば、自動でstatからwatchdogに切り替わります。

$ pip install watchdog
$ python -m flask run 
....
* Restarting with watchdog (fsevents)
...

やりました。

小さなプロジェクトだとあまり変化は感じられませんが、topコマンドなどでCPU使用率などみてるとwatchdogの方が確かに低くなっていました(半分くらいになる)。

ちなみに同じく有名なWebフレームワークのDjangoにも自動リロード機能つきの開発サーバがついていますが、こちらは自前で実装しているようです。

以下のリンクがリロード用のモジュールのようですね(間違ってたらご指摘お願いします)。 django/autoreload.py at main · django/django · GitHub

pytest-watch

少し古いですが、ファイルを変更するたびにpytestを自動実行してくれる強力なライブラリです。

watchdogを使ったお手本のような例です。.pyで終わるファイルを検知してくれます。

以下のようなプロダクトコード/テストコードを書きます。

def heiju(text):
    return f"兵十「{text}」"

def test_heiju():
    text = "おまいだったのか"
    actual = heiju(text)
    assert actual == "兵十「{おまいだったのか}」"

以下のgifでは

  1. まず普通にpytestでテストを実行しています。
  2. 次に、ptwでテストを実行しています。コードを編集すると、自動で再度テストが走っています。

f:id:Qtatsu:20211206233700g:plain

watchdogがファイルの変更を検知し、その度にpytestが走るというシンプルな仕組みですがかなり強力だと思います。

自分は小さなスクリプトを作る時や、ちょっと競プロに手を出していた時はこちらを使っていました。コードがめっちゃ速く書けます。

watchdogを自分でも使ってみる。

前置き: tmuxと一緒に使うのがオススメ.

画面分割→ 1画面でコード編集、1画面をwatchdogで監視...この形が一番捗ります。

自分はtmuxが好きなのでこちらをお勧めしますが、iTerm2を使ってる人も周りには多いですね。

こちらの記事がわかりやすいので、紹介させていただきます。 - tmuxを必要最低限で入門して使う - Qiita

例1: watchmedoでコマンドを登録してみる

まずはwatchmedoを使ってみましょう!

こちらwatchdogの公式製ツールで、以下のように追加インストールできます。

$ pip install "watchdog[watchmedo]" 

パッと思いつくようなことは、大体コイツで可能です。

例えば以下のことをやってみましょう。

  • jsonファイルが変更されたら、文法チェックをおこなう。
$ watchmedo shell-command \ 
    -c 'python -m json.tool ${watch_src_path} > /dev/null'
    -p '*.json' 
    --drop

まずはGIFをどうぞ。

f:id:Qtatsu:20211206233747g:plain

若干わかりづらいですが、:wというコマンドが画面一番下に出た瞬間、保存しています。

jsonをチェックしているコマンドはPythonの標準ライブラリで、jsonのフォーマッタです。標準出力を/dev/nullに捨てることでエラーチェックに使っています。

手前味噌ですがこちらの記事でも紹介しました。 シェル上でjsonをフォーマットする、各種ツール導入手順と使い方の個人的なまとめ

  • -cオプションで、ファイル変更イベント時に実行するコマンドを渡しています。
    • ${watch_src_path}は変更されたファイルのパスをフルパスで取得できます。
  • -pオプションで、変更を検知するファイル名のパターンを記述しています(今回はjson)
  • --dropはイベントが複数回走らないようにしています。
    • これはvimのファイル変更方法が特殊なため、指定しています。他のエディタなら不要かと思います(未検証)

これらは以下のようにhelpを見ると、全てのオプションが説明されています。

$ watchmedo shell-command -h

ちなみに、コマンドを登録するshell-commandサブコマンドの他にも単純なlogを出したり、yamlを読ませて実行するサブコマンドもあります。

以下のようにして確認できます。

$ watchmedo -h

例2: ファイルの種類によって異なるコマンドを実行する!

  • pythonファイルが変更されたら、実行する。
  • jsonファイルが変更されたら、文法チェックをおこなう。

このようなケースでは、yamlファイルに動作を定義して簡単に実行するサブコマンドが用意されています.

tricks-from というサブコマンドで、読み込むyamlファイルはtricsと呼ばれています。

また、以下の記事を参考にさせていただきました。

Python Watchdogのtricksを試す - Qiita

ではyamlを書いてみます。

tricks:
- watchdog.tricks.ShellCommandTrick:
    patterns:
    - "*.py"
    shell_command: 'python ${watch_src_path}'
    drop_during_process: true

- watchdog.tricks.ShellCommandTrick:
    patterns:
    - "*.json"
    shell_command: 'python -m json.tool ${watch_src_path} > /dev/null'
    drop_during_process: true

では、上記のtrics.yamlを指定してwatchmedoを実行してみます。

$ watchmedo tricks-from trics.yaml  

pythonファイル編集→実行、jsonファイル編集→チェック、の順番でテストしています。

f:id:Qtatsu:20211206233849g:plain

例3: 監視用スクリプトを実行する!

一番自由度が高い方法です。

Quickstart — watchdog 0.8.2 documentation

公式ドキュメントにもサンプルがあり、いろんな記事で紹介されているので、(機能的にはこちらメインですが)省略させていただきます。

以下、watchdogの利用例の記事を紹介させていただきます。

Pythonで変更のあったモジュールを動的インポート/リロードする - Qiita Pythonのwatchdogを使ってFTP受信ファイルを安全に処理する - Qiita Pythonのwatchdogを使ってFTP受信ファイルを安全に処理する - Qiita

まとめ

  • watchdogを使うと、ファイル変更を監視することができる。
  • watchmedoにより、ファイル変更-> コマンド実行やログ出しなどのよくあるパターンを簡単に実行できる。
  • watchdogは他の便利なライブラリを支える、縁の下の力持ちとしても活躍している。