Zero to API in 4 minutes


Are you are a network engineer and spend most of your time doing service delivery, instead of network engineering, architecture, and automation? One way to eliminate the toil might be to expose your services as APIs so your customers can consume them, self-service and programatically. Ansible and python are popular tools for network automation. Using familiar tools, YAML and python, a simple design-first API can be written will little to no code.

This tutorial should take no more than a few minutes to work through and results in a documented, usable API in a format your application devops team should be ready to consume.

Getting started

Build a new folder for the project, add a virtual environment and install Flask.

# Make a new project folder
mkdir my_api
cd my_api
# Set up a virtual environement
virtualenv venv
source venv/bin/activate
# Install flask
pip install flask
# Save the dependancies
pip freeze > requirements.txt

Install Swagger editor

The API will be define using the The OpenAPI Specification (fka The Swagger Specification)

Install the swagger editor and unzip

wget https://github.com/swagger-api/swagger-editor/releases/download/v2.10.4/swagger-editor.zip
unzip swagger-editor.zip

Build a backend for swagger.

Two small changes need to be made to swagger-editor.

1) Enable backend saving, so files can be saved the local file system.

2) Set the default format to yaml.

Make the following changes to the swagger-editor/config/defaults.json file:

Swagger editor doesn't ship with a backend for saving files. To enable backend saving modify the swagger-editor/config

"useBackendForStorage": true,
"useYamlBackend": true,

The following python script is all that is needed for a backend. Swagger editor only does a GET when it starts, and a PUT every time the spec file is modified.

Create backend.py:

#!/usr/bin/env python

""" Simple swagger backend (backend.py)
"""

import argparse
import webbrowser
from flask import Flask, request

# pylint: disable=invalid-name
specfile = None
port = None
app = Flask(__name__, static_url_path='', static_folder="swagger-editor")

@app.route('/')
def root():
    """ The swagger editor entry point
    """
    return app.send_static_file('index.html')

@app.route('/editor/spec', methods=['GET', 'PUT'])
def spec():
    """ The route for swagger
    """
    if request.method == 'GET':
        try:
            with open(specfile) as file_contents:
                data = file_contents.read()
                return data
        except IOError:
            return '', 400
    if request.method == 'PUT':
        try:
            with open(specfile, 'w') as outfile:
                outfile.write(request.data)
            return '', 200
        except IOError:
            return '', 400

def main():
    """ The main entry point
    """
    # pylint: disable=global-statement
    global port
    global specfile
    parser = argparse.ArgumentParser(description='Simple swagger backend')
    parser.add_argument('-p', '--port', action="store", dest="port", type=int,
                        required=True)
    parser.add_argument('-s', '--specfile', action="store", dest="specfile",
                        required=True)
    args = parser.parse_args()
    specfile = args.specfile
    port = args.port
    webbrowser.open_new('http://127.0.0.1:' + str(port))
    app.run(port=port)

if __name__ == "__main__":
    main()

Create our API specification

This is a design first approach to API development. The schema for the API is written in yaml.

Start the swagger backend:

python backend.py -p 5000 -s swagger.yml

The system default browser should open to http://127.0.0.1:5000/.

Here is the specification for the API. Paste it in the editor, the backend should save the file automatically for you.

swagger: "2.0"
info:
  version: "1.0.0"
  title: "Hello World"
  description: "A sample API that says hello"
  contact:
    name: "You name here"
  license:
    name: "MIT"
host: "localhost:8080"
basePath: "/api/v1"
schemes:
    - "http"
consumes:
- "application/json"
produces:
- "application/json"
paths:
  /message:
    get:
      description: "Returns a greeting."
      responses:
        "200":
          description: "Success"
          schema:
            $ref: "#/definitions/Message"
definitions:
  Message:
    type: "object"
    properties:
      message:
        type: "string"

Convert the specification to code

using swagger codegen

The conversion can be done easily using either the online swagger codegen api or a local docker container

Generate using the online API

In order to us the online API, a few dependancies need to be installed.

pip install pyyaml
pip install requests
# update the requirements.txt file
pip freeze > requirements.txt

A simply python script can submit the swagger.yml file and unzip it.

Create submit.py:

""" Submit for codegen
"""

import argparse
import json
import zipfile
import yaml
import requests
import shutil


FILENAME = "codegen.zip"

def convert(specfile):
    """ convert to json
    """
    with open(specfile) as file_contents:
        data = yaml.load(file_contents.read())
        return data

def send(data):
    """ Send the swagger spec file
    """
    data = json.dumps({'spec': data, 'options': {'supportPython2': True }})
    headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
    url = 'http://generator.swagger.io/api/gen/servers/python-flask'
    response = requests.post(url, data=data, headers=headers)
    return response.json()['link']

def retrieve(url):
    """ Retreive the codegen file and save
    """
    response = requests.get(url)
    with open(FILENAME, 'wb') as outfile:
        outfile.write(response.content)

def unzip():
    """ Unzip the codegen file
    """
    zip_ref = zipfile.ZipFile(FILENAME, 'r')
    zip_ref.extractall('.')
    zip_ref.close()
    print "Extracted in Current Directory"

def main():
    """ The main entry point
    """
    parser = argparse.ArgumentParser(description='Submit for codegen')
    parser.add_argument('-s', '--specfile', action="store", dest="specfile",
                        required=True)
    args = parser.parse_args()
    specfile = args.specfile
    data = convert(specfile)
    url = send(data)
    retrieve(url)
    unzip()

if __name__ == "__main__":
    main()

and run it

python submit.py -s swagger.yml

Generate using a local docker container

docker run -it -v `pwd`:/swagger-api/out \
    -v `pwd`:/swagger-api/yaml \
    jimschubert/swagger-codegen-cli generate \
    -i /swagger-api/yaml/swagger.yml \
    -l python-flask \
    -D supportPython2=true \
    -o /swagger-api/out/python-flask-server

API server prep

Install the requirements for the backend API server.

pip install -r python-flask-server/requirements.txt
pip install flask-cors
# Save the dependancies
pip freeze > requirements.txt

Move the swagger_server directory up to the project parent folder and delete the previous parent.

mv python-flask-server/swagger_server swagger_server
rm -rf python-flask-server

Modify the swagger_server/main.py.

Since the API server will be run on a different port than the swagger editor, we need to enable CORS.

Modify the swagger_server/main.py file as follows:

#!/usr/bin/env python

import connexion
from .encoder import JSONEncoder
from flask_cors import CORS

if __name__ == '__main__':
    app = connexion.App(__name__, specification_dir='./swagger/')
    app.app.json_encoder = JSONEncoder
    app.add_api('swagger.yaml', arguments={'title': 'A sample API that syas hello'})
    CORS(app.app)
    app.run(port=8080)

Several models were generated during the codegen process, use the Message class:

Modify the swagger_server/controllers/default_controler.py:

def message_get():
    """
    message_get
    Returns a greeting.

    :rtype: Message
    """
    response = Message()
    response.message = 'Hello World'
    return response

Start the backend

python -m swagger_server

Test the API

The new API can be tested from the command line:

curl localhost:8080/api/v1/message
"Hello world"

or using the swagger editor or UI built into your project:

http://localhost:8080/api/v1/ui/

Congratulations

You just wrote your first REST API. Take a few minutes to look through the directories and models. One word of caution, if you rerun the submit.py script it will overwrite your changes in the swagger_server directory.