Build Restful API with Swagger documentation
Tags: python, webdev, tutorial, beginners
Published: 2020-08-13 23:18:25

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 <a href="https://dev.to/dev0928/build-restful-apis-using-python-flask-56c7">blog</a>, let’s walk through an example with Flask-RestPlus which is an extension to the Flask framework.

<h3>Project Setup</h3>
* Create a project folder - `mkdir quotes-rest-api`
* Setup and activate virtual environment - <a href="https://dev.to/dev0928/what-is-a-python-virtual-environment-2hkn">Virtual Environment Setup</a>
* Install below packages in project’s virtual environment:
```python
  pip install flask-restplus
  # Flask-restplus needs below version of Werkzeug
  pip install Werkzeug==0.16.0  
``` 

<h3>Application Setup</h3>
Create a file called `app.py` in the project folder and add necessary imports, application setup along with model definition code like shown below:
```python
# 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")
})
```

<h3>Quotes CRUD Operations Setup</h3>
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.   
```python
# 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'})
```

<h3>API endpoints for GET and POST operations</h3>
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. 
```python
# 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
```

<h3>API endpoints for other operations</h3>
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. 
```python
# 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)
```

<h3>How to test the API?</h3>
Add below code to the end of the app.py file, so we could start the application using `python app.py` command.
```python
if __name__ == '__main__':
    app.run(debug=True)
```
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](https://dev-to-uploads.s3.amazonaws.com/i/mrvu0221tmykjenltx5e.png)

<h3>Further Learning</h3>
* <a href="https://swagger.io/docs/specification/about/">Swagger Specification</a>
* <a href="https://flask-restplus.readthedocs.io/en/stable/index.html">Flask RestPlus</a>