This is a self-resolved issue, but I will post it for your reference.
Build an application using FastAPI+Uvicorn in the PyInstaller.
For debugging purposes, I was looking at the log output as console=True in the binaries built with PyInstaller and checking the execution status.
After passing the test, the production release approached, so when I set console=False and built it with PyInstaller, the following error occurred:
File "logging\config.py", line 543, in configure
File "logging\config.py", line 655, in configure_formatter
File "logging\config.py", line 474, in configure_custom
File "uvicorn\logging.py", line 47, in __init__
AttributeError: 'NoneType' object has no attribute 'isatty'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "multiprocessing\process.py", line 315, in_bootstrap
File "multiprocessing\process.py", line 108, in run
File "_app.py", line 75, in run
File "_app.py", line 51, in __init__
File "uvicorn\config.py", line 299, in __init__
File "uvicorn\config.py", line 407, in configure_logging
File "logging\config.py", line 809, indictConfig
File "logging\config.py", line 546, in configure
ValueError: Unable to configure formatter 'default'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "_app.py", line 120, in <module>
File "Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_multiprocessing.py", line 49, in_freeze_support
File "multiprocessing\spawn.py", line 116, inspawn_main
File "multiprocessing\spawn.py", line 129, in_main
File "multiprocessing\process.py", line 330, in_bootstrap
AttributeError: 'NoneType' object has no attribute 'write'
Similar issues include the following articles, but the problem does not change even if we take action.
In some cases, the following articles will also need to be addressed.
https://stackoverflow.com/questions/64281002/pyinstaller-compiled-uvicorn-server-does-not-start-correctly
Before declaring FastAPI objects, please include the following sentence:
Because the console option changes the presence or absence of errors, the problem was that FastAPI tried to output standard.
sys.stdout=open(os.devnull, 'w')
Note the folder configuration for the sample code.
The spec,PS1 file assumes the following folder tree:
(project folder)
+--- ENV (virtual environment folder created in venv)
|
\--- tests
\--- pyinstaller_fastAPI
| _ app.py
| app_release.spec
| pyinstaller_fastAPI.ps1
\--- app (PyInstaller Output Folder)
+---build
+--- dist
from multiprocessing import freeze_support, set_start_method, Process
from multiprocessing import Queue as MP_Queue
from fastapi import FastAPI
from physical import BaseModel
import pathlib
importos
import sys
from threading import Thread
import time
import tkinter as tk
from ubicorn import Config, Server
class ApiResult (BaseModel):
status —str
result —Dict
error:dict
sys.stdout=open(os.devnull, 'w')
api_port=8080
TERMINATE_SERVER = 'TERMINATE_SERVER'
api_title = 'test Pyinstaller & fast FPI'
api_version = '0.0'
app=FastAPI(
title=api_title,
description=api_title,
version=api_version
)
@ app.get(f"/test")
default_api_root():
result=ApiResult(
status = 'SUCCESS',
result = {'Welcome': api_title, 'version': api_version},
error={}
)
return result.dict()
class web_server_thread:
def_init__(
self, web_app, host:str, port:int, reload:bool=False):
self.config=Config(
app=web_app,
host=host,
port = port,
reload = reload,
# log_level = "warning",
)
self.server=Server(self.config)
self.server.install_signal_handlers=lambda: None
self.proc = None
def start (self):
self.th=Thread(name="WebServerThread", target=self.server.run)
self.th.setDaemon (True)
self.th.start()
def stop(self):
if self.th:
self.th.join (0.25)
class ApiServer():
def run(self,
recv_queue, host:str='127.0.0.1', port:int=8080):
self.instThread=web_server_thread(
app, host=host, port=port)
self.instThread.start()
isLoop=True
while(isLoop):
time.sleep(0.2)
while(not recv_queue.empty()):
item=recv_queue.get()
if item==TERMINATE_SERVER:
self.instThread.stop()
isLoop=False
return
class_DebugDialog():
def_init_(self, send_queue):
self.isLoop=True
self.after_interval = 200
self.send_queue = send_queue
self.dialog=tk.Tk()
self.dialog.protocol("WM_DELETE_WINDOW", self.quit)
self.dialog.after(self.after_interval, self.loop)
self.dialog.mainloop()
return
default(self):
self.isLoop=False
self.send_queue.put(TERMINATE_SERVER)
self.dialog.destroy()
return
def loop(self):
if self.isLoop:
self.dialog.after(self.after_interval, self.loop)
return
if__name__=='__main__':
freeze_support()
set_start_method('spawn')
send_queue = MP_Queue()
instServer=ApiServer()
instProcess=Process(
name = 'FastAPI Server',
target = instServer.run,
args=(send_queue,),
kwargs = {
'port': api_port
}
)
instProcess.daemon=True
instProcess.start()
_DebugDialog(send_queue)
#-*-mode:python-*-
importos
from PyInstaller.utils.hooks import collect_submodules
current_dir=os.getcwd()
work_dir=current_dir
source_dir = current_dir + '\\tests\pyinstaller_fastAPI'
hiddenimports=collect_submodules('uvicorn')
block_cipher=None
a = Analysis(
[source_dir+'\\_app.py'],
pathx=[],
binaries=[],
datas=[],
hiddenimports = hiddenimports,
hookspath=[],
runtime_hooks=[],
exclude=[],
win_no_prefer_redirects=False,
win_private_assembly=False,
cipher=block_cipher,
noarchive=False
)
pyz=PYZ(a.pure, a.zip_data, cipher=block_cipher)
options=[('--uac-admin', None, 'OPTION')]
exe=EXE(pyz,
a.scripts,
options,
exclude_binaries = True,
name = 'pyinstaller_fastAPI',
debug = False,
bootloader_ignore_signals=False,
strip=False,
upx = True,
console=False,
)
col=COLLECT(exe,
a. binaries,
a.zipfiles,
a.datas,
strip=False,
upx = True,
name = 'pyinstaller_fastAPI')
# indicate the current directory of the terminal
$PS_FILE_PATH = $MyInvocation.MyCommand.Path
$PS_FILE_DIR = Split-Path $PS_FILE_PATH-Parent
$_FILE_NAME_=Split-Path $PS_FILE_PATH-Leaf
$WORK_DIR = Split-Path $PS_FILE_DIR-Parent
$WORK_DIR = Split-Path $WORK_DIR-Parent
Set-Location-Path$WORK_DIR
$CURRENT_DIR = Convert-Path.
$CURRENT_DIR = Split-Path $CURRENT_DIR-Parent
$PROJECT_NAME="Test_PyInstaller"
$PYTHON_PATH=$WORK_DIR+'\ENV\Scripts\python.exe'
$PYINSTALL_DIR = $WORK_DIR + '\tests\pyinstaller_fastAPI'
$DIST_PATH=$PYINSTALL_DIR+'\app\dist'
$BUILD_PATH=$PYINSTALL_DIR+'\app\build'
$PROJECT_PATH=$DIST_PATH+'\'+$PROJECT_NAME
$RET_CODE = 0
$LOG_FILE_NAME=[System.IO.Path]::GetFileNameWithoutExtension("$_FILE_NAME_")
$LOG_FILE_PATH = $PS_FILE_DIR+'\'+$LOG_FILE_NAME+'.log'
Start-Transcript $LOG_FILE_PATH
write-host" Executable File Name: "$PS_FILE_PATH
write-host "current directory:"$CURRENT_DIR
write-host" working directory: "$WORK_DIR
write-host-NoNewline" Project Name: "$PROJECT_NAME
write-host" ■ Generate a standalone app."
$ErrorActionPreference="Stop"
try{
Remove-Item-Path$DIST_PATH-Recurse-Force
Removed write-host-NoNewline" directory: "$DIST_PATH
}
catch{
write-host-NoNewline"Delete directory not found. :"$DIST_PATH
}
try{
Remove-Item-Path$BUILD_PATH-Recurse-Force
Removed write-host-NoNewline" directory: "$BUILD_PATH
}
catch{
write-host-NoNewline "Delete directory not found.:"
write-host$BUILD_PATH
}
# Build in Standalone Run Format
write-host" ■ Start building in standalone execution format."
$PYINSTALL=$PYTHON_PATH+"-m PyInstaller$PYINSTALL_DIR\app_release.spec --distpath$DIST_PATH --workpath$BUILD_PATH"
Invoke-Expression $PYINSTALL
write-host" ■ Standalone executable build completed."
write-host "Press any key."
Console :: ReadKey($true) | Out-Null
write-host "Exit."
Stop-Transcript
exit$RET_CODE
© 2024 OneMinuteCode. All rights reserved.