I needed to provide an update command for a Python script. I am documenting the process here.

The application was packed in a zip file and distributed that way, a so-called ZipApp. While I picked this option for ease of distribution, it actually makes self-updating way easier too. Since you will only have to replace a single file, you don’t have to worry about partial updates.

The first thing to do is to fetch the update file and overwrite the application file with it. The proper way to do this is to write to a temporary file, verify it if it is an option, and then move the file over the original with an atomic move operation. To keep this page shorter, I am just going to write over the original file and hope for the best.

Keep in mind that on Windows you might not be able to overwrite the file that is currently being used. This method should work on Linux and Mac OS though. On Windows, people use different methods such as having another executable installed and using that to overwrite the application on startup.

For the download, you can use any method. I am using urllib here because it is built in. You can get the path of the current file with os.path.dirname(__file__).

import urllib.request
import os

path = os.path.dirname(__file__)

with urllib.request.urlopen("https://example.com/latest.zip") as upd:
    with open(path, "wb+") as f:
        f.write(upd.read())

The file is now up-to-date, and the next time it is restarted, the application will be on the latest version. But we can actually replace the current process with a new one, and pass the arguments given to the original application. To do this, we use the execl syscall.

import sys
import os

os.execl(sys.argv[0], *sys.argv)