Create one executable file for a Flask app with PyInstaller

Flask_pyinstaller_logo

EDIT 2019.05.03: The content order was change and the ideas were express more succinctly.

Having a single executable file could be a great advantage in so many cases and nowadays more a more desktop applications are using web technologies (React Native, Cordova, Ionic, etc.). It was time for the Python stack to join the Group via PyFlaDesk, a simple script to combine Flask, Qt and PyInstaller to create desktop Apps.

I wanted to create a single executable desktop application and since PyInstaller was there for a while I thought it would be pretty easy, it turns out that after after trying lots of solutions from Stack overflow, Quora and several blogs without success, I decided to post how I managed to solve the problem.

Note: A full list of the resources mentioned is below

If you just want to read the solution, scroll down to the solution section

The Problem

Well, Flask is a WEB framework (or Microframework as it desribes itself), used to create WEBsites that will be served by a WEB server and view from a WEB browser. Problems emerge when we try to replace 'WEB' with desktop in the previous sentence, we are trying to force something built for a particular scenario with its limitations to work in a completely different one.

Motivation

Desktop applications aren't the most popular use for Python, those positions belongs to Web Development, DevOps and Data Science and, among the technologies used in the Web Development, Django and Flask are the most popular frameworks.

Using the tools one already knows for something completely different is a great advantage, in this case we want to create a desktop app with a single executable file from a web app built with Flask.

Some of the main advantages of the self-contained (also known as portable) software are:

There are some obvious disadvantages such as:

This are some points to consider, they could be advantages or disadvantages depending on the case:

Flask comes into the action

If you attempt to port a web app to a desktop one without changing the framework chances are you will use a lightweight framework like Flask. Now, how do you create a desktop app? Using a desktop framework (Qt, Tk, wx, etc), the most commonly used framework for this task is Qt. The idea is to create a minimal web browser capable of rendering HTML and then, execute the flask application server and browse to the url of the server inside this browser.

But what are the differences between creating a web browser and using the systems default? Well, first of all, you assume there will be one, and that that one would be able to render all the HTML, CSS and JS you are using, that could not be the case. More often than not we found ourselves developing software for old operating systems (aka Windows XP o older).

Sounds easy and actually it can be done, I was contributing to a script to achieve this very goal PyFladesk. There are some concerns about which version of Qt is the appropriate and the convenience of one over the other.

Note: If all you use is Flask for served static or pseudo static content you could tried Frozen Flask.

Solution

After reading all the previous posts and some of the PyInstaller docs. I found that some people actually solved it! But, the solution they propose was editing the spec file, which is generated after a first run of PyInstaller. I thought that solution was a hack and not the proper way to achieve what I wanted.

So I tried to understand what the changes in the spec file did and it turned out that that changes was to copy the folders Flask uses into the file directory/file (Actually one of the proposed solutions was build and then copy paste the folders, but besides being unpractical it wouldn't work with one file builds). After a little reasearch, I came across the command line arguments to achieve the same.

Windows:

pyinstaller -w -F --add-data "templates;templates" --add-data "static;static" app.py

Linux (NOT TESTED):

pyinstaller -w -F --add-data "templates:templates" --add-data "static:static" app.py

This will create a folder dist with our executable ready to be shipped. The executable will open the main window of our app.

It first started as a contribution for the PyFladesk project and then, I realize that since Qt is quite big, our executable were big too. The example app of that repository is 70 MB (much of which was the Qt Component for displaying HTML (WebEngine)). This is reasonable taking into account that we were shipping a self contain web browser.

There is no simple solution to the size problem but some suggestions are proposed in a following section.

Tutorial

If you haven't already, install it with pip (if you use virtual environments you should install it inside it)

pip install pyinstaller

Some parameters to consider:

Since Flask relies on a directory structure you should pass it to the folder, in the example case we only have two folders: templates and static, in case you use a database or some other directory structure you may adapt this.

Note: For more complex scenarios check the PyInstaller Docs

The Size Problem

Is the project using as few dependencies as possible? If yes, continue reading, if not check and then come back. Make sure you create a virtual environment for your project and execute PyInstaller from there, if the size is still big, I recommend you to check one of these:

Conclusion

Using PyInstaller and Flask is not as hard as people may experience if you have the correct knowledge but it requires a bit of work to get a clean, lightweight file. However, it's possible to create executable with complex apps, with special directory structure, databases and so on, but don't expect that to be a tiny file.

Additional Resources

The following are the pages where this topic was mentioned and I couldn't find a proper answer: