In recent months I really came to like Flask as a framework for creating websites and web applications. The usage of Jinja2 templates, HTML5 and Javascript to create good-looking and responsive user interfaces along with the Python language in the backend comes by naturally and on some point I asked myself why not to use Flask to create Desktop applications. The idea to use web technology for creating desktop applications is quite old and for other programming languages there exist some nice frameworks (e.g. Githubs Electron). However for Python there are not so many players around. So I decided to use the PyQt5 WebView widget. I have some experience with PyQt5 so this approach seemed the most natural to me. I wanted the application to become a stand alone executable. So I used cx_freeze here.

This is just a simple Hello World app including the named components Flask, QWebView and cx_freeze. The Flask application is started in a separate thread. I tested the code on Python3.5 and Python3.6. For Python 3.6 Flask makes use of the asyncio module. cx_freeze couldn't find all modules so I had to help by including the missing ones directly in the application.

app.py

import platform

import threading
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import *
from PyQt5.QtWebKitWidgets import QWebView
from flask import Flask

# Python 3.6 only
# Needed imports for cx_freeze to include all necessary
# modules
if int(platform.python_version_tuple()[1]) >= 6:
    import asyncio
    import asyncio.base_futures
    import asyncio.base_tasks
    import asyncio.compat
    import asyncio.base_subprocess
    import asyncio.proactor_events
    import asyncio.constants
    import asyncio.selector_events
    import asyncio.windows_utils
    import asyncio.windows_events

    import jinja2.asyncsupport
    import jinja2.ext


app = Flask(__name__)


@app.route('/')
def index():
    return 'Hello World'


thread = threading.Thread(target=app.run, args=['localhost', 5000])
thread.daemon = True
thread.start()


qt_app = QApplication([])
w = QWebView()
w.load(QUrl('http://localhost:5000'))
w.show()
qt_app.exec_()

setup.py

from cx_Freeze import setup, Executable

# Dependencies are automatically detected, but it might need
# fine tuning.
# When using flask-sqlalchemy add sqlite3.dll to include_files
buildOptions = dict(
    packages = [],
    excludes = [],
    include_files=[r'c:\miniconda\Library\plugins\platforms']
)

import sys
base = 'Win32GUI' if sys.platform=='win32' else None
# base = 'Console'

executables = [
    Executable('app.py', base=base)
]

setup(name='Test',
      version = '1.0',
      description = '',
      options = dict(build_exe = buildOptions),
      executables = executables)

As one can see this example was created on Windows. The include_files section in the setup.py is important as cx_freeze likes to miss the qt platform plugins which results in the application not working. At the end we get a nice executable shows our GUI which we can now create by using all nice features Flask gives us at hand. Lately I used the beautiful Flask-Admin to create a simple CRUD application in very few lines, which was really fun.