Model for API

We will begin our journey into APIs by creating and thinking about data. We have learned about Python Lists and dictionaries. In this data example, we are going to make "the best computer jokes ever ;)" and serve them over the Internet. The ultimate objective is to allow our viewers to provide a like or dislike on each of our jokes.

  • This code planning begins by coming up with some jokes and defining a data "model" to keep and manage the jokes.

    • jokes_data contains a list of dictionary records containing joke and reactions:haha or boohoo - joke_list contains collection of jokes we will put into jokes_data
  • Next comes some functions to interact with our jokes

    • def initJokes(): initializes jokes_data
    • def getJokes(): returns the complete list of jokes
    • def getJoke(): returns a single joke from our list
    • ... many more function can be examined by reading comments below ...
import random

jokes_data = []
joke_list = [
    "If you give someone a program... you will frustrate them for a day; if you teach them how to program... you will "
    "frustrate them for a lifetime.",
    "Q: Why did I divide sin by tan? A: Just cos.",
    "UNIX is basically a simple operating system... but you have to be a genius to understand the simplicity.",
    "Enter any 11-digit prime number to continue.",
    "If at first you don't succeed; call it version 1.0.",
    "Java programmers are some of the most materialistic people I know, very object-oriented",
    "The oldest computer can be traced back to Adam and Eve. It was an apple but with extremely limited memory. Just "
    "1 byte. And then everything crashed.",
    "Q: Why did Wi-Fi and the computer get married? A: Because they had a connection",
    "Bill Gates teaches a kindergarten class to count to ten. 1, 2, 3, 3.1, 95, 98, ME, 2000, XP, Vista, 7, 8, 10.",
    "Q: What’s a aliens favorite computer key? A: the space bar!",
    "There are 10 types of people in the world: those who understand binary, and those who don’t.",
    "If it wasn't for C, we’d all be programming in BASI and OBOL.",
    "Computers make very fast, very accurate mistakes.",
    "Q: Why is it that programmers always confuse Halloween with Christmas? A: Because 31 OCT = 25 DEC.",
    "Q: How many programmers does it take to change a light bulb? A: None. It’s a hardware problem.",
    "The programmer got stuck in the shower because the instructions on the shampoo bottle said: Lather, Rinse, Repeat.",
    "Q: What is the biggest lie in the entire universe? A: I have read and agree to the Terms and Conditions.",
    'An SQL statement walks into a bar and sees two tables. It approaches, and asks may I join you?'
]

# Initialize jokes
def initJokes():
    # setup jokes into a dictionary with id, joke, haha, boohoo
    item_id = 0
    for item in joke_list:
        jokes_data.append({"id": item_id, "joke": item, "haha": 0, "boohoo": 0})
        item_id += 1
    # prime some haha responses
    for i in range(200):
        id = getRandomJoke()['id']
        addJokeHaHa(id)
    # prime some haha responses
    for i in range(50):
        id = getRandomJoke()['id']
        addJokeBooHoo(id)
        
# Return all jokes from jokes_data
def getJokes():
    return(jokes_data)

# Joke getter
def getJoke(id):
    return(jokes_data[id])

# Return random joke from jokes_data
def getRandomJoke():
    return(random.choice(jokes_data))

# Liked joke
def favoriteJoke():
    best = 0
    bestID = -1
    for joke in getJokes():
        if joke['haha'] > best:
            best = joke['haha']
            bestID = joke['id']
    return jokes_data[bestID]
    
# Jeered joke
def jeeredJoke():
    worst = 0
    worstID = -1
    for joke in getJokes():
        if joke['boohoo'] > worst:
            worst = joke['boohoo']
            worstID = joke['id']
    return jokes_data[worstID]

# Add to haha for requested id
def addJokeHaHa(id):
    jokes_data[id]['haha'] = jokes_data[id]['haha'] + 1
    return jokes_data[id]['haha']

# Add to boohoo for requested id
def addJokeBooHoo(id):
    jokes_data[id]['boohoo'] = jokes_data[id]['boohoo'] + 1
    return jokes_data[id]['boohoo']

# Pretty Print joke
def printJoke(joke):
    print(joke['id'], joke['joke'], "\n", "haha:", joke['haha'], "\n", "boohoo:", joke['boohoo'], "\n")

# Number of jokes
def countJokes():
    return len(jokes_data)

# Test Joke Model
if __name__ == "__main__": 
    initJokes()  # initialize jokes
    
    # Most likes and most jeered
    best = favoriteJoke()
    print("Most liked", best['haha'])
    printJoke(best)
    worst = jeeredJoke()
    print("Most jeered", worst['boohoo'])
    printJoke(worst)
    
    # Random joke
    print("Random joke")
    printJoke(getRandomJoke())
    
    # Count of Jokes
    print("Jokes Count: " + str(countJokes()))
Most liked 17
14 Q: How many programmers does it take to change a light bulb? A: None. It’s a hardware problem. 
 haha: 17 
 boohoo: 4 

Most jeered 5
6 The oldest computer can be traced back to Adam and Eve. It was an apple but with extremely limited memory. Just 1 byte. And then everything crashed. 
 haha: 10 
 boohoo: 5 

Random joke
4 If at first you don't succeed; call it version 1.0. 
 haha: 13 
 boohoo: 3 

Jokes Count: 18

Backend Interface for Web API (Control)

An application programming interface (API) is the medium by which different systems of software interact. In our applications we have two big systems:1. Python Backend that stores data beyond a single Web page2. GH Pages/Fastpages Frontend that is responsible for presenting data

To communicate data between Frontend and Backend, this section Backend code provides and interface to the Frontend using a Web Service Endpoint. Examples of endpoints are listed below and can be typed within a browser, which will return JSON data:

As you can see, these Endpoints return JSON. They are NOT that readable by normal humans. However, they are very effective in passing requested data across the Internet. The Frontend code is responsible for formatting and presenting and interface that allows the typical computer user to interact with this data.

The next cell of code is Creating Endpoints that return JSON. This allows developers in the Frontend to interact with Backend data. API is a contract between the Frontend and Backend on how to share data.

FYI, there is NO output from this section .

from flask import Blueprint, jsonify  # jsonify creates an endpoint response object
from flask_restful import Api, Resource # used for REST API building
import requests  # used for testing 
import random

# Blueprints allow this code to be procedurally abstracted from main.py, meaning code is not all in one place
app_api = Blueprint('api', __name__,
                   url_prefix='/api/jokes')  # endpoint prefix avoid redundantly typing /api/jokes over and over

# API generator https://flask-restful.readthedocs.io/en/latest/api.html#id1
api = Api(app_api)

class JokesAPI:
    # not implemented, this would be where we would allow creation of a new Joke
    class _Create(Resource):
        def post(self, joke):
            pass
            
    # getJokes()
    class _Read(Resource):
        def get(self):
            return jsonify(getJokes())

    # getJoke(id)
    class _ReadID(Resource):
        def get(self, id):
            return jsonify(getJoke(id))

    # getRandomJoke()
    class _ReadRandom(Resource):
        def get(self):
            return jsonify(getRandomJoke())
    
    # getRandomJoke()
    class _ReadCount(Resource):
        def get(self):
            count = countJokes()
            countMsg = {'count': count}
            return jsonify(countMsg)

    # put method: addJokeHaHa
    class _UpdateLike(Resource):
        def put(self, id):
            addJokeHaHa(id)
            return jsonify(getJoke(id))

    # put method: addJokeBooHoo
    class _UpdateJeer(Resource):
        def put(self, id):
            addJokeBooHoo(id)
            return jsonify(getJoke(id))

    # building RESTapi interfaces, these routes are added to Web Server http://<server</api/jokes
    api.add_resource(_Create, '/create/<string:joke>')
    api.add_resource(_Read, '/')    # default, which returns all jokes
    api.add_resource(_ReadID, '/<int:id>')
    api.add_resource(_ReadRandom, '/random')
    api.add_resource(_ReadCount, '/count')
    api.add_resource(_UpdateLike, '/like/<int:id>/')
    api.add_resource(_UpdateJeer, '/jeer/<int:id>/')
    

Frontend (View Simulation) and Hacks

This python codes tests endpoints on a server. This can be handy for development and testing when making modifications to the Jokes Web APIs. This code works off of the server endpoint/url, not from code cells above it in this notebook.

To work with this code and make observation for learning...

  • Run a local server from flask_portfolio project and the change server variable to be local
  • Observe the requests endpoints and the output, see if you can observe what is happening/changing on put requests
  • The "requests" are captured into a List, the List is used in the for loop to extract from RESTful API format.
  • Try running this with Debugging and observe what data is being created at each step (Required)
  • Try to format this data in Python print statements to be more readable (Required)
  • Start and stop local server and observe errors
from flask import Blueprint, jsonify  # jsonify creates an endpoint response object
from flask_restful import Api, Resource # used for REST API building
import requests  # used for testing 
import random

# Blueprints allow this code to be procedurally abstracted from main.py, meaning code is not all in one place
app_api = Blueprint('api', __name__,
                   url_prefix='/api/jokes')  # endpoint prefix avoid redundantly typing /api/jokes over and over

# API generator https://flask-restful.readthedocs.io/en/latest/api.html#id1
api = Api(app_api)

class JokesAPI:
    # not implemented, this would be where we would allow creation of a new Joke
    class _Create(Resource):
        def post(self, joke):
            pass
            
    # getJokes()
    class _Read(Resource):
        def get(self):
            return jsonify(getJokes())

    # getJoke(id)
    class _ReadID(Resource):
        def get(self, id):
            return jsonify(getJoke(id))

    # getRandomJoke()
    class _ReadRandom(Resource):
        def get(self):
            return jsonify(getRandomJoke())
    
    # getRandomJoke()
    class _ReadCount(Resource):
        def get(self):
            count = countJokes()
            countMsg = {'count': count}
            return jsonify(countMsg)

    # put method: addJokeHaHa
    class _UpdateLike(Resource):
        def put(self, id):
            addJokeHaHa(id)
            return jsonify(getJoke(id))

    # put method: addJokeBooHoo
    class _UpdateJeer(Resource):
        def put(self, id):
            addJokeBooHoo(id)
            return jsonify(getJoke(id))

    # building RESTapi interfaces, these routes are added to Web Server http://<server</api/jokes
    api.add_resource(_Create, '/create/<string:joke>')
    api.add_resource(_Read, '/')    # default, which returns all jokes
    api.add_resource(_ReadID, '/<int:id>')
    api.add_resource(_ReadRandom, '/random')
    api.add_resource(_ReadCount, '/count')
    api.add_resource(_UpdateLike, '/like/<int:id>/')
    api.add_resource(_UpdateJeer, '/jeer/<int:id>/')
    

# Pick the server you want to test comment in/comment out
# server = "http://127.0.0.1:5000/" # run local
server = 'https://flask.nighthawkcodingsociety.com/' # run from web server
url = server + "api/jokes/"
responses = []  # responses list

# Get the count of jokes on server
count_response = requests.get(url+"count")
count_json = count_response.json()
count = count_json['count']

# Update likes/dislikes test sequence
num = str(random.randint(0, count-1)) # test a random record
responses.append(
    requests.get(url+num)  # Get/read joke by id
    ) 
responses.append(
    requests.put(url+"like/"+num) # Put/add to like count
    ) 
responses.append(
    requests.put(url+"jeer/"+num) # Put/add to jeer count
    ) 

# Get a random joke
responses.append(
    requests.get(url+"random")  # Get/read a random joke
    ) 

# Cycle through and print responses
for response in responses:
    print(response)
    try:
        print(response.json())
    except:
        print("data error")
<Response [200]>
{'boohoo': 4609, 'haha': 1001876, 'id': 17, 'joke': 'An SQL statement walks into a bar and sees two tables. It approaches, and asks may I join you?'}
<Response [200]>
{'boohoo': 4609, 'haha': 1001877, 'id': 17, 'joke': 'An SQL statement walks into a bar and sees two tables. It approaches, and asks may I join you?'}
<Response [200]>
{'boohoo': 4610, 'haha': 1001877, 'id': 17, 'joke': 'An SQL statement walks into a bar and sees two tables. It approaches, and asks may I join you?'}
<Response [200]>
{'boohoo': 420741, 'haha': 186601, 'id': 0, 'joke': 'If you give someone a program... you will frustrate them for a day; if you teach them how to program... you will frustrate them for a lifetime.'}