pythonpython-3.xfastapiuvicorn

How to run Uvicorn FastAPI server as a module from another Python file?


I want to run FastAPI server using Uvicorn from A different Python file.

uvicornmodule/main.py

import uvicorn
import webbrowser
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

app = FastAPI()

import os
script_dir = os.path.dirname(__file__)
st_abs_file_path = os.path.join(script_dir, "static/")
app.mount("/static", StaticFiles(directory=st_abs_file_path), name="static")

@app.get("/")
async def index():
    return FileResponse('static/index.html', media_type='text/html')

def start_server():
    # print('Starting Server...')       

    uvicorn.run(
        "app",
        host="0.0.0.0",
        port=8765,
        log_level="debug",
        reload=True,
    )
    # webbrowser.open("http://127.0.0.1:8765")

if __name__ == "__main__":
    start_server()

So, I want to run the FastAPI server from the below test.py file:

from uvicornmodule import main
main.start_server()

Then, I run python test.py.

But I am getting the below error:

RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

What I am doing wrong? I need to run this module as package.


Solution

  • When spawning new processes from the main process (as this is what happens when uvicorn.run() is called), it is important to protect the entry point to avoid recursive spawning of subprocesses, etc. As described in this article:

    If the entry point was not protected with an if-statement idiom checking for the top-level environment, then the script would execute again directly, rather than run a new child process as expected.

    Protecting the entry point ensures that the program is only started once, that the tasks of the main process are only executed by the main process and not the child processes.

    Basically, your code that creates the new process must be under if __name__ == '__main__':. Hence:

    from uvicornmodule import main
    
    if __name__ == "__main__":
        main.start_server()
    

    Additionally, running uvicorn programmatically and having reload and/or workers flag(s) enabled, you must pass the application as an import string in the format of "<module>:<attribute>". For example:

    # main.py
    import uvicorn
    from fastapi import FastAPI
    
    app = FastAPI()
    
    if __name__ == "__main__":
        uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
    

    On a sidenote, the below would also work, if reload and/or workers flags were not used:

    if __name__ == "__main__":
        uvicorn.run(app, host="0.0.0.0", port=8000)
    

    Also, as per FastAPI documentation, when running the server from a terminal in the following way:

    > uvicorn main:app --reload
    

    the command uvicorn main:app refers to:

    • main: the file main.py (the Python "module").
    • app: the object created inside of main.py with the line app = FastAPI().
    • --reload: make the server restart after code changes. Only use for development.

    Note that the default host and port are 127.0.0.1 and 8000, respectively. You could use the --host and/or --port flag(s), in order to change the host and/or port of the server (have a look at all the available Uvicorn command line options, as well as this answer). Example:

    > uvicorn main:app --host 0.0.0.0 --port 8000