When building Fast API+Uvicorn environment with PyInstaller, console=False results in an error

Asked 1 years ago, Updated 1 years ago, 489 views

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

python pyinstaller fastapi ubicorn

2022-12-28 03:08

1 Answers

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


2022-12-28 06:43

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.