As a long time Java/Spring and Spring-MVC user, when our team recently needed to build a REST API in Python, we set about trying to see how we can create something as similar as possible to what we are accustomed to (and love!) in Spring MVC. There was a lot learning along the way, so I would like to share the very satisfactory results that we had.
The first decision that we needed to make was which HTTP framework to use. We decided to base our application on Flask, one of the well known, easy to use, libraries for building web servers. On top of that, we used Flask-RESTX, which as their web site indicates it “adds support for quickly building REST APIs”. Flask-RESTX has many features, such as simplified generation of Swagger documentation and HTTP error handling. On top of that, we added Flask-Injector, enabling us to use Dependency Injection, a feature that is so basic for us Spring developers. Dependency injection makes it trivial to mock out our database and other dependencies in unit tests. It also makes it easy to set up a singleton connection or our database which can be used in multiple places in our requests
Now that we have our framework of technologies, we can get started. One of the confusing issues within Flask and Flask-RESTX, for those familiar with Flask, is how to organize our APIs. After trying out namespaces and blueprints, we concluded that for the sake of our Swagger documentation that we will generate, the best approach is to use namespaces.
So a simple API would look as follows:
For each API we define a separate class. Notice doc=False. This will tell Flask-RESTX that we are not interested in including our health check in the generated swagger documentation. Also, note the naming of the function determines the HTTP Method used to invoke this action.
One of the nice features of Spring MVC is the simplicity with which we can set up interceptors or filters of all requests based on regex expressions. While we didn’t find a way to set up interceptors based on regex, the interceptor feature exists for before and after requests and can be used with dependency injection using Flask-Injector.
Here is a simple example:
Our interceptor will log all incoming requests that are not the health check. After all, we do not want every ping of the load balancer to our service to be logged. In addition, on all regular user requests, we will pull the authenticated username (via AWS Cognito before our service is invoked) from the Header. We then query our injected datasource to pull additional user information. Once we have this information we insert it into our context using “g” so that this information will be available to all subsequent request handling.
There are two steps missing here to make this work, initialization of our datasource and registering our before (and after) request interceptor. Both of these happen as part of the code needed to initialize our Flask application. These steps include:
- Create the flask app
- Using the Flask Object created, add interceptors
- Create a Class to inherit from injector’s module class to register all our singletons like our database connection for injection
Before we get to this, we will create our flask restx API. As we found in other sample projects we create this in our __init__.py file at the top of our modules of all our requests:
Just as you see we added our health check you can add the rest of your namespaces here. Now, using this flask_restx we can complete our initialization:
The bottom function create_run_time_flask() shows how we put together all the initializations in this file. This function is used for runtime (as we run using gunicorn via Docker) whereas a similar function creating mocks for injection is used in our unit test module. In the unit test module, once we have our app set up, we use our flask_app.test_client to send all our REST requests.
Happy Flasking…