How to create an API in Python

As a data scientist or data engineer, many times we have to share our work so that anyone else in the company can use the processes or models that we have created. Clearly, sharing a script is not an option, since everyone would need to have those same programs as you. This is when APIs come into play. Today, we are going to see what they are and how to create an API in Python. Sounds interesting? Well let’s get to it!

API Basics

An API (Application Programming Interface) allows two computer systems to interact with each other. For example, if we create an automation that generates a report and sends it by email, the sending of that email is not done manually, the script itself will do it. To do this, Python (or the language we use), must ask Gmail to send that email, with that report attached to certain people. The way to do it is through an API, in this case the Gmail API.

Ok, now that you know what an API is, let’s see what the main parts of an API are:

  • HTTP transfer protocol : it is the main way of communicating information on the web. There are different methods, each of them used for different issues:
    • GET: this method allows obtaining information from the database or from a process.
    • POST: allows you to send information, either to add information to a database or to pass the input of a machine learning model, for example.
    • PUT: update information. It is generally used to manage information in the database.
    • DELETE: this method is used to delete information from the database.
  • Url: is the address where we can find our API. Basically this URL will consist of three parts:
    • Protocol: like any address, it can be http:// or https://.
    • Domain: the host on which it is hosted, which goes from the protocol to the end of .com, or whatever ending the url has. On my website, for example, the domain is anderfernandez.com.
    • Endpoint : like a website has several pages (/ blog), (/ legal), the same API can include multiple points and each one does different things. When creating our API in Python we will indicate the endpoints, so we must make sure that each enpoint is representative of what the API behind it does.

Ok, now that we know what an API is and what its main parts are, let’s see how we can create an API in Python. Let’s get to it!

How to create an API in Python

There are different ways to create an API in Python, the most used being FastAPI and Flask. So, I will explain how both work, so that you can use the way to create APIs in Python that you like the most. Let’s start with FastAPI.

How to create an API in Python with FastAPI

Requirements to use FastAPI

FastAPI is a way of creating APIs in Python that came out at the end of 2018. It is very fast, although it can only be used with Python 3.6+ (in my opinion this should not be a problem, but it is important).

To use it, you must install two libraries: fastapi and uvicorn .

pip install fastapi
pip install uvicorn

Your first Python API with FastAPI

Now that we have installed the packages, we simply have to create a file in Python where we will define our API. In this file, we must create an app, where we will include the APIs, with their endpoints, parameters, etc.

Once we have the app, that’s where we define the information that the API requires: endpoint, HTTP method, input arguments and what the API will do behind it.

from fastapi import FastAPI
app = FastAPI()

@app.get("/my-first-api")
def hello():
  return {"Hello world!"}

With this we would already have a very simple API created, which simply returns “Hello world!”. As you will see, in a few lines we have defined: the method (get), the endpoint (“/”) and the function that this API should run.

We could even pass arguments to our API, for it to use in its function. Whenever we pass an argument to our function we must indicate the type of data that it must be (number, text, etc.).

Important : FastAPI performs a check that the type of data that we pass to it in the call is what we have indicated it should be. This is essential to ensure that our API works properly, and it is something that other API creation frameworks (like Flask) do not include.

Let’s see how it works in an example:

from fastapi import FastAPI
app =from fastapi import FastAPI
app = FastAPI()

@app.get("/my-first-api")
def hello(name: str):
  return {'Hello ' + name + '!'} 

Now, when we make the request to this API, we will have to pass the name parameter to it for it to work. That is, if previously it was enough for us to go to http://127.0.0.1:8000/my-first-api, now we will have to pass the name parameter. Thusthe request will look as follows: http://127.0.0.1:8000/my-first-api?name=Ander .

As we have included the name argument, this argument is required: if it is not included in the request, it will not work. However, we may want to pass optional arguments.

For this, we have to indicate the argument as None and FastAPI will interpret it properly. Example: we are going to create an API to which you can pass, or not, the variable name. If you pass it, it will return “Hello {name}!” and, if not, simply “Hello!”.

from fastapi import FastAPI

app = FastAPI()

@app.get("/my-first-api")
def hello(name = None):

    if name is None:
        text = 'Hello!'

    else:
        text = 'Hello ' + name + '!'

    return text

In this case, if we go to http://127.0.0.1:8000/my-first-api the API will be executed correctly and it will return “Hello!”, while if we pass the parameter, for example, http://127.0.0.1:8000/my-first-api?name=Ander , it will use it and it will continue to work correctly, returning Hello Ander!.

Return different data types with FastAPI

The vast majority of times an API usually returns text (a prediction, data, etc.), although many times it can return other types of data, such as a DataFrame or an image, for example.

When it comes to “normal” objects, like a DataFrame, FastAPI will convert it directly to a JSON file. Example:

from fastapi import FastAPI

app = FastAPI()

@app.get("/get-iris")
def get_iris():

    import pandas as pd
    url ='https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv'
    iris = pd.read_csv(url)

    return iris

If we make a request to this endpoint, we will receive the following response:

resp.text
'{"sepal_length":{"0":5.1,"1":4.9,"2":4.7,"3":4.6,"4":5.0,"5":5.4,"6":4.6,"7":5.0,"8":4.4,"9":4.9,"10":5.4,"11":4.8,"12":4.8,"13":4.3,"14":5.8,"15":5.7,"16":5.4,"17":5.1,"18":5.7,"19":5.1,"20":5.4,"21":5.1,"22":4.6,"23":5.1,"24":4.8,"25":5.0,"26":5.0,"27":5.2,"28":5.2,"29":4.7,"30":4.8,"31":5.4,"32":5.2,"33":5.5,"34":4.9,"35":5.0,"36":5.5,"37":4.9,"38":4.4,"39":5.1,"40":5.0,"41":4.5,"42":4.4,"43":5.0,"44":5.1,"45":4.8,"46":5.1,"47":4.6,"48":5.3,"49":5.0,"50":7.0,...

As you can see, Fast API converts the Data Frame directly into a JSON object. However, what about images or videos?

FastAPI is based on starlette, so in order to respond with images or icons, we can use both StreamingResponse and FileResponse. In any case, we will need to install aiofiles.

So, if we want to display an image in our API, our FastAPI application will look like this:

@app.get("/plot-iris")
def plot_iris():

    import pandas as pd
    import matplotlib.pyplot as plt

    url ='https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv'
    iris = pd.read_csv(url)

    plt.scatter(iris['sepal_length'], iris['sepal_width'])
    plt.savefig('iris.png')
    file = open('iris.png', mode="rb")

    return StreamingResponse(file, media_type="image/png")

As we can see, creating an API in Python with FastAPI is very simple and very versatile. But how can I check that my API is working properly? Let’s see it!

Check the operation of a FastAPI API

As I said at the beginning of this section, to create an API in FastAPI we must include our code in a Python file, preferably main.py. Also, we must have uvicorn installed. Taking this into account, we can run our API in a very simple way, with the following code:

uvicorn main:app --reload

This will run our application and we will be able to access our API, both at the browser level and by making calls from our computer. The API has not been uploaded anywhere yet, so it will be accessible from localhost. You can access the localhost both in http://127.0.0.1/ and in http://localhost/, although you will have to do it in the same port that is running the API. To find out, in my opinion, the easiest thing is to use the link that uvicorn will give you when executing the API:

How to create APIs in Python

Let’s see for example how I request an API that includes all the endpoints that I have previously explained.

import requests
from PIL import Image
import io

resp = requests.get('http://127.0.0.1:8000/plot-iris')
file = io.BytesIO(resp.content)
im = Image.open(file)
im.show()

As we can see, our API is returning the image to us correctly. Let’s now try the first endpoint that we have created:

resp = requests.get('http://127.0.0.1:8000/my-first-api?name=Ander')
resp.text
'"Hello Ander!"'

In addition, FastAPI itself creates the documentation in swagger, which we can access from the path, docs. In my case, as the API is exposed in, I can access http://127.0.0.1:8000/docs, as seen in the following image:

How to create APIs in Python

Besides, there is also another way to document the API, with OpenAPI, which is accessible through the /redoc endpoint, as we see below:

How to create APIs in Python

As you can see, creating an API with FastAPI is very very simple, very fast and intuitive. However, FastAPI is not the only way to create an API in Python. Another way to do it is by using flask. Let’s see how it works!

How to create an API in Python with Flask

How to create an API with Flask

First of all, in order to create an API in Python using Flask we must install the flask and flask-restful packages. Once we have the libraries installed, we have to to create our server, as we did with FastAPI. We can do this with the following command:

from flask import Flask

app = Flask()  

In addition, we will have to indicate that the server to launch together with the port where it should be launched. To do this, at the end of our app we must include the following code:

if __name__ == '__main__':
    app.run(debug=True, port=8000)

Once we have our server created and we have indicated on which port it should run, we can start creating our APIs. To create an API in Python with Flask, we have to indicate: the endpoint, the method and the function that should be executed on that endpoint. Let’s see an example with an API that simply returns the text “Hello world!”.

from flask import Flask, jsonify, request,send_file

app = Flask()

@app.route('/my-first-api', method = ['GET'])

def hello():

    return "Hello world!"

As you can see, in this case, we define both the endpoint and the method when we define the server path with @app.route. In my case, I have defined the GET method, although it would not be necessary, since, by default, Flask uses the GET method. Therefore, it is only mandatory to define the method if we are going to use a method other than GET, that is, if we use a POST, PUT or DELETE.

How to pass parameters to the API in Flask

Many times, our APIs require parameters. A clear example is when we put a model into production: each input of the model must be a parameter of our API.

In that sense, we can pass arguments to our API in Flask when making the request. In this case, to be able to use them in the function we must first extract them from the request, which we will do with request.args.get. Yes, this is somewhat different from how it is done in FastAPI and, in my opinion, is more cumbersome in Flask than in FastApi.

Let’s see, for example, how to create an API that receives a name parameter and makes a print of “Hello {name}!”.

@app.route('/my-first-api', methods = ['GET'])
def hello():

    name = request.args.get('name')

    if name is None:
        text = 'Hello!'

    else:
        text = 'Hello ' + name + '!'

    return text

In this case, our API is taking the name parameter from the request URL, with two options:

  1. If the name parameter has not been passed, for example: http://127.0.0.1:8000/my-first-api the name parameter will be None, so the API will return Hello!.
  2. If the name parameter has been passed, example http://127.0.0.1:8000/my-first-api?name=Ander, the API will use the parameter to create the response, which in this case will be Hello Ander! .

Important: Flask does not make an evaluation of the request parameters, so we will have to do an evaluation ourselves, otherwise it will be used in the function. For example, if we request the URL: http://127.0.0.1:8000/my-first-api?name=2, the API will return Hello 2! , since it has not verified that the value of the parameter must be a string and not a number or any other type of object.

Return different data types with Flask

As we can see, so far we have returned text strings. However, APIs usually return data in JSON format. To do this, we must use the jsonify module. Let’s see an example of how to return the previous string, but this time in JSON format:

@app.route('/my-first-api', methods = ['GET'])
def hello():

    name = request.args.get('name')

    if name is None:
        text = 'Hello!'

    else:
        text = 'Hello ' + name + '!'

    return jsonify({"message": text})

In this same way, we can return another type of object, such as a dataset. Let’s see what an example would look like, creating a new endpoint that returns the iris dataset through a GET request:

@app.route("/get-iris")
def get_iris():

    import pandas as pd
    url ='https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv'
    iris = pd.read_csv(url)

    return jsonify({
        "message": "Iris Dataset",
        "data": iris.to_dict()
        })

To pass a DataFrame, we first have to convert it to an object that can be converted to a JSON, like a dictionary. With this, we will have an answer like the following:

resp.json()
{'data': {'petal_length': {'0': 1.4, '1': 1.4, '2': 1.3, '3': 1.5, '4': 1.4, '5': 1.7, '6': 1.4, '7': 1.5, '8': 1.4, '9': 1.5, '10': 1.5, '11': 1.6, '12': 1.4, '13': 1.1, '14': 1.2, '15': 1.5, '16': 1.3, '17': 1.4, '18': 1.7, '19': 1.5, '20': 1.7, '21': 1.5, '22': 1.0, '23': 1.7, '24': 1.9, '25': 1.6, '26': 1.6, '27': 1.5, '28': 1.4, '29': 1.6, '30': 1.6, '31': 1.5, '32': 1.5, '33': 1.4, '34': 1.5, '35': 1.2, '36': 1.3, '37': 1.5, '38': 1.3, '39': 1.5, '40': 1.3, '41': 1.3, '42': 1.3, '43': 1.6, '44': 1.9, '45': 1.4, '46': 1.6, '47': 1.4, '48': 1.5, '49': 1.4, '50': 4.7, ...

Finally, can you send images through an API in Python made in Flask? In FastAPI we have seen that it can and, of course, Flask was not going to be less. In fact, the way to do it is very similar, only that, in this case, we must save the content within the flask itself (or whatever directory it is) and send it using the send_file function.

@app.route("/plot-iris")
def plot_iris():

    import pandas as pd
    import matplotlib.pyplot as plt
    import os

    url ='https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv'
    iris = pd.read_csv(url)

    plt.scatter(iris['sepal_length'], iris['sepal_width'])
    plt.savefig('flask/iris.png')

    return send_file('iris.png')

Now that we know how to send different types of content with a Python API created with Flask, let’s see how to check that our API works correctly.

Check the functioning of a Flask API

Before executing a Python API made in Flask we have to check to make sure that we are exposing it on a port. For this we must include the code that I have mentioned and that I put below:

if __name__ == '__main__':
    app.run(debug=True, port=8000)

So, to run the application we just have to run our Python file where we have created the API. In my case the command is the following:

python flask/main.py

This will generate a code like the following:

How to create APIs in Python

As you can see, our API will be running on our local host, in the port that we have indicated (in my case, port 8000).

So, to test the API we can simply go to our browser and go to the endpoints or, directly, make the requests from Python:

resp.json()
{'message': 'Hello Ander!'}

As we can see, the API is returning the content properly.

We can also add Swagger and OpenApi documentation to our API in Flask, although doing so is going to be a bit more tedious than in FastAPI. In fact, this would make for a post itself, so I link you to this post written by Sean Bradley, in case you are interested.

Conclusion

As you can see, both using FastApi and Flask the creation of APIs is very simple, although they have certain differences:

  • Flask is a framework that allows you to create entire web applications.
  • FastAPI performs the validation of the data types that we perform, as well as the entire documentation process, saving us a lot of time compared to whether We do it in Flask.

So, my recommendation would be that, if you only want to create the API, do it in FastAPI, since it will surely be faster. However, if you want to create a complete user interface (front and back) and that the API is part of the ecosystem, I would recommend using Flask, since everything will be under the same framework and it will be much easier for you.

In any case, creating APIs in Python is something very simple and fundamental for any dedicated person in the world of data. Either for the execution of processes or for the production of Machine Learning algorithms, surely the APIs are part of that process.

As always, I hope you found the post interesting. If so, I recommend that you subscribe to be informed every time I upload a new post. In any case, see you at the next one!