Command execution triggered by file update (python edition)

It was a while ago, but I found an article like this on Qiita. Or rather, it came from @wpythonnews.

Run unit test the moment you save the file

The method introduced here is to save the last update Timestamp and check the modification time of the files under the target directory one by one.

The check itself is executed every 100ms, and although it does not run the CPU at full capacity, it still has a certain load. About 77% load on my Mac. In addition, this code makes me feel unreadable when multiple files are updated in 100ms (results vary depending on the order in which they are evaluated).

On the other hand, it is certain that there are some situations where this kind of "file update confirmation" is required. Therefore, recent operating systems have kernel-level support.

Perhaps there are Python modules that use these. So, when I look it up, it comes out in various ways.

By the way, epoll and kqueue are supported by Python standard modules (select) with lower level I / F than others. There is.

Among the above modules, watchdog uses different APIs for each platform, and this seems to be the best if you write a general-purpose program. So, I tried using it.

Use the usual pip for installation.

shell::


$ pip install watchdog

And the sample code looks like this.

python::dirwatch2.py


#!/usr/bin/env python
from __future__ import print_function

import sys
import time
import subprocess
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler


class MyHandler(PatternMatchingEventHandler):
    def __init__(self, command, patterns):
        super(MyHandler, self).__init__(patterns=patterns)
        self.command = command

    def _run_command(self):
        subprocess.call([self.command, ])

    def on_moved(self, event):
        self._run_command()

    def on_created(self, event):
        self._run_command()

    def on_deleted(self, event):
        self._run_command()

    def on_modified(self, event):
        self._run_command()


def watch(path, command, extension):
    event_handler = MyHandler(command, ["*"+extension])
    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


if __name__ == "__main__":
    if 4 > len(sys.argv):
        print("Usage:", sys.argv[0], "dir_to_watch command extension")
    else:
        watch(sys.argv[1], sys.argv[2], sys.argv[3])

It's a little longer, but the basics are simple, just create a class that inherits one of the prepared event handler classes and implement the contents of on_moved, on_created, on_deleted, on_modified. These methods are called when moving, creating, deleting, or changing files.

By the way, the following four event handler classes are prepared.

The first is a basic class that handles file change event processing, and the second and third are the addition of a function to narrow down files by pattern matching or regular expressions. The fourth is an implementation of a handler that writes file change events as a log.

Since it is supposed to be narrowed down by extension here, PatternMatchingEventHandler is inherited and implemented so that the command is issued regardless of which of the four handler methods is called.

Then just create an instance of the Observer class, pass the event handler and the directory to monitor to schedule (), and start (). After that, there is a process that occurs every second in an infinite loop, but this is to capture the key event so that it can be stopped by Ctrl-C. This consumes a little CPU, but it's about 10% at hand. Well, is it acceptable? It can be reduced further by increasing the interval.

So, although the sample is executed, the argument of the command is matched with the dirwatch of "Execute unit test at the moment when the file is saved. Execution is like this.

shell::


$ python dirwatch2.py <directory_to_watch> <command> <extension>

A file with the specified extension under the specified directory The specified command is issued when the file is changed.

Note that this Python module called watchdog comes with a tool watchmedo to do the same thing. With it you can write like this:

shell::


$ watchmedo shell-command \
	--patterns="*"$3 \
	--command $2 \
	--recursive \
	$1

It was easy enough to beat.

In the same way, "monitoring files and directories and doing something if there is a change" can be done quite normally with Node.js tools such as Grunt and Gulp. I'd like to summarize them as well.

Recommended Posts

Command execution triggered by file update (python edition)
External command execution in Python
Execution by subshell (and variable update)
Read the file line by line in Python
Python update (2.6-> 2.7)
Read the file line by line in Python
Notify error and execution completion by LINE [Python]
Read line by line from a file with Python
Get the update date of the Python memo file.
Script python file
Python file processing
Read the xml file by referring to the Python tutorial
Speech file recognition by Google Speech API v2 using Python