Each and every web application needs to make HTTP calls to the server to procure data to populate the dynamic parts of the application.
The aim of this tutorial is to work with Flask extensions that help us create a production environment ready Python application and to integrate with Swagger UI without a hitch. We will learn to build Rest APIs using Python 3 and Flask extensions such as Connexion, Flask-Marshmallow, and Flask-SQLAlchemy and share the API using Swagger UI.
OpenAPI
The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.
Swagger
Swagger is a set of open-source tools built around the OpenAPI Specification that can help to design, build, document, and consume REST APIs. The major Swagger tools include:
- Swagger Editor — browser-based editor where you can write OpenAPI specs
- Swagger UI — renders OpenAPI specs as interactive API documentation
- Swagger Codegen — generates server stubs and client libraries from an OpenAPI spec
Connexion
Connexion is a framework that automagically handles HTTP requests based on OpenAPI Specification (formerly known as Swagger Spec) of our API described in YAML format. Connexion allows us to write an OpenAPI specification, then maps the endpoints to our Python functions; this makes it unique, as many tools generate the specification based on our Python code. We can describe the REST API in as much detail as we want; then Connexion guarantees that it will work as we specified.
Creating the Item REST API
We are going to create a REST API providing access to a collection of items with CRUD access to an individual item within that collection. Here’s the API design for the item collection:
Following are the steps required to create the sample Flask-based API for an Item management application:
- Setup and Installation
- Integrate Flask-Marshmallow
- Integrate Flask-SQLAlchemy
- Create Models
- Create Schemas
- OpenAPI Configuration File
- Handler for Item Endpoint
- Application Entry Point
- Swagger UI
- Conclusion
Prerequisites
We require Python 3 with Pipenv and Git installed. Pipenv is a package and a virtual environment manager which uses PIP
under the hood. It provides more advanced features like version locking and dependency isolation between projects.
1. Setup and Installation
Once the prerequisites are in place we can begin creating the application.
a) Create a Sample Item Management Flask Application
To begin with our application, create a folder called python-sample-flask-application
in any directory on the disk for our project.
$ cd /path/to/my/workspace/
$ mkdir python-sample-flask-application
$ cd python-sample-flask-application
Navigate to the project folder.
b) Activate Virtual Environment
Once we are inside the project folder, execute the following commands to activate the VirtualEnv.
pipenv shell --python 3.7
The virtual environment will now be activated, which will provide the required project isolation and version locking.
c) Install Dependencies
Next, install all the required dependencies using Pipenv as shown.
pipenv install flask==1.1.2
pipenv install flask-marshmallow==0.14.0
pipenv install flask-sqlalchemy==2.5.0
pipenv install marshmallow-sqlalchemy==0.24.1
pipenv install "connexion[swagger-ui]"
After we execute the above commands, the required dependencies will be installed.
We can see now two files, which have been created inside our project folder, namely, Pipfile
and Pipfile.lock
.
Pipfile
contains all the names of the dependencies we just installed.Pipfile.lock
is intended to specify, based on the dependencies present inPipfile
, which specific version of those should be used, avoiding the risks of automatically upgrading dependencies that depend upon each other and breaking your project dependency tree.
Note: Here, we have installed all the dependencies with specific versions, which worked on my machine while writing this tutorial. If we don’t specify any version then the latest version of that dependency will be installed, which might not be compatible with other dependencies.
Now, we are all set to write some code for our application.
2. Integrate Flask-Marshmallow
To integrate Flask-Marshmallow with our application, create a file ma.py
with the following content.
from flask_marshmallow import Marshmallow
ma = Marshmallow()
Here, we have imported Marshmallow from flask_marshmallow
. Later on, we will use this Marshmallow instance ma
to integrate with the flask application using the command ma.init_app(app)
.
3. Integrate Flask-SQLAlchemy
For Flask-SqlAlchemy integration, create a file called db.py
having the following content.
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Here, we have imported SQLAlchemy from flask_sqlalchemy
. We will also use this SQLAlchemy instance db
to integrate with the flask application using the command db.init_app(app)
.
4. Create Database Models
Next, we will create database models for our data storage and organization. For our application, we need to create one database model Item and its repository ItemRepo. We will be using db
, the instance of the SQLAlchemy from Flask-SQLAlchemy, which we created earlier (Step 3) to create our models.
The db
instance contains all the functions and helpers from both sqlalchemy
and sqlalchemy.orm
. It provides a class called Model
that is a declarative base, which can be used to declare our models.
Create the models
package and add two files named entities.py
and repositories.py
. We can add all the database entities in entities.py
and it’s corresponding repository in repositories.py
.
4.1 entities.py
The entities.py file should contain the following content:
The above code in entities.py
does the following:
- We started off by creating the Item Model class in
line 3
. - In
line 4
, we declared the table nameitems
where this model will be mapped to. - From
line 6 to 8
, we defined the table columns along with their data types. - From
line 11
to15
, we added some helper methods to print the object at runtime.
4.2 repositories.py
The repositories.py file should contain the following content:
The above code in repositories.py
does the following:
- We started off by creating the ItemRepo class in
line 6
. - From
line 8 to 25
, we defined some helper methods, which we can use to perform CRUD operations onItem
entity.
5. Create Schemas
Create the Marshmallow Schemas
from our models defined earlier using SQLAlchemyAutoSchema
. We will be using ma
, the instance of the Marshmallow from Flask-Marshmallow, which we created earlier (Step 2) to create our schemas.
Create the schemas
package and the file named schemas.py
.
We need to create the schema for the Item Model class, which we defined earlier (Step 4). Once we have defined the Schema, we can use it to dump and load the ORM objects.
6. OpenAPI Configuration File
Now, let us define the OpenAPI specification for our Item Resource. API specifications can be written in YAML or JSON format. Although, YAML and JSON have similar capabilities, YAML is a superset of JSON and tends to be more readable than JSON.
Here, we will use the YAML format to write the specification.
The above swagger.yml
is a YAML file containing all of the information necessary to perform input parameter validation, output response data validation, URL endpoint definition, and Swagger UI.
The specification defines several endpoints for our API. Here, we have defined one endpoint for each of the CRUD (GET, POST, PUT and DELETE) operations.
The above code in swagger.yml
does the following:
- We started off defining the version of the OpenAPI Specification in
line 1
. The OpenAPI version defines the overall structure of an API definition – what we can document and how we can document it. Line 2 to 5
contains theinfo
section of the API specification:title
,description
(optional), andversion
.title
is the API name.description
is extended information about the API.version
is an arbitrary string that specifies the version of the API.Line 6 to 8
contains theservers
section, which specifies the API server and base URL. All API paths are relative to this server URL.Line 9 to 106
contains thepaths
section where we defined individual endpoints (paths) of our API, and the HTTP methods (operations) supported by these endpoints.
An operation definition includes parameters, request body (if any), possible response status codes (such as 200 OK or 404 Not Found) and response contents. For more information, see Paths and Operations. Operations can have parameters passed via URL path (/item/{id}
), query string (/item?name=chair
), headers (X-CustomHeader: Value
) or cookies (Cookie: debug=0
). We can define the parameter data types, format, whether they are required or optional, and other details. For each operation, we can define possible status codes, such as 200 OK or 404 Not Found, and the response bodyschema
. Schemas can be defined inline or referenced via$ref
.
Note: The operation_id
will be the Python function name in our code that will respond to the API call and tags:
defines a grouping for the UI interface.
Line 108 to 119
contains globalcomponents/schemas
section, which lets us define common data structures used in our API. They can be referenced via$ref
whenever aschema
is required – in parameters, request bodies, and response bodies.
7. Handler for Item Endpoint
In the swagger.yml
file, we configured Connexion with the operationId
value to call the various functions within item
module. This means item.py
module must exist and contain all the functions used in swagger.yml
. Following is the item.py
module that we need to create:
The above code in item.py
does the following:
It contains all the functions with arguments specified in swagger.yml
file. For example, def get(id)
function has one argument ID. The corresponding configuration used in swagger.yml
is as shown below:
/item/{id}:
get:
operationId: item.get
tags:
- Item
summary: Return an Item with given ID
description: Return an Item with given ID
parameters:
- name: id
in: path
description: Item ID
required: true
schema:
type: integer
format: int64
responses:
"200": # status code
description: Return an Item with given ID
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
The above def get(id)
function is called when an HTTP request to GET /item/{id}
is received by the server. The function returns the JSON string representation of Item object as specified in the configuration file.
All the functions in item.py
use ItemRepo
created in step 4.2
to perform all database related operations on Item Data Model. It also uses ItemSchema
created in Step 5
to load or dump the Item schema.
8. Application Entry Point
Now, let us create our application entry point. In the root directory of the project, create a file named app.py
with the following content:
The above code within app.py
does the following:
8.1 Add Connexion to the server
There are two parts to adding a REST API URL endpoint to your application with Connexion. We will need to add Connexion to the server and create a configuration file it will use. We have already added the configuration file swagger.yml
to our application. Let us now add the Connexion to the server.
- The
import connexion
statement atline 1
adds the module to our program. - Then we created an application instance using Connexion at
line 6
rather than using Flask. This internally, creates a Flask application, but it now has additional functionality added to it. It includes a parameterspecification_dir
. This informs Connexion what directory to look in for its configuration file, in our case it’s root directory. - Right after this, at
line 8
we tell the app instance to read the file configuration fileswagger.yml
from the specification directory and configure the system to provide the Connexion functionality.
8.2 SQLAlchemy
- From
line 12
to14
we have added some configuration related to SQLAlchemy. Here, we are using SQLite DB. These configurations are required to link SQLite DB with SQLAlchemy.data.db
is the name of the DB File.
8.3 Database
- From
line 17
to19
we have used the db instance (from filedb.py
) to create the DB file along with the tables before the user accesses the server.
8.4 SQLAlchemy and Marshmallow
- In
line 23
andline 24
integrate SQLAlchemy and Marshmallow.
8.5 Start the Application
- Finally, in
line 25
we configure the application to run at port=5000.
Our application is ready now. We can start the application by executing the below command:
python app.py
We are done with all the coding part and it’s testing time. Once the application is started successfully and we navigate to http://localhost:5000/ui/
, the system will bring up a page that looks something like this:
9. Swagger UI
We can now test our application to ensure that everything is working fine. We can open the URL http://localhost:5000/ui/
in our browser.
Swagger interface and shows the list of URL endpoints supported by our application. This is built automatically by Connexion when it parses the swagger.yml
file.
If we click on the /item
endpoint in the Swagger UI, the interface will expand to show a great deal more information about our API and should look something like this:
This displays the structure of the expected response, the content-type
of that response, and the description text we entered about the endpoint in the swagger.yml
file.
We can even try the endpoint out by clicking the Try It Out!
button . That will further expand the interface to look something like this:
Using this Swagger UI the API users can explore and experiment with the API without having to write any code to do so. Building an API this way is very useful. Not only is the Swagger UI useful as a way to experiment with the API and read the provided documentation, but it’s also dynamic. Any time we update the configuration file, the Swagger UI changes as well. This UI allows everyone to see all of the documentation we have included in the swagger.yml
file and interact with all of the URL endpoints making up the CRUD functionality of the item interface.
Swagger UI also helps the developers with API testing, either in case of any issues reported or while adding new API’s to the existing application.
If you would like to refer to the full code, do check:
10. Conclusion
In this tutorial, we saw how easy it is to create a comprehensive REST API using Connexion and Flask. With the help of Connexion module and some additional configuration work, a useful documentation and interactive system can be put in place, which will make the API consumers life easy and enjoyable.