Run application#
This page provides a detailed explanation of the options available for running a FastAPI application.
import uvicorn
import requests
from fastapi import FastAPI
from multiprocessing import Process
Image#
Here we’ll focus on the image that we’ll primarily use for experimenting with FastAPI. This is the default image used in most FastAPI examples.
Dockerfile#
In the next cell is the docker file I am using for this example.
%%writefile run_application_files/dockerfile
FROM python:3.11
COPY requrements.txt requrements.txt
RUN pip3 install -r requrements.txt
EXPOSE 8000
Overwriting fastapi/run_application_files/dockerfile
requrements.txt
#
Python libraries you only needed to run the fastapi
server. It is supposed to be used in the dockerfile described above.
%%writefile fastapi/run_application_files/requrements.txt
fastapi==0.103.1
uvicorn==0.23.2
Overwriting fastapi/run_application_files/requrements.txt
Build the image#
Image with name fastapi_experiment
, created in the following cell, will be used in the other subsections of the fastapi section.
!docker build -t fastapi_experiment\
./fastapi/run_application_files/ &> /dev/null
Runtime update#
The most convenient approach to experimenting with a container that contains fastapi is to dynamically swap the program, enabling the execution of multiple examples within a single container. For this purpose, I typically establish a connection between the utilized execution Python file and the container as a volume. This ensures that any changes made on the computer are promptly reflected within the container.
To implement this functionality, it is necessary to run uvicorn with the --reload
flag. This flag enables uvicorn to monitor changes in the program and update accordingly.
The following cells start an application that returns initial line
.
%%writefile ./run_application_files/reload.py
from fastapi import FastAPI
my_first_app = FastAPI()
@my_first_app.get("/")
def say_hello():
return "initial line"
Overwriting ./run_application_files/reload.py
Note that the uvicorn
call here includes the --reload
option.
!docker run --rm -itd\
--name test_container\
-v ./run_application_files/reload.py:/reload.py\
-p 8000:8000 \
fastapi_experiment \
uvicorn --host 0.0.0.0 --reload reload:my_first_app
f70c9566a8c067d1d20008d9204876e5696a48823218b5a6ac02aa12458a59a8
As expected, a request to this API returns initial line
.
requests.get("http://localhost:8000/").content
b'"initial line"'
Now, without restarting the container, simply change the file containing your application - it will now return updated line
.
%%writefile ./run_application_files/reload.py
from fastapi import FastAPI
my_first_app = FastAPI()
@my_first_app.get("/")
def say_hello():
return "updated line"
Overwriting ./run_application_files/reload.py
By checking api result we’ll got updated line
.
requests.get("http://localhost:8000/").content
b'"updated line"'
Therefore, the API was refreshed without restarting the container.
!docker stop test_container
test_container
Run as __main__
#
Sometimes it’s useful to be able to start a program by simply running it as a Python script, i.e. without bothering to call uvicorn
.
All you have to do is run uvicorn.run(<fast_api object>, ...)
.
The next cell shows a program using such an approach.
%%writefile ./run_application_files/reload.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def say_hello():
return "I'm started from __main__"
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Overwriting ./run_application_files/reload.py
To run it now we just use the command python3 <script_name>.py
.
!docker run --rm -itd\
--name test_container\
-v ./run_application_files/reload.py:/reload.py\
-p 8000:8000 \
fastapi_experiment \
python3 reload.py
054cc3956c27ad9c72c3ec7e960ccd92eed4b8ac9544c751af6040a6dab3b2db
Let’s ask api to make sure everything is working.
!curl localhost:8000
"I'm started from __main__"
!docker stop test_container
test_container
Test client#
FastAPI provides a special tool, fastapi.testclient.TestClient
, for testing applications. This approach is valuable because it allows you to run tests in a different software environment, which can be very useful for research purposes.
The following cell creates fastapi appliction but not saves it to file - it creates fastapi relate objects just in jupyter environment.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def index():
return "hello from test client"
With fastapi.testclient.TestClient
, you can get special object which actually interface that allows to interact with created application. The following cell shows requesting to the /
endpoint from TestClient
object.
from fastapi.testclient import TestClient
test_client = TestClient(app = app)
display(test_client.get("/").content)
test_client.close()
b'"hello from test client"'
As a result, we get a response exactly as defined by the API.
Process#
An alternative way to start a Python process is to use multiprocessing.Process
. You need to specify target=uvicorn.run
, along with args
and kwargs
to match the arguments you would use for the program.
The following cell shows how it can be achieved.
app = FastAPI()
@app.get("/")
def index():
return "I'm a python process"
proc = Process(
target=uvicorn.run, args=(app,), kwargs={"log_level": "info"}, daemon=True
)
proc.start()
INFO: Started server process [271008]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [271008]
Note: Jupyter prints the stdout of the process, so you’ll see the application logs in your notebooks. To prevent this, set the log_level
keyword argument to critical
.
To ensure that everything is functioning properly, we request an application, followed by terminating the process.
print(requests.get("http://localhost:8000").content)
proc.terminate()
INFO: 127.0.0.1:58210 - "GET / HTTP/1.1" 200 OK
b'"I\'m a python process"'
httpx.AsyncClient
#
Run application through httpx.AxsyncClient
is another option. You can just open asynchronomous context manager and all code in it will be able to reques the application under consideration.
The following cell shows how to create a “Hello World” application with FastAPI and run it using httpx.AsyncClient
.
from httpx import AsyncClient
app = FastAPI()
@app.get("/")
def index():
return "hello world"
async with AsyncClient(app=app, base_url="http://hello") as ac:
print(((await ac.get("/")).content))
b'"hello world"'