How to quickly build an API with Python
By: Aymen Hammouda
Tags: DevOps, API, Flask, Docker, Python

If you’re here then you already have said this to yourself: I need to quickly build an API, with the least amount of code possible, but how?

“black flat screen computer monitor” by kvncnls on Unslpash

Introduction

As DevOps Engineers, we’re often faced with the need to build, among other things, automation tools [1][2] and have them exposed using an API[3] to be used later by other software components (Jira, Jenkins, services, applications,… ). So, in this tutorial, we’re going to propose building a simple API and have it automate a few tasks for us. In a second article, we’ll show how to add HTTPS support and authentication mechanisms so it can pass the security requirements of enterprises and organizations[4].

TL;DR

What I needed to start, made a Hello Wold API, something a little more complicated, and ran it in a container.

Requirements

To build our API we will be using Python 3 programming language and we’ll use the Connexion framework to handle our HTTP requests and interactions.

- Python 3

- Connexion

I presume you have already Python set up on your platform. If you don’t, I suggest these awesome tutorials to get you up and running:

- Installing Python

- Awesome Python

Connexion

In my line of work, I have grown very fond of the declarative approach as more and more tools are adopting it such as Kubernetes and Terraform; a code that it’s its own documentation, built with the end result in mind, reusable and idempotent[5].So when I first discovered Connexion, I saw its potential to take a huge chunk of the work off in a contract-first approach where the API definition is interpreted from YAML file with all the necessary data structures and validations. I understand if some users would prefer using Flask [6]in a more conventional way for advanced use cases. But for the rest of use cases, I would rather use this contract first[7], and Connexion does a very good job at it. (See also Why Connexion.)

Let’s Start

If you haven’t installed connexion yet you can do so by running:

$ pip install connexion

Hello World

Let’s start with an old fashioned Hello World :

In our app folder let’s create a new file helloworld.py :

In the code above, we have the main code that will render our specifications for the API and load the arguments, and the operation method greeting which will be executed when we run the API server.

Next, we will write the specifications of the API. For this, we will write a Swagger definition file. If you never heard of Swagger, it’s not that complicated:

Swagger is a set of rules (in other words, a specification) describing REST APIs. These specifications are human-readable and machine-interpretable. As a result, it can be used as documentation for testers and developers, but can also be used by various tools to automate API-related processes.

Now, we will create a new folder called swagger and we will add a new file swagger/helloworld.yaml :

And that’s it, we are ready to go 😀 :

To run the API server:

$ python helloworld.py

Note: If you’re faced with a warning message saying that “The swagger_ui directory could not be found.”, this is a library used to generate the swagger documentation for the defined specifications. To fix this, you can install the optional connexion swagger-ui: (Read more about optional dependencies here)

$ pip install 'connexion[swagger-ui]'

This UI is accessible at http://localhost:8080/v1.0/ui

To test the endpoint that we just created you can run:

$ curl --request POST 'http://localhost:8080/v1.0/greeting/john'

So what just happened?

The helloworld.py 's main looked for our YAML file and rendered our specifications in a spec first fashion to create the API server. Under paths we will define the endpoint that will be exposed by the API, the first method defined is a POST to /greeting/{name} which means that my endpoint will take an in-path variable called name to be used in our implementation.

This method is linked to the operation greeting in the helloworld.py using the keyword and value pair operationId:helloworld.greeting : helloworld is the name of our python script and greeting is the method name ( which conveniently takes a string parameter called name )

Log My Messages

Now that we got around the basic let’s do something that is not a Hello World, We’re going to build an API that will receive a POST request with a JSON body and write the text in a file and then return the lines count in this file, so let’s dive right to it!

In a new app folder, let’s call it logmymessages, once again we’ll create a main file called app.py:

And then let’s create the swagger specifications for this API. In a swagger folder, create a new file swagger.yaml :

To define the behavior of the endpoint we are going to create a new file called log.py where we will implement the operation api.log.write . To do so, we'll create a folder called api and inside this folder, we will create the log.py file as follows:

api/log.py:

Let’s run the API server and test our new app:

$ python app.py

We will send a POST request with the content of our message to the API server:

$ curl --request POST 'http://localhost:8080/v1.0/log' \

--header 'Content-Type: application/json' \

-d '{

"message": "Hi, I am Matt. I am a radar technician",

"sender": "Matt"

}'

Output:

{

"count_messages": 1

}

We will send a second POST request with another message:

$ curl --request POST 'http://localhost:8080/v1.0/log' \

--header 'Content-Type: application/json' \

-d '{

"message": "I am 90% sure Matt is Kylo Ren!",

"sender": "Stormtrooper"

}'

Output:

{

"count_messages": 2

}

If everything is working fine we should be able to see a new file created in our app folder called log.txt with two lines in it:

$ cat log.txt

[20210101-101001][Matt] : Hi, I am Matt. I am a radar technician

[20210101-101010][Stormtrooper] : I am 90% sure Matt is Kylo Ren!

For our second operation, we will try the read this file and get back the messages logged in this file by sending a GET request to our /log endpoint:

In the swagger/swagger.yaml file we will add:

And in the api/log.py file we will add:

And that’s it!

we just created a second endpoint for our API and implemented its operation method:

Let’s re-run the API server now by executing:

$ python app.py $ curl --request GET 'http://localhost:8080/v1.0/log'$ curl --request GET 'http://localhost:8080/v1.0/log'

Output:

[

{

"message": " Hi, I am Matt. I am a radar technician",

"sender": "Matt",

"timestamp": "20210101-101001"

},

{

"message": " I am 90% sure Matt is Kylo Ren!",

"sender": "Stormtrooper",

"timestamp": "20210101-101010"

}

]

Containerization

In the section, we’re going to create a docker image of our Python API and run our service in a containerized environment so we can prep our API to be deployed on different platforms.

For this we’re going to use the logmymessages example to build our docker image which is fairly a simple task to do:

First, we start by creating a requirements.txt file where we'll define all the dependencies needed for our API, and it should look something like this:

requirements.txt:

gunicorn >= 20.0.4

connexion >= 2.7.0

connexion[swagger-ui] >= 2.7.0

This will allow for our container to be shipped with all its dependencies. As you can see it only requires two things:

the connexion framework and the optional extra swagger-ui

gunicorn or Green Unicorn an open-source Python production-grade web server that we’ll use to run our API on.

In case you’re wondering what’s a requirements.txt file, it's a text file that, by convention, contains a list of all the PyPI dependencies required by a python application to build and run.[8]

Next, we’ll create the Dockerfile:

To build the image of our container we’re going to use python:3.6 as a base image, which is a Debian image preloaded with python 3.6, pip 20.

In WORKDIR we copy our app and then RUN pip to install the required dependencies.

To run the server, we use the gunicorn command, specify the binding socket and the path for the server’s main.

With the keyword HEALTHCHECK we tell the running container daemon to perform a health check every 30s, and if the check fails, the container should exit.

To build our container, in the logmymessages app folder, we run:

$ docker build -t logmymessages .

As output, we should have the log of the image being built tailed by a line Successfully tagged logmymessages:latest .

Now we can run our container by executing the following command:

$ docker run -p 8080:8080 logmymessages

The API server should be up and running and available on the same URL. you can test this by accessing http://localhost:8080/v1.0/ui to see the Swagger UI.

Conclusion

In this tutorial, we saw how to rapidly build an API service using the contract first approach and build the container image that can be deployed on any supporting platform.

In the next tutorial, we’ll try to secure our application and prepare it for deployment to production on a Kubernetes cluster.