2

I downloaded and installed Python 3.10.6 on windows 10 pro, installed Shiny for Python, created the sample app and run it. This worked fine.

I installed pyinstaller and converted the app to an exe. I tried to run the app it threw (please see below).

Does anyone know if this can work and if so how?

This is the file2.spec that worked:

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


block_cipher = None
import os
# /c/Users/raz/AppData/Local/Programs/Python/Python310/Lib/site-packages/
shiny = os.path.abspath("../AppData/Local/Programs/Python/Python310/Lib/site-packages/shiny")


a = Analysis(
    ['file2.py'],
    pathex=[],
    binaries=[],
    datas=[('app.py', '/'), (shiny,'/shiny')],
    hiddenimports=[],
    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='file2',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

This below did not work:

raz@rays8350 MINGW64 ~/shiny
$ cat app.py
from shiny import App, render, ui

app_ui = ui.page_fluid(
    ui.h2("Hello Shiny!"),
    ui.input_slider("n", "N", 0, 100, 20),
    ui.output_text_verbatim("txt"),
)


def server(input, output, session):
    @output
    @render.text
    def txt():
        return f"n*2 is {input.n() * 2}"


app = App(app_ui, server)

raz@rays8350 MINGW64 ~/shiny
$


raz@rays8350 MINGW64 ~/shiny
$ ../AppData/Local/Programs/Python/Python310/Scripts/shiny.exe run --reload dist/app/app.exe
INFO:     Will watch for changes in these directories: ['C:\\Users\\raz\\shiny\\dist\\app']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [23368] using StatReload
Process SpawnProcess-1:
Traceback (most recent call last):
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\multiprocessing\process.py", line 314, in _bootstrap
    self.run()
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\multiprocessing\process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\site-packages\uvicorn\_subprocess.py", line 76, in subp
rocess_started
    target(sockets=sockets)
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\site-packages\uvicorn\server.py", line 60, in run
    return asyncio.run(self.serve(sockets=sockets))
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 646, in run_until_complet
e
    return future.result()
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\site-packages\uvicorn\server.py", line 67, in serve
    config.load()
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\site-packages\uvicorn\config.py", line 479, in load
    self.loaded_app = import_from_string(self.app)
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\site-packages\uvicorn\importer.py", line 24, in import_
from_string
    raise exc from None
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\site-packages\uvicorn\importer.py", line 21, in import_
from_string
    module = importlib.import_module(module_str)
  File "C:\Users\raz\AppData\Local\Programs\Python\Python310\lib\importlib\__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'app'
M--
  • 25,431
  • 8
  • 61
  • 93
Ray Tayek
  • 9,841
  • 8
  • 50
  • 90

1 Answers1

1

Okay... So here are the steps I took to make it work.

  1. open new directory (my_new_dir)
  2. python -m venv venv
  3. venv\scripts\activate.bat
    • or your OS's equivalent.
  4. pip install pyinstaller shiny
  5. open new file and paste this code in (file1.py)

file1

from shiny import App, render, ui
app_ui = ui.page_fluid(
    ui.h2("Hello Shiny!"),
    ui.input_slider("n", "N", 0, 100, 20),
    ui.output_text_verbatim("txt"),
)
def server(input, output, session):
    @output
    @render.text
    def txt():
        return f"n*2 is {input.n() * 2}"
app = App(app_ui, server)
  1. open a second new file next to the first (file2.py) and copy paste
file2.py
import os
import sys
from shiny._main import main
path = os.path.dirname(os.path.abspath(__file__))
apath = os.path.join(path, "file1.py")

# these next two lines are only if you are using Windows OS
drive, apath = os.path.splitdrive(apath)
apath = apath.replace("\\","/")
# 

sys.argv = ['shiny', 'run', apath]
main()
  1. pyinstaller -F file2.py
    • this will create a file2.spec file open it and make the changes in the code below:

file2.spec

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

block_cipher = None
import os
                       # use OS equivalent for path below
shiny = os.path.abspath("./venv/Lib/site-packages/shiny") 

a = Analysis(
   ...
   ...
   datas = [('file1.py', '/'), (shiny,'/shiny')]  # fill in the datas value
   ...
   ...

Last step:

  1. pyinstaller file2.spec

At the end of this your top level directory should look like this:

build
dist
venv
file1.py
file2.py
file2.spec

That is what worked for me. And the exe is in the dist folder.

If you want to change the name of the executable or the icon or any of that stuff that can all be done in the spec file and instructions can be found in the pyinstaller docs

Alexander
  • 16,091
  • 5
  • 13
  • 29
  • How did you know to include the path to `shiny` in the datas list located in `file2.spec`? – Chandler Tayek Aug 06 '22 at 07:26
  • 1
    @ChandlerTayek I tried it first without adding it and it didn't work... so I tried adding it and it did work. And also previous experiences using pyinstaller. I didn't even know what shiny was before reading this question. – Alexander Aug 06 '22 at 08:14
  • Awgh gotcha, so with pyinstaller if there are any problems reading paths for a package you probably want to include it as a data file? Also, I have been staring at your answer for an hour now and still can't figure out how file2.py helps kick off the shiny command. What made you think to include `from shiny._main import main` – Chandler Tayek Aug 06 '22 at 08:16
  • @ChandlerTayek To your first question the answer is yes, it's not a guarantee but it's always worth a try. To your second question, I went to shiny github page and looked for an easy entry point to the package in the shiny source code. – Alexander Aug 06 '22 at 08:19
  • Good point! How does pyinstaller know the entry point though? It doesn't show up in the .spec file does it? I haven't tried it out but I'm assuming the file2.py puts it in some of the other parameters not listed in your answer above? – Chandler Tayek Aug 06 '22 at 08:23
  • 1
    The spec file points to file2.py as the original source code that runs the exe. So no it's not included in the spec file, but if you deleted the file2.py the spec file wouldn't work of course once you are done with the last command none of them are needed all you need is the exe @ChandlerTayek – Alexander Aug 06 '22 at 08:25