Search code examples
pythonmultithreadingflaskwxpython

Please how do I compile a wxpython app that runs a flask server?


The wxpython application does not work as it is supposed to work after compiling it with pyinstaller. When I run the python code with the python interpreter, it works properly. But when it is bundled into an app.exe, web.html2 does not render the flask web pages properly. Here is the code:

# webview_app.py

import wx
import wx.html2
import threading
from flask import Flask, render_template


app = Flask(__name__)


@app.route('/')
def home():
    return """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bible Display Software</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: green;
        }
        .card {
            background-color: yellow;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            text-align: center;
        }
        h1 {
            margin: 0;
        }
    </style>
</head>
<body>
    <div class="card">
        <h1>Bible Display Software</h1>
    </div>
</body>
</html>

"""


class MainFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='WxPython Flask App', size=(800, 600))
        
        panel = wx.Panel(self)
        self.browser = wx.html2.WebView.New(panel)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.browser, 1, wx.EXPAND)
        panel.SetSizer(sizer)
        self.browser.LoadURL("http://127.0.0.1:5000")


def run_flask():
    app.run(host='127.0.0.1', port=5000)


def run_wx():
    wx_app = wx.App()
    frame = MainFrame()
    frame.Show()
    wx_app.MainLoop()


if __name__ == '__main__':
    flask_thread = threading.Thread(target=run_flask, daemon=True)
    flask_thread.start()  
    run_wx()

And below is the webview_app.spec file I tried.


#webview_app.spec
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

# Add the templates directory to the datas
added_files = [
    ('templates', 'templates'),  # (source_dir, dest_dir)
]

a = Analysis(
    ['webview_app.py'],
    pathex=[],
    binaries=[],
    datas=added_files,  # Include templates directory
    hiddenimports=['wx', 'wx.html2', 'wx._core', 'wx._html2', 'flask', 'werkzeug', 'jinja2'],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False
)

pyz = PYZ(
    a.pure,
    a.zipped_data,
    cipher=block_cipher
)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='WebViewApp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,
    disable_windowed_traceback=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    windowed=True
)

Solution

  • Pyinstaller would create a working EXE out of the box (brand-new, Python 3.13.1/PyInstaller 6.11.1), but it looked weird. Found out EXE uses wx.html2.WebViewBackendIE and not wx.html2.WebViewBackendEdge. Reason for this is PyInstaller does not copy the interfacing DLL \Lib\site-packages\wx\WebView2Loader.dll

    See this wxPython ticket for details

    What I did to get a EXE where Edge is used as backend

    rem change WINPYDIR to where your python is
    rem set WINPYDIR="C:\Program Files\python"
    pyinstaller ./app.py --distpath ./vdist -y --clean --contents-directory . --add-binary "%WINPYDIR%\Lib\site-packages\wx\WebView2Loader.dll:wx"
    

    Alternatively, the following was proposed on the wxPython Forum

    The absolute path can be avoided in the .spec file by:

    1. Add the following to the top of the .spec file
    from PyInstaller import HOMEPATH
    
    1. Use the relative path as follows:
    binaries=[(f’{HOMEPATH}/wx/WebView2Loader.dll’, ‘.’)],