Sometimes, you may wish to check within a script when a configuration file or the script itself changes, and if so, then automatically restart the script. In this post, you will see a way of doing this in Python.
Consider the following scenario. You have a Python script that runs as a daemon and regularly performs the prescribed tasks. Examples may be a web server, a logging service, and a system monitor. When the script starts, it reads its configuration from a file, and then enters an infinite loop. In this loop, it waits for inputs from the environment and acts upon them. For example, a web server may react to a request for a page, which results into sending a response to the user.
From time to time, it may be necessary to restart the script. For example, if you fix a bug in it or change its configuration. One way of doing so is to kill the script and run it again. However, this requires manual intervention, which you may forget to do. When you fix a vulnerability in the script, you want to be sure that you do not forget to restart the script. Otherwise, someone may exploit the vulnerability if you did not restart the script. It would be nice if there existed a way of restarting the script within itself after it detected that its sources or a configuration file changed. In the rest of this post, we will show such a way.
For the purpose of the present post, let us assume that the script has the following structure:
# Parse the arguments and configuration files. while True: # Wait for inputs and act on them. # ...
That is, it processes the arguments and loads the configuration from the configuration files. After that, the script waits for inputs and processes them in an infinite loop.
Next, we describe how to watch files for changes. After that, we show how to restart the script.
Checking Watched Files For Changes
First, we define the paths to the files whose change we want to watch:
WATCHED_FILES = [GLOBAL_CONFIG_FILE_PATH, LOCAL_CONFIG_FILE_PATH, __file__]
We watch the global configuration file, the local configuration file, and the script itself, whose path can be obtained from the special global variable
__file__. When the script starts, we get and store the time of the last modification of these files by using
from os.path import getmtime WATCHED_FILES_MTIMES = [(f, getmtime(f)) for f in WATCHED_FILES]
Then, we add a check if any of these files have changed into the main loop:
while True: for f, mtime in WATCHED_FILES_MTIMES: if getmtime(f) != mtime: # Restart the script. # Wait for inputs and act on them. # ...
If either of the files that we watch has changed, we restart the script. The restarting is described next.
Restarting the Script
We restart the script by utilizing one of the
exec*() functions from the
os module. The exact version and arguments depend on how you run the script. For example, on Linux or Mac OS, you can make the file executable by putting the following line to the top of the file
$ chmod a+x daemon.py
Then, you can run the script via
In such a situation, to restart the script, use the following code:
Otherwise, when you run the script via
$ python daemon.py
use this code:
os.execv(sys.executable, ['python'] + sys.argv)
Either way, do not forget to import the
To explain, the arguments of
os.execv() are the program to replace the current process with and arguments to this program. The
__file__ variable holds a path to the script,
sys.argv are arguments that were passed to the script, and
sys.executable is a path to the Python executable that was used to run the script.
os.execv() function does not return. Instead, it starts executing the current script from its beginning, which is what we want.
If you use the solution above, please bear in mind that the
exec*() functions cause the current process to be replaced immediately, without flushing opened file objects. Therefore, if you have any opened files at the time of restarting the script, you should flush them using
os.fsync(fd) before calling an
Of course, the presented solution is only one of the possible ways of restarting a Python script. Depending on the actual situation, other approaches, like killing the script externally and starting it afterwards, may be more suitable for you. Moreover, there exist other methods of checking whether a watched file has changed and acting upon such a change. If you know of another way of restarting a Python program within itself, please share it by posting a comment.
Complete Source Code
The complete source code for this post is available on GitHub.