gulp
とかは指定したファイルの変更があると、それに応じてアクションを実行してくれる機能がありますね。またbottleやdjangoのDEVサーバ等でもコードの変更を検知して自動でプロセスを再起動してくれる機能があります。
先日作業中に、WEBサーバではないものの常駐型プロセスの開発をするケースがあり、コード書き換えごとに再起動するのが面倒だったので、変更を検知するとプロセスを自動で再起動してくれるコードを書きました。
今回はそれをもう少し整えてPyPIに登録したのでまとめておきます。
PyWatcher
pywatcherというライブラリを作製しました。
. ├── LICENSE ├── MANIFEST.in ├── README.md ├── pywatcher │ ├── __init__.py │ ├── command.py │ └── pywatcher.py ├── requirements.txt └── setup.py
実処理が書いてあるpywatcher.py
とコマンドラインツールとしてのインターフェースをもつcommand.py
だけの作りです。pywatcherをライブラリとして利用することは出来ると思いますが、本ライブラリ自体が提供するpywatcher
というコマンドラインツールを使うだけでいいと思います。
インストール
pipでいれるだけです。
$ pip install pywatcher
これでpywacher
コマンドが使用可能になります。
(pywatcher) denzownoMacBook-Pro:pywatcher denzow$ pywatcher -v usage: pywatcher [-h] -t TARGET_DIR_PATH -c TARGET_COMMAND_STR [-i RELOAD_THRESHOLD_SECONDS] [--disable-capture-stdout] pywatcher: error: the following arguments are required: -t/--target-dir, -c/--command (pywatcher) denzownoMacBook-Pro:pywatcher denzow$ pywatcher -h usage: pywatcher [-h] -t TARGET_DIR_PATH -c TARGET_COMMAND_STR [-i RELOAD_THRESHOLD_SECONDS] [--disable-capture-stdout] ----------------------------------------------------------------------- PyWatcher: monitor file and reload process. like gulp watch e.g: pywatcher -t . -c 'ping localhost' -> if some file on current dir changed, restart process 'ping localhost'. ----------------------------------------------------------------------- optional arguments: -h, --help show this help message and exit -t TARGET_DIR_PATH, --target-dir TARGET_DIR_PATH target directory for watching. -c TARGET_COMMAND_STR, --command TARGET_COMMAND_STR target command. this command execute and restart when file changed. -i RELOAD_THRESHOLD_SECONDS, --reload-interval-seconds RELOAD_THRESHOLD_SECONDS reload threshold seconds. --disable-capture-stdout is_disable_capture_stdout
使い方
基本的にはREADME通りですが、以下のように使用します。
$ pywatcher -t . -c 'ping localhost'
-t
で監視ディレクトリを指定し、-c
で常駐するプロセスのコマンドを指定します。以降は監視ディレクトリ配下で変更が発生するたびに指定しているプロセスが停止され、再起動します。
(pywatcher) denzownoMacBook-Pro:pywatcher denzow$ pywatcher -t. -c 'ping localhost' 2018-03-11 23:35:37,114 - INFO - [start process]: ping localhost 2018-03-11 23:35:37,124 - DEBUG - [subprocess_output]: PING localhost (127.0.0.1): 56 data bytes 2018-03-11 23:35:37,124 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.043 ms 2018-03-11 23:35:38,129 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.132 ms 2018-03-11 23:35:39,133 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.161 ms 2018-03-11 23:35:40,138 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.152 ms 2018-03-11 23:35:41,142 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.062 ms 2018-03-11 23:35:42,147 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.095 ms 2018-03-11 23:35:43,141 - INFO - [reload process]: ping localhost <-- ファイルを変更したので再起動された 2018-03-11 23:35:43,141 - DEBUG - [subprocess_output]: b'' 2018-03-11 23:35:43,141 - INFO - [start process]: ping localhost 2018-03-11 23:35:43,149 - DEBUG - [subprocess_output]: PING localhost (127.0.0.1): 56 data bytes 2018-03-11 23:35:43,149 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.044 ms 2018-03-11 23:35:44,152 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.063 ms 2018-03-11 23:35:45,152 - DEBUG - [subprocess_output]: 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.048 ms
こんな感じで呼び出されているプロセス側の標準出力もロギングされています。終了時はctrl+c
で停止します。
また、指定したディレクトリのすべてのファイルではなく特定のパターンにマッチする場合のみ監視したいという要件もあるかと思いますので-p
オプションを設けています。本オプションでGlobパターンを指定することで、それに該当するファイルの変更のみをトリガーとすることができます。
$ # *.pyファイルの変更のみを監視する $ python pywatcher/command.py -t ./ -c 'ping localhost' -p '*.py'
ちょっと込み入った事
pywatcherはwatchdog
というライブラリを使用しています。このライブラリはファイルの変更を検知してそのイベントに応じて何かをするといった処理を簡単にかけるようにしてくれます。
from watchdog.events import FileSystemEventHandler class PyWatcher(FileSystemEventHandler): def __init__(self, process_command, reload_threshold_seconds, is_capture_subprocess_output, pattern_list=None, logger=None): """ :param str process_command: process command string :param int reload_threshold_seconds: reload min threshold seconds. :param bool is_capture_subprocess_output: capture subprocess output flag. """ super().__init__() : def on_created(self, event): """create時の処理""" def on_modified(self, event): """modify時の処理""" def on_deleted(self, event): """delete時の処理"""
抜粋ですが、基本的にFileSystemEventHandler
を継承しon_xxx
メソッドを実装するだけで処理がかけます。引数で渡ってくるevent
はFileSystemEventHandler
の場合watchdog.events.FileSystemEvent
のサブクラスとなっており、ファイルパス等の情報が取得できますのでパターンマッチも容易でした。
まとめ
とりあえず作れるかな?とおもって作り始めてみたらさっとできたので個人的には満足しています。
$ pip install pywatcher