Updating python apps

Creating python binaries and updating those binaries is a vital part of a good application. When a user is required to go out of their way to download a seperate binary and install it (or worse, manually overwrite it), it worsens the experience.

As such most users would prefer if their applications updated automatically without them having to do anything (or at least offer a simple button to update). This guide aims to provide an example on how to do just that.

The setup

Since we're distributing apps to our users, ideally we don't just send them a .py file. For that purpose we are going to use pyinstaller. This will allow us to bundle our script into an executable file (that works even if they don't have python 😀).

Let's install all the required packages so we can work start working:

pip install pyinstaller
pip install flask

The app

To keep things simple our app will just print out it's current version. We'll create a file called myapp.py, with the contents:

app_version = "1.0.0"
print(app_version)

We'll change the variable app_version by increasing the version number to emulate how an update would change the behaviour of the script.

The server

In keeping with the spirit of python, we'll use flask to build the update server.

Let's create a failed called server.py with the content:

from flask import Flask, send_from_directory
app = Flask(__name__)

@app.route("/app")
def download():
  return send_from_directory(".", "myapp.exe")

Note, I used myapp.exe here since I'm doing this on windows, and that is what pyinstaller will generate, replace that with whatever your correct filename is.

In order to run flask, we first need to set the environment variable FLASK_APP to the name of your server file (without .py). This is OS specific, so set the environment in the terminal in whatever way the OS supports it. Then launch the flask app with flask run. e.g., for windows it would be:

$env:FLASK_APP="server"
flask run

The updater

The updater script is what will actually download the latest version, and launch the app. Let's create a file called updater.py:

import requests
import os

url = "http://localhost:5000/app"
r = requests.get(url)
open("myapp.exe", "wb").write(r.content)
os.startfile("myapp.exe")

Distribute

In order to be able to share our python app, we need to create an executable that will work regardless if the user has python or not.

pyinstaller --onefile myapp.py
pyinstaller --onefile updater.py

Running this will create two executables in the dist folder. We want to give our users the updater.exe (on windows), which will download and execute myapp.exe (our actual app).

Copy myapp.exe into the root directory of where the server is, so going to localhost:5000/app will download it.

Example

Copy updater.exe to a new empty folder and run it. Make sure the server is running!

You will se a new myapp.exe appear, and start. We've just pulled downloaded the latest version of the app 🙌

Next let's try updating it. Increase the version in myapp.py and recompile it with pyinstaller --onefile myapp.py, and move the myapp.exe into the root folder of the server.

Try running updater.exe again... it redownloaded our app, and it's the latest version!

We've just implemented updating into our app 🎉

sample

In production

Of course this was just a simple example, meant to illustrate the steps required to implement updating into your app. Ideally we wouldn't download the whole myapp.exe everytime, but rather use a versioning system to check if an update is even needed.

Closing thoughts

Hopefully now that you've seen a small example on how to implement updates into your apps, you'll consider this as a way to improve the user experience of your app.

While in this guide we had to setup everything, from the update logic to the server. We didn't really need to do all of this. Since this is such a common issue, we built pakkly, a service that hopes to make updating and installing (thats a topic for a future post 😀) seamless and easy.

Regardless if you decide to check out pakkly, hopefully this guide was useful to you, thanks for reading and stay tuned for more!