Fast API
Fast API
FAST!
by Thom Ives, PhD.
Recently, I did a series of LinkedIn posts on creating a Python REST API with FastAPI and
Flask. FastAPI won thanks to Patrick Pichler's recommendation of FastAPI and my
subsequent investigations. FastAPI works great for Python based REST APIs. I am now
successfully using FastAPI in my work at my company. I think that most of you can leverage
from this simple API and take it much further. I also wanted to share in detail how to release
a basic model serving REST API using FastAPI.
For learning FastAPI, FastAPI has great tutorials. Just go to the main FastAPI Tiangolo
pages, and you will find all that you need. I hope that you will check them out. I went through
the main one and then through all the tutorials. If you are accustomed to Flask like me, this
may take some getting accustomed to. However, once you are a few steps in, you'll be sold
by their automatically created documentation pages for the APIs that you write. These
documentation pages can also be used to test your APIs. That's right. You don't need to
immediately write your own HTTP method call scripts. Nor do you need to use something like
Postman. It's all built right in.
Prerequisites
I recommend that we first create or activate our preferred Python virtual environment. Once
you are in there, you will want to do the following pip installations. The first one, I trust, is
obvious. The second one is the package that will serve your API. Note that it's a different
package than the one used by Flask. The third and last is simply the quickest way to obtain
all the FastAPI goodies available.
NOTE that I am still learning FastAPI, and I still have some questions about the best way to
handle things. However, I think this code structure is good and safe. This is a basic starting
point, but it can serve MANY basic model delivery needs.
I do at least recommend that you become familiar with decorators through some web
searching if they are foreign to you. An example of a decorator below is
@app.post("/run_model") . I like to think of decorators as a Pythonic elegant way to
have Python wrap the functions below the decorator in a standard well defined way. They will
seem magic until you study how to create your own. To me, they are one of those elegant
Pythonic things like context managers. I very much appreciate them.
Imports
We will need Pickle to store our model after we train it. Of course we will want to import
FastAPI from fastapi. The BaseModel from Pydantic is a great. I won't say much about
Pydantic here, but PLEASE study it more. It's a major helpful set of data model classes that
help FastAPI work like a charm. I've noticed that FastAPI practitioners love using Pydantic
data models. We will need a type of list in the BaseModel that we will create for the data that
we pass in.
In [ ]: import pickle
Necessary Declarations
Next, we declare a data class using Pydantic's BaseModel.
In [ ]: class Features(BaseModel):
note: str
data: List[float]
Note that we are loading a saved model file. The code to create the model and save it will be
shown below soon.
IF you've done some Python based API work in the past, the app = FastAPI()
statement should be expected. It is essential if this is completely new to you. We instantiate a
class instance of FastAPI named app.
In [ ]: model_file_name = "Linear_Regression_Model.pkl"
with open(model_file_name, 'rb') as f:
mod_LR = pickle.load(f)
app = FastAPI()
When building APIs, we normally use the following specific HTTP methods:
For this basic model delivery REST API that takes inputs and provides predictions, we will
only use the POST operation. I do have a tutorial on how to use all of these basic operations.
I will release that soon.
Our POST method is expecting a features input of type Features defined previously using
Pydantic. We'll see shortly how to enter this and make calls for it from both the automatically
generated documentation for this API and from python scripts.
1. we pass in a note that is a string that is only used for documentation about the
prediction,
2. we pass in the list of three float values for the features and use them in the call to the
prediction algorithm,
3. we load the prediction into the return data structure along with the note,
Note that for our case, run_model is what is know as a route. We don't really need it here,
but I am including it, because, later, we might add GET, PUT, and DELETE operations to this
overall API too, and the run_model route will help to distinguish this operation from the
other future ones.
In [ ]: @app.post("/run_model")
async def run_model(features: Features):
print( {
'note': features.note,
'list': features.data
} )
Y_pred = mod_LR.predict([features.data])
print(Y_pred)
return {
"note": features.note,
"value": Y_pred[0, 0]
}
The entire API code is shown below. I've named this file Run_Model_API.py.
In [ ]: # Run_Model_API.py
import pickle
class Features(BaseModel):
note: str
data: List[float]
model_file_name = "Linear_Regression_Model.pkl"
with open(model_file_name, 'rb') as f:
mod_LR = pickle.load(f)
app = FastAPI()
@app.post("/run_model")
async def run_model(features: Features):
print( {
'note': features.note,
'list': features.data
} )
Y_pred = mod_LR.predict([features.data])
print(Y_pred)
return {
"note": features.note,
"value": Y_pred[0, 0]
}
In [ ]: # Basic_Fake_Data_Model_Test_And_Measure.py
import numpy as np
import sklearn.metrics as sklm
import math
import pickle
Break the test data into features and labels (inputs and outputs)
X_test = feature_label_data[:, :-1]
Y_test = feature_label_data[:, -1]
Implementation
Well, that looks all wonderful in theory, but does this work? First, we have to start this script
and make sure it launches on our local server using uvicorn. Then, we need to test each
method. Let's test each method two different ways. Using the automatically generated
documentation for this API, and using requests from our fake user's Python script.
What does it look like when we start our API from the command line. AND how do we do
that? Well, let me show you! We start our API from a command line terminal using the
following line. NOTE that whatever the python script name of your API is, that's what you'd
put in for Run_Model_API minus the .py part.
If successful, your uvicorn server launch will look like the following:
(py38std) thom@thom-PT5610:~/DagsHub_Repos/API_Dev_Work
/API_Dev_Work_Public/Basic_Model_Serving_API$ ./run
INFO: Will watch for changes in these directories: ['/home
/thom/DagsHub_Repos/API_Dev_Work/API_Dev_Work_Public
/Basic_Model_Serving_API']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C
to quit)
INFO: Started reloader process [3238637] using watchgod
INFO: Started server process [3238639]
INFO: Waiting for application startup.
INFO: Application startup complete.
If you are running, great! If not, go back through this document carefully, OR step through the
FastAPI tutorials until you can find your issue. Many people forget to make sure their terminal
is looking at the same directory that their API script is in. You may also have a typo in your
I hope it runs for you, because once it does, the rest of this will likely go pretty smoothly for
you.
Next, let's open the amazingly good automatically generated document page for our API
using the below line. If you ever used PostMan for testing APIs, this is much the same, but
specific to FastAPI and makes FastAPI easy to test and debug!
http://127.0.0.1:8000/docs
Expand the POST block and click on "Try it out" at the upper right of that expanded block.
"Try it out" will switch to "Cancel" in case you decide you do NOT want to try it out right now.
You will see a "Request body". Edit the dictionary in that "Request body" as shown in the
next image.
Now, we click on wide blue Execute button. We want to then check on two things. First, in
this expanded POST block, scroll down a bit, and you will see the response that you
formulated in your return statement if all went well. I have shown mine below.
X_test = pd.read_csv("X_Test_Data.csv")
X_test = X_test.values
data_file_name = "Test_Data.npz"
with open(data_file_name, 'rb') as f:
feature_label_data = np.load(f)
Y_pred_from_api = []
features = {
"note": str(note),
"data": data
}
API_URL = "http://127.0.0.1:8000/run_model"
response = requests.post(API_URL, json=features)
output = json.loads(response.text)["value"]
Y_pred_from_api.append(output)
Y_pred = np.array(Y_pred_from_api)
print_metrics(Y_test, Y_pred, 4)
(py38std) thom@thom-PT5610:~/DagsHub_Repos/API_Dev_Work
/API_Dev_Work_Public/Basic_Model_Serving_API$ /home/thom
/.virtualenvs/py38std/bin/python /home/thom/DagsHub_Repos
/API_Dev_Work/API_Dev_Work_Public/Basic_Model_Serving_API
/Call_Model_Run_API.py
Mean Square Error = 0.0008951082926707904
Root Mean Square Error = 0.029918360460940877
Mean Absolute Error = 0.023989555548717817
Median Absolute Error = 0.020721802574338644
R^2 = 0.9991017552979535
Adjusted R^2 = 0.9990880066545549
And these results are the same as the results we obtained when running
Basic_Fake_Data_Model_Test_And_Measure.py.
Summary
We used Python FastAPI to create a REST API to provide model predictions using a trained
model. Dang that was fun! We discovered some great new power and methods using
FastAPI. I am eager to show you more examples with FastAPI ASAP!