Search code examples
pythonflaskpyinstaller

Flask App Won't Run from PyInstaller Executable


I built a very simple Flask server with a single API route. It runs great if I just use python order_input_api.py. However, I am trying to build an executable file using PyInstaller. When I run the executable, if I send the exact same request to my API endpoint, it doesn't work.

Here is a simplified version of the order_input_api.py file:


    import sys
    from flask import Flask, request, jsonify
    from flask_cors import CORS
    import subprocess
    import socket
    import json
    import os
    
    app = Flask(__name__)
    CORS(app)
    
    
    def find_free_port():
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind(('localhost', 0))
            return s.getsockname()[1]
    
    
    def parse_output_data(output):
        # Returns json data
    
    
    @app.route('/process_orders', methods=['POST'])
    def process_orders():
        data = request.json
        packages_input = '\n'.join(data['packages']) + '\n' + 'done'
        python_executable = sys.executable
        process = subprocess.Popen([python_executable, 'order_counter.py'],
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   text=True)
        stdout, stderr = process.communicate(input=packages_input)
    
        if process.returncode == 0:
            return parse_output_data(stdout)
        else:
            return jsonify({'success': False, 'error': stderr}), 500
    
    
    os.chdir(app.root_path)
    
    if __name__ == '__main__':
        port = find_free_port()
        with open("port_info.json", "w") as f:
            json.dump({"port": port}, f)
        print(port)
        from waitress import serve
        serve(app, host='127.0.0.1', port=port)
    

With the way this is set up right now, after running the executable, if I send a POST request to the '/process_orders' endpoint, I never receive a response. However, I have tried several different things that have received errors:

  • Before implementing waitress, I was using the flask development server:

    if __name__ == '__main__':
        port = find_free_port()
        with open("port_info.json", "w") as f:
            json.dump({"port": port}, f)
        print(port)
        app.run(debug=True, host='127.0.0.1', port=port)

This resulted in the following response:


    {
        "error": " * Debugger is active!
                   * Debugger PIN: 454-500-849
                  Traceback (most recent call last):
                    File "order_input_api.py", line 63, in <module>
                    File "flask/app.py", line 615, in run
                    File "werkzeug/serving.py", line 1077, in run_simple
                    File "werkzeug/serving.py", line 917, in make_server
                    File "werkzeug/serving.py", line 784, in __init__
                  IndexError: string index out of range
                  [26517] Failed to execute script 'order_input_api' due to unhandled exception!",
        "success": false
    }

  • I then changed the port from find_free_port() to just 5000:

    "...
        Traceback (most recent call last):
          File \"order_input_api.py\", line 63, in <module>
          File \"flask/app.py\", line 615, in run
          File \"werkzeug/serving.py\", line 1077, in run_simple
          File \"werkzeug/serving.py\", line 917, in make_server
          File \"werkzeug/serving.py\", line 779, in __init__
          File \"socket.py\", line 544, in fromfd
        OSError: [Errno 9] Bad file descriptor
        [25777] Failed to execute script 'order_input_api' due to unhandled exception!
    ..."

  • I tried updating the .spec file and adding os.chdir(app.root_path) according to this link
  • I tried adding 'werkzeung' to the hiddenimports in the .spec file
  • I tried building the executable from within and outside of my virtual environment, and running the executable inside/outside of the venv
  • I checked all of the other StackOverflow posts about Flask/PyInstaller issues; most don't have answers or have different solutions that did not work for me

Every time I made a change I tried running the script itself (python order_input_api.py), sending a POST request to the endpoint, and it worked fine. Then running the executable and sending the same request fails.

I'm running out of ideas on how to debug or what the issue might be. Any help is greatly appreciated at this point.


Solution

  • I determined that the issue was with using subprocess.Popen and trying to call the python command (or sys.executable):

        python_executable = sys.executable
        process = subprocess.Popen([python_executable, 'order_counter.py'],
    

    Since this wasn't working, I just imported order_counter.py's main as a function and called it directly, and captured the output with contextlib's redirect_stdout:

    
        import io
        from contextlib import redirect_stdout
        from order_counter import main as order_counter
        
        @app.route('/process_orders', methods=['POST'])
        def process_orders():
            try:
                data = request.json
                packages_input = '\n'.join(data['packages']) + '\n' + 'done'
                input_stream = io.StringIO(packages_input)
                output_stream = io.StringIO()
                with redirect_stdout(output_stream):
                    sys.stdin = input_stream
                    order_counter()
                    sys.stdin = sys.__stdin__
                return parse_output_data(output_stream.getvalue())
            except Exception as e:
                return jsonify({'success': False, 'error': str(e)}), 500