Making with Code

Lab API #

In this lab we are going to learn how the riddle server is made using Banjo.

[0] Experience the ISF Riddle Server #

The last time we saw riddles, they existed only within each of your computers. You had to manually add new ones and there was no way to keep track of how many people guessed them.

We are now going to look at Riddles that are hosted on the internet!

πŸ’» Visit http://sycs.student.isf.edu.hk/riddle/all to view riddle server.

Not very easy to read, right? You can install the JSON Formatter Chrome extension to view better formatted JSON.*

πŸ’» Now try making GET request to http://sycs.student.isf.edu.hk/riddle/all receive the same information using : httpie.io/app

πŸ’» Do you know the answer? Try sending a POST request to make a guess. This request to a different url endpoint: /guess

  1. Add a new request
  2. Change GET to POST
  3. Paste in the URL: http://sycs.student.isf.edu.hk/riddle/guess
  4. Select Body
  5. At the bottom, change None to Form
  6. Add the payloads: id and guess

It is common for POST requests to send a payload with the request. In this case, the payloads are:

  • id specifying which riddle we are guessing
  • guess specifying the guess We are also sending this request to a different url endpoint: /guess

Explore the Endpoints #

πŸ”— Server APIs often rely on different URL endpoints to the base url to determine what the API should do.

  • The base url to this server is: http://sycs.student.isf.edu.hk/riddle

Here is a cheatsheet of the Riddle endpoints, what parameters they take in their payload, and what they do:

MethodURLRequired PayloadAction
GET/allReturns a list of all the riddles, without answers.
GET/oneidReturns the riddle if it exists
POST/newquestion, answerCreates a new riddle (with an automatically-assigned id). Returns the riddle.
PUT/guessid, guessChecks whether the guess is correct. In the response, correct is True or False.
GET/difficultyidReturns the riddle if it exists with its difficulty score. (Otherwise, it returns an error with status code 404.)
βœ… CHECKPOINT:

πŸ’» Explore each endpoint in the httpie tool, and be sure to successfully:

  • view all riddles without the answers
  • view a single riddle
  • add a new riddle
  • guess a riddle
  • try to break the riddle server, what happens when you provide incorrect parameters?

πŸ“– When using the the httpie tool,

  • for a GET request, put the payload in the Params
  • for a POST request, put the payload in the Body > Form


[1] Set Up #

πŸ’» Download Mac app of HTTPIE: httpie.io/download. We need to download the app to make local HTTP requests for testing purposes.

πŸ’» Start by going into the unit folder and the lab. Remember to replace yourgithubusername with your actual GitHub username.

cd ~/desktop/making_with_code/unit03_networking/
git clone https://github.com/the-isf-academy/lab_apiyourgithubusername
cd lab_api_yourgithubusername
πŸ’» Install libraries
poetry install
πŸ’» Enter the Poetry shell
poetry shell

This server is written using the Flask Library. It allows easily write an API to interact with a SQL databse.

πŸ“ Our API has a few main files api.py helpers.py.

  • api.py - API strucutre where endpoints are defined
  • helpers.py - helper functions to interact with database
  • database.db - riddles SQL database

[2] Local Riddle Server #

Your computer can host a local server that accessess the riddle API.

πŸ’» Now, let's start your local server: python api.py. This will run a local server that is only accessible on your local computer.

πŸ’» You can now visit this server in your web browser, just as you did with the riddler server hosted on the internet: 127.0.0.1:8000/riddle/all

πŸ’» Open the HTTPie desktop app to send the same GET request to /all.

Look at the Terminal window running the server. Notice how it recorded your request.
[16/Sep/2024 02:44:20] "GET /riddle/all HTTP/1.1" 200 1069

Hitting the Server #

πŸ’» Send a POST request to the /new endpoint.

Payload:

  • question (str)
  • answer (str)
Look at the Terminal window running the server. Notice how it recorded your request.
[01/Sep/2024 20:56:22] "POST /riddle/new?id=1 HTTP/1.1" 200 127

Your version of the riddle server only has the 2 endpoints:

  • /riddle
  • /riddle/all
  • /riddle/new
βœ… CHECKPOINT:

πŸ’» Explore both endpoints via the HTTPie desktop app and be sure to successfully:

  • view all riddles without the answers
  • create a new riddle


[3] Writing Routes #

In this lab, you will build out the functionality of the Riddle server. Currently, your file only has riddle/all and riddle/new.

It is up to you to add the following endpoints:

  • riddle/one
  • riddle/random
  • riddle/guess

πŸ’» Start by opening up the folder: code .

You should first, open a new terminal tab with ⌘ + T, so you can keep your server running in one tab, and use the other tab to access your filesystem

πŸ’» Open the api.py file. Here is where you will write the additional endpoints.

πŸ‘€ First, take a look at the index() function.

1
2
3
4
@app.route('/', methods=['GET'])
def index():
    son = {'message': 'Hello from the ISF Riddles API!'}
    return json, 200
  • line 1 - defines the URL route and the HTTP request type
  • line 2 - defines a function and any necessary parameters
  • line 3 - defines the JSON response
  • line 4 - returns the JSON and the HTTP response code

HTTP response codes #

The important HTTP success response codes

  • 200 - successful GET or PUT request
  • 201 - successful POST request

riddle/one #

πŸ’» Write the riddle/one endpoint.

  • HTTP method: get
  • Payload/args: id
  • Return: a single Riddle with the question, guesses, and correct properties

πŸ€” Which functions in `helpers.py`` could be useful?

βœ… CHECKPOINT:

πŸ’» Test the riddle/one endpoint in the HTTPie desktop app

http://127.0.0.1:8000/riddle/one id=4

βœ”οΈ It should return json like:

{
  "riddle": {
    "id": 1,
    "question": "I’m light as a feather, yet the strongest person can’t hold me for five minutes. What am I?",
    "total_guesses": 43,
    "correct_guesses": 0  } 
}


riddle/random #

πŸ’» Write the riddle/random endpoint.

  • HTTP method:get
  • Payload/args: none
  • Return: a single Riddle with the id, question, correct, and guess properties

πŸ€” Which query method may be useful? Be sure to reference the Banjo documentation.

βœ… CHECKPOINT:

πŸ’» Test the riddle/random endpoint in the HTTPie desktop app

http://127.0.0.1:8000/riddle/random 

βœ”οΈ It should return json like:

{
    "correct_guesses": 0,
    "difficulty": 0,
    "id": 219,
    "question": "What is full of holes, but can still hold a lot of water?",
    "total_guesses": 0
}


riddle/guess #

πŸ’» Write the riddle/guess endpoint.

  • HTTP method: put
  • Payload/args: id and guess
  • Return:
    • if the guess was correct
      • message telling the user they were correct
      • a single Riddle with all of row values
    • if the guess was incorrect
      • message telling the user they were incorrect
      • a single Riddle without the answer

πŸ€” Which functions in `helpers.py`` could be useful?

βœ… CHECKPOINT:

πŸ’» Test the riddle/guess endpoint in the HTTPie desktop app

http://127.0.0.1:8000/riddle/guess

βœ”οΈ It should return json like:

{
    "correct": true,
    "riddle": {
        "answer": "Noon",
        "correct_guesses": 14,
        "difficulty": 0.8235294117647058,
        "id": 3,
        "question": "What time of day, when written in a capital letters, is the same forwards, backwards and upside down?",
        "total_guesses": 17
    }
}



[4] Deliverables #

⚑✨

Once you’ve successfully completed the worksheet be sure to fill out this Google form.

πŸ’» Push your work to Github:

  • git status
  • git add -A
  • git status
  • git commit -m “describe your code and your process here”

    be sure to customize this message, do not copy and paste this line

  • git push


[5] Extensions #

Error Messaging #

Try to access a riddle that does not exist. You get an error, correct? A better solution is to return an error message with helpful information.

The HTTP code for an erorr is 404. You are probably familiar with this from surfing the web.

Here is an example of an error message:

if riddle is None:
      return {'error': 'Post not found'}, 404
if not question or not answer:
    return {'error': 'Question and Answer are required.'}, 400

Incorporate approprate error messages for each of your endpoints. Try to break them.

The important HTTP success response codes

  • 400 - incorrect payload
  • 404 - no results found

Difficulty #

Since that we track difficulty, it would be nice if we could GET a list of riddles of easy, medium, or hard difficulty.

πŸ’» Write a function get_riddles_difficulty(level) that returns all of the riddles within the appropriate range.

  • reference SQL WHERE operators
  • consider what the difficulty ranges should be for easy, medium, hard (difficulty of 1 is impossibly hard, while a Riddle with a difficulty of 0 is easy)

πŸ’» Write an endpoint returns riddles within a difficulty category

  • HTTP method: GET
  • Payload/args: level
  • Return:
    • a list of Riddles with the id, question, correct, and guess properties of the designated difficulty level

You can use an url parameter like:

@app.route(f'/{BASE_URL}/all/<str:difficulty>', methods=['GET'])
def all_riddles_difficulty(difficulty):

βœ”οΈ It should return json like:

{
  "difficulty_level": "hard",
 "riddles": [
    {
      "correct": 1,
      "guesses": 44,
      "id": 1,
      "question": "I’m light as a feather, yet the strongest person can’t hold me for five minutes. What am I?",
      "difficulty": 0.9555555555555556
    },
    {
      "correct": 4,
      "guesses": 9,
      "id": 2,
      "question": "What comes down but never goes up?",
      "difficulty": 0.875
    }
 ]
}