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:
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
}
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!
..."
.spec
file and adding os.chdir(app.root_path)
according to this link'werkzeung'
to the hiddenimports
in the .spec
fileEvery 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.
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