toddbirchard / plotlydash-flask-tutorial

📊📉 Embed Plotly Dash into your Flask applications.

Home Page:https://hackersandslackers.com/plotly-dash-with-flask/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Packaging as standalone application (pyinstaller)

mcrone opened this issue · comments

Thank you for a really awesome tutorial!

I'd like to package this as an application using pyinstaller. The straightforward approach doesn't seem to work (perhaps because of the directory structure), have you had any experience with this?

I've answered my own question and I should have persevered a little more, but hopefully this solution will help others.

Importantly it was necessary to explicitly import the config.py file into the root directory (because it is only imported as an object by the flask environment and therefore was not automatically added by pyinstaller). It is also important to explicity import the dash modules into the root directory.

Here is my spec file for pyinstaller:

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['wsgi.py'],
             pathex=['/Users/xxx/plotlydash-flask'],
             binaries=[],
             datas=[('config.py', '.'), ('data', 'data'), ('plotlyflask_tutorial', 'plotlyflask_tutorial'),('/usr/local/lib/python3.9/site-packages/dash_html_components','dash_html_components'),('/usr/local/lib/python3.9/site-packages/dash_core_components','dash_core_components'),('/usr/local/lib/python3.9/site-packages/plotly','plotly'),('/usr/local/lib/python3.9/site-packages/dash_table','dash_table'),('/usr/local/lib/python3.9/site-packages/dash_renderer','dash_renderer'),('/usr/local/lib/python3.9/site-packages/dash','dash')],
             hookspath=[],
             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,
          [],
          exclude_binaries=True,
          name='wsgi',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='wsgi')

I also placed this into init.py:

"""
Simple module that monkey patches pkg_resources.get_distribution used by dash
to determine the version of Flask-Compress which is not available with a
flask_compress.__version__ attribute. Known to work with dash==1.16.3 and
PyInstaller==3.6.
"""

import sys
from collections import namedtuple

import pkg_resources

IS_FROZEN = hasattr(sys, '_MEIPASS')

# backup true function
_true_get_distribution = pkg_resources.get_distribution
# create small placeholder for the dash call
# _flask_compress_version = parse_version(get_distribution("flask-compress").version)
_Dist = namedtuple('_Dist', ['version'])

def _get_distribution(dist):
    if IS_FROZEN and dist == 'flask-compress':
        return _Dist('1.8.0')
    else:
        return _true_get_distribution(dist)

# monkey patch the function so it can work once frozen and pkg_resources is of
# no help
pkg_resources.get_distribution = _get_distribution

There are also some tricks if using a database because pyinstaller uses an absolute rather than relative path. I got around this with this piece of code which goes up two directories (using '..') from the location of the dash libraries.

FN = path.abspath(path.join(path.dirname(__file__), '..' , '..' ,"data", "run_data.db"))