Building a RESTful API using Python & Flask-RESTPlus

Introduction

Flask-RESTPlus is a great Python library to help quickly build well structured RESTful APIs. This post is a guide on how to build REST APIs as well as expose documentation using Swagger.

As an example I shall be using a TV Show Schedule web application using Python. The CRUD functions are used.

  1. Create: Add new Show
  2. Read: Show All, Show Lookup, Schedule Latest
  3. Update: Show Update
  4. Delete: Show Delete

Overview

Installation

Install flask-restplus using pip.

pip install flask-restplus

Namespace

Namespace are not always necessary but aids in organizing APIs, and allows a grouping mechanism of related resources.

The app itself can be split into reusable namespaces. You can define multiple namespaces, particularly useful when scaling the app and maintaining multiple versions of APIs.

webapp/
├── app.py
├── core
│   ├── __init__.py
│   ├── db_utils.py
│   └── tv_utils.py
└── apis
    ├── __init__.py
    ├── ns_tvsched.py
    ├── ns_movienews.py
    ├── ...
    └── namespaceX.py

I have defined 2 namespaces called ’ns_tvsched’ and ’ns_movienews’. I shall focus on only 1 namespace for this example; ’ns_tvsched'.

The root namespace is called ‘schedule’ which means any calls to endpoints will route to ‘/schedule/<…>’

Namespace: ’ns_tvsched’

from flask_restplus import Api, Resource

ns_tvsched = Namespace('schedule',description='List of operations for TV Schedule')

# get latest tv schedule
@ns_tvsched.route('/latest')
@ns_tvsched.doc(responses={200: "OK", 400: 'Invalid Argument', 304: 'Not modified'})

class LatestSchedule(Resource):

    def get(self):
        """
        return json schedule
        """

Endpoints

/schedule/latest

  • Returns JSON response of latest TV schedule
  • Method: GET

/schedule/show/add

  • Adds a new TV Show into database
  • Method: POST
  • Payload: JSON

/schedule/show/update

  • Updates existing TV Show in database
  • Method: PATCH
  • Payload: JSON

/schedule/show/delete

  • Deletes TV Show from database
  • Method: DELETE
  • Params: ID

Request Parameters

Injecting request parameters are defined as the following.

<int:showId> - means that the required request parameter called showId must be an integer

def delete(self,showId): - defines a delete function which uses the value taken from the request.

@ns_tvsched.doc - documents the HTTP responses

@ns_tvsched.route('/schedule/show/delete/<int:showId>')

@ns_tvsched.route('/show/delete/<int:showId>')
class RemoveShow(Resource):
    @ns_tvsched.doc(responses={202: "Deleted", 400: "Invalid Argument", 304: "Not modified"})
    def delete(self,showId):
        """"
            do delete
        """"

When the app is run, swagger renders the following.

Here is another example.

""" Model for inserting a new show into a database """
insert_show_data = ns_tvsched.model('Add new show',{
    "tv_maze_id" : fields.String(description="TV Maze ID", required=True),
    "name" : fields.String(description="Show Name", required=True),
    "tracking" : fields.Boolean(description="Tracking", required=True),
    "description" : fields.String(description="Summary", required=True),
    "network" : fields.String(description="Network", required=True),
    "officialsite" : fields.String(description="Official Site", required=True)
})

"""
Endpoint to add a new TV Show
"""
@ns_tvsched.route('/show/add')
class AddShow(Resource):
    @ns_tvsched.expect(insert_show_data)
    @ns_tvsched.doc(responses={201: "Created", 400: 'Invalid Argument', 304: 'Not modified'})
    def post(self):
        json_data = request.json
        
        """
        Process JSON, add new tv show to database
        """

Breaking down the above code.

  • insert_show_data = defines a structured request parameter; when a request is made to add a new TV Show it must conform to the valid fields as described in the code.
  • @ns_tvsched.expect = only the mandatory model defined is allowed to be used in the request

Swagger renders the following.

Clicking on Try it out! will load a view to inject the required payload parameter. Clicking on execute will post the payload.

Swagger UI

Inside the app.py which is the entrypoint required to launch flask.

api = Api(app=flask_app,doc='/docs')

This overrides the location of where the Swagger docs can be reached. By default it is located at the root via - http://localhost:5000

By providing doc='/docs' Swagger UI will be accessible via - http://localhost:5000/docs

Testing the API

You can use tools like Curl, Postman, SoapUI or even Swagger itself to test that the endpoints exposed are working as expected.

curl http://localhost:5000/schedule/latest
# returns JSON array response

Working with multiple namespaces

I touched briefly on using multiple namespaces, and how it ensures a more cleaner and organised API structure.

In your main Flask app.

app.py

from ns_movie_news import movie_news as ns_movie_news
from ns_tv_sched import schedule as ns_tv_sched

api = Api(app=flask_app,doc='/docs')

api.add_namespace(ns_movie_news)
api.add_namespace(ns_tv_sched)
...

This means I have 2 APIs setup.

  1. /schedule
  2. /movienews

Hints

Hiding endpoints from the Swagger UI use the following decoration. Which instructs flask-restplus to hide the endpoint from the docs.

@api.doc(False)

Further reading

https://flask-restplus.readthedocs.io/en/stable/

https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods

Last updated on 9 Oct 2019
Published on 9 Oct 2019