Using PyInstaller (or similar) to create an executable

Hi, I want to build a one-click executable out of my code that replicates the terminal command:

streamlit run mycode.py

Is there any way to achieve this with PyInstaller or similar? The main motivation is to share the functionality of my app with others who only use windows or who only use computers where python (along with the required imports) is not installed.

Hi @s_mc and welcome to the forum! :wave:

The best way to share would be to run streamlit in a server and share the app url. Another option would be to create a docker image with your code and share the image, but this docker option could be a little complex for individuals who are using your app.

Currently, we don’t have a way to internally deploy your app within Streamlit, but we are working on a “For Teams” offering that will add this functionality. If you’re interested in hearing more, here is a link where you can sign-up to get updates as they become available.

In the meantime, here is a link to a post that goes over some community driven ways to host your app.

Hope this helps answer your question, and feel free to let us know if this isn’t the functionality you’re looking for! :smiley:

UPDATE Not yet solved, but almost, further help appreciated!!!

I created the following wrapper script to allow any script utilitzing streamlit to be run by calling
python streamlitWrapper.py.

import runpy
runpy.run_module('streamlit.cli')
import streamlit.cli
import click
click.pass_context
if __name__ == '__main__':
    streamlit.cli._main_run_clExplicit('pythonScript.py', 'streamlit run')

Save the 7 lines above into another script called streamlitWrapper.py (or any other name). You will also need to edit the file cli.py contained in the streamlit distribution to include the following def:

def _main_run_clExplicit(file, command_line, args=[]):
    streamlit._is_running_with_streamlit = True
    Credentials.get_current().check_activated(auto_resolve=True)
    if version.should_show_new_version_notice():
        click.echo(NEW_VERSION_TEXT)
        bootstrap.run(file, command_line, args)

After all that, you can run your script using the usual python call python streamlitWrapper.py.

The next step is to run:

pyinstaller streamlitWrapper.py

And with some massaging you should receive an executable.

The problem now is, the pyinstaller executable starts streamlit on port 3000 instead of port 8501 and my browser (for reasons I don’t understand) can’t run my script when using streamlitWrapper.exe (this is the default name of the executable pyinstaller will produce), running python streamlitWrapper.py works exactly the same as when calling streamlit run pythonScript.py, but for some reason the pyinstaller executable doesn’t exactly replicate the functionality you get when running python streamlitWrapper.py.

1 Like

I have discovered the reason why when using the bundled executable my web browser doesn’t load properly, it is due to a check performed in streamlit to establish whether streamlit was properly installed. Basically everytime streamlit is run, at some time the following is executed in config.py:

@_create_option("global.developmentMode", visibility="hidden", type_=bool)
    def _global_development_mode():
        """Are we in development mode.

        This option defaults to True if and only if Streamlit wasn't installed
        normally.
        """
        return (
            not util.is_pex()
            and "site-packages" not in __file__
            and "dist-packages" not in __file__
        )

This is called from Report.py which has the effect of setting the port to 3000:

def _get_browser_address_bar_port():
    """Get the report URL that will be shown in the browser's address bar.

    That is, this is the port where static assets will be served from. In dev,
    this is different from the URL that will be used to connect to the
    server-browser websocket.

    """
    if config.get_option("global.developmentMode") and config.get_option(
        "global.useNode"
    ):
        return 3000
    return config.get_option("browser.serverPort")

This call works normally when using either streamlit run pythonScript.py or python streamlitWrapper.py but (for whatever reason), once pyinstaller bundles streamlit into the executable, the above check fails and sends streamlit into development mode, and things stop working as expected. This is obviously the wrong behaviour on streamlit’s part, pyinstaller has almost certainly bundled everything correctly but streamlit is looking in the wrong place to check if it has been installed correctly. What do I need to modify to ensure that streamlit is satisfied that it has been properly installed when running from an executable? I get the feeling that modifying def _global_development_mode(): to always return True is probably a dangerous idea.

2 Likes

Wow, great detective work @s_mc!

I think there’s some work Streamlit devs can do to make this easier in the future, but for now it sounds like all that’s left for you is to turn global.developmentMode off — which happens to be quite simple :smiley:

Here are a few ways to do it:

  • Add the code below to either $HOME/.streamlit/config.toml or .streamlit/config.toml (in the folder you’re running Streamlit from)

    [global]
    developmentMode = false
    
  • Pass --global.developmentMode=false to the streamlit run command

  • Set the STREAMLIT_GLOBAL_DEVELOPMENT_MODE environment variable to false

Let me know how it goes!