Using PyInstaller to package our FastAPI

A simple way to deploy a micro-service is to create an executable. This way of packaging our code has several advantages :

  • No need to manage dependencies : Every dependency will

  • Ease of deployment : A single executable to run

  • Can be used for Serverless deployments : AWS lamda

PyInstaller

To package our API we will use PyInstaller

PyInstaller documentation : https://www.pyinstaller.org/en/stable/usage.html

Requirements

Install PyInstaller

pip install pyinstaller

Entry point

The code of our application is located in project/app/main.py. While in development run our application using uvicorn app.main:app --reload.

With PyInstaller we cannot simply generate an executable from this file, it will mess with Python imports, we need to create an entrypoint in the root folder of our project :

&> cat project/pyinstaller_entrypoint.txt

import uvicorn

from app.main import app

"""
This file is used by pyinstaller to build the distributable binary file.
It is at the project root folder to make sure that imports work properly.
"""

def serve():
    """Serve the web application."""
    uvicorn.run(app, port=8000)  # or whatever port number you want.


if __name__ == "__main__":
    serve()

This file launch the application from the root folder to ensure all our import statements work correctly.

Executable

Let’s create a single file executable from our entrypoint :

pyinstaller pyinstaller_entrypoint.py --name api --hidden-import=asyncpg.pgproto.pgproto --onefile

Note the --hidden-import=asyncpg.pgproto.pgproto, this is related to SqlAlchemy which imports are not detected by PyInstaller, without the hidden import our executable would fail.

Conclusion

It is very easy to create a self containing python executable, we can distribute and deploy our project easily without worrying for dependencies management.