DEV Community

Cover image for Build Restful API with Swagger documentation
dev0928
dev0928

Posted on

Build Restful API with Swagger documentation

OpenAPI (formerly known as Swagger) specification is a language agnostic set of specification used for documenting REST APIs. Swagger team joined SmartBear in 2015 and they open-sourced the Swagger specification which is now called OpenAPI specification.

Documenting APIs is one of the most important steps of API development. This way of documenting not only helps fellow developers but also consumers of the API. Also, Swagger enabled APIs could also be quickly tested without the need for going through detailed documentation.

There are several Restful API frameworks that come with OpenAPI documentation support in Python. In this article, similar to my prior blog, let’s walk through an example with Flask-RestPlus which is an extension to the Flask framework.

Project Setup

  • Create a project folder - mkdir quotes-rest-api
  • Setup and activate virtual environment - Virtual Environment Setup
  • Install below packages in project’s virtual environment:
  pip install flask-restplus
  # Flask-restplus needs below version of Werkzeug
  pip install Werkzeug==0.16.0  
Enter fullscreen mode Exit fullscreen mode

Application Setup

Create a file called app.py in the project folder and add necessary imports, application setup along with model definition code like shown below:

# necessary Imports
from flask import Flask, jsonify, request
from flask_restplus import Api, Resource, fields
from werkzeug.middleware.proxy_fix import ProxyFix


# application & documentation setup
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(app, version='1.0', title='Swagger enabled Quotes API',description='A simple Quotes API')

# namespace definition
ns = api.namespace('quotes', description='Quote CRUD operations')

# model definition - would be automatically displayed as part of API documentation
quote = api.model('quote', {
    'id': fields.Integer(readonly=True, description="Quote's unique identifier"),
    'quote_desc': fields.String(required=True, description="Quote"),
    'quote_type': fields.String(required=True, description="quote's type"),
    'author': fields.String(required=True, description="quote's author")
})
Enter fullscreen mode Exit fullscreen mode

Quotes CRUD Operations Setup

Add below DAO (Data Access Object) class to the app.py file. This class is going to be used by API endpoints to perform necessary CRUD Operations. Also, note that quotes are maintained in application's memory as a Python list object.

# Quote class containing CRUD methods
class QuoteDAO(object):
    def __init__(self):
        self.counter = 0
        self.quotes = []

    def get(self, id):
        for q in self.quotes:
            if q['id'] == id:
                return q
        api.abort(404, "Quote {} doesn't exist".format(id))

    def create(self, data):
        q = data
        q['id'] = self.counter = self.counter + 1
        self.quotes.append(q)
        return q

    def update(self, id, data):
        q = self.get(id)
        q.update(data)
        return q

    def delete(self, id):
        q = self.get(id)
        self.quotes.remove(q)


# Initialize DAO & create sample quotes
DAO = QuoteDAO()
DAO.create({'quote_desc':'It always seem impossible until it is done.', 'quote_type': 'Motivation', 'author': 'Nelson Mandela'})
DAO.create({'quote_desc':'With the new day comes new strength and new thoughts.', 'quote_type': 'Motivation', 'author': 'Eleanor Roosevelt'})
DAO.create({'quote_desc':'The secret of getting ahead is getting started.', 'quote_type': 'Motivation', 'author': 'Mark Twain'})
Enter fullscreen mode Exit fullscreen mode

API endpoints for GET and POST operations

Below class defines GET and POST operations for the quotes maintenance. Below code could also be added to the end of same app.py file.

# API Endpoints for listing and creating new quotes
@ns.route('/')
class QuoteList(Resource):
    '''Shows a list of all quotes, and lets you POST to add new quotes'''
    @ns.doc('list_quotes')
    @ns.marshal_list_with(quote)
    def get(self):
        '''List all quotess'''
        return DAO.quotes

    @ns.doc('create_quote')
    @ns.expect(quote)
    @ns.marshal_with(quote, code=201)
    def post(self):
        '''Create a new quote'''
        return DAO.create(api.payload), 201
Enter fullscreen mode Exit fullscreen mode

API endpoints for other operations

Below class is used to define rest of the CRUD operations. Note the use of common namespace decorators for quote id, response and route information.

# API Endpoints for rest of CRUD operations
@ns.route('/<int:id>')
@ns.response(404, 'Quote not found')
@ns.param('id', 'The quote identifier')
class Quote(Resource):
    '''Show a single quote item and lets you delete them'''
    @ns.doc('get_quote')
    @ns.marshal_with(quote)
    def get(self, id):
        '''Fetch a quote given its identifier'''
        return DAO.get(id)

    @ns.doc('delete_quote')
    @ns.response(204, 'Quote deleted')
    def delete(self, id):
        '''Delete a quote given its identifier'''
        DAO.delete(id)
        return '', 204

    @ns.expect(quote)
    @ns.marshal_with(quote)
    def put(self, id):
        '''Update a quote given its identifier'''
        return DAO.update(id, api.payload)
Enter fullscreen mode Exit fullscreen mode

How to test the API?

Add below code to the end of the app.py file, so we could start the application using python app.py command.

if __name__ == '__main__':
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

API endpoints could be tested in the browser itself. Try it Out button (shown below) could be used to GET list of quotes.
Alt Text

Further Learning

Top comments (1)

Collapse
 
colinb profile image
Colin Bounouar

Hello,

Flask Restplus is dead and not maintained anymore. Maybe you should use Flask restx instead as it is the official fork. (If you really want to stick to Flask but know that some major competitors are also out there)

However promoting a framework providing OpenAPI 2 definition only when OpenAPI 3 is out seems a bit odd ;-)