RESTful API Practicum (Spring 2024)

For this practicum, we’re simplifying our code base to just a Flask API.

Setup

  1. Create a new project on https://kgcoe-git.rit.edu (in your own account, not in the REST group) called rest-practicum
  2. Add your instructor as Reporter (see Slack for their username)
  3. Clone the new repo to your local machine
  4. Download our starter code (which is the zip link from this repo)
  5. Make sure your repo has no directories in it. In other words, make sure you didn’t create a subfolder inside of your repo. For simplicity, all files will be at the root of the repository.
  6. Get this running locally.
  • Run python server.py to start the server
  • Run python -m unittest -v to run your unit tests (in verbose mode)
  • There should be one test passing, called test_server_running
  1. Commit and push to your repo
  2. Make sure the CI also runs successfully

Practice

See a previous RESTful practicum from Spring 2023.

Takehome

There is no takehome portion, however, we strongly recommend getting all this set up ahead of time.

Practicum

In the ancient civilization of the mid-1990’s, shortly after inventing the wheel and discovering fire, people used to have “guestbooks” on their websites. Any stranger could post a nice message (because the assumption is that everyone is nice on the internet), and everyone else can read those messages. Let’s simulate that situation.

Step 1. Update server.py

Copy and paste this over your existing server.py. (Note We’ve changed our port from 5000 to 5344 to avoid some strange Mac issues that bind to the 5000 port)

from flask import Flask, request
from flask_restful import Resource, Api
from guestbook import GUESTBOOK_DB

app = Flask(__name__)
api = Api(app)

class RootLevel(Resource):
    def get(self):
        return "It works!"

######### PLEASE PLACE ALL OF YOUR FLASK CODE HERE #########
#########   (to make grading at little easier) #############

api.add_resource(RootLevel, '/')

if __name__ == '__main__':
    app.run(port=5344, debug=True)

Step 2. Add guestbook.py

Create a file called guestbook.py, and copy the following Python code into that file.

# PRACTICUM: Yes, this is a fake DB
# If we restart the server, we'll reset everything
GUESTBOOK_DB = {
	"anonymous1": "Hello, world!",
	"anonymous2": "This is anonymous??",
	"alexander": "I am not throwing away my shot",
}

Step 3. Update test_practicum.py

Copy this on top of your existing tests.

import unittest
from test_utils import *

class TestPracticum(unittest.TestCase):
    def test_server_running(self):
        str = get_rest_call(self, 'http://127.0.0.1:5344/')
        self.assertEqual("It works!", str)

    def test_list_guestbook_count(self):
        guestbook = get_rest_call(self, 'http://127.0.0.1:5344/guestbook')
        self.assertTrue(len(guestbook) >= 3, "At least three guestbook entries in database")

    def test_list_guestbook_default_entries(self):
        guestbook = get_rest_call(self, 'http://127.0.0.1:5344/guestbook')

        self.assertEqual(guestbook["anonymous1"], "Hello, world!")
        self.assertEqual(guestbook["anonymous2"], "This is anonymous??")
        self.assertEqual(guestbook["alexander"], "I am not throwing away my shot")

    def test_get_one_entry(self):
        entry = get_rest_call(self, 'http://127.0.0.1:5344/guestbook/anonymous1')
        self.assertEqual(entry, "Hello, world!")

    def test_get_one_entry_lowercase(self):
        entry = get_rest_call(self, 'http://127.0.0.1:5344/guestbook/anonymous1?lower=true')
        self.assertEqual(entry, "hello, world!")

    def test_get_one_entry_bad_input(self):
        # We should return a 400 status code when asked for an entry that does not exist
        exp_http_code = 400
        entry = get_rest_call(self, 'http://127.0.0.1:5344/guestbook/nobody', expected_code = exp_http_code)
        self.assertEqual(entry, "bad input!")

    def test_add_new_entry(self):
        username = "samuel"
        entry = 'this congress does not speak for me'
        post_rest_call(self, 'http://127.0.0.1:5344/guestbook', {"username": username, "entry": entry})

        # Note: these tests require previous tests to work since it uses /guestbook
        # Also note: this isn't idempotent because we don't have a reset method
        guestbook = get_rest_call(self, 'http://127.0.0.1:5344/guestbook')
        self.assertTrue(guestbook[username], entry)

    def test_overwrite_new_entry(self):
        username = "samuel"
        entry = 'This congress does not speak for me'
        post_rest_call(self, 'http://127.0.0.1:5344/guestbook', {"username": username, "entry": entry})
        entry2 = 'I pray the king shows you his mercy'
        post_rest_call(self, 'http://127.0.0.1:5344/guestbook', {"username": username, "entry": entry2})

        guestbook = get_rest_call(self, 'http://127.0.0.1:5344/guestbook')
        self.assertTrue(guestbook[username], entry2)

Step 4: Implementation

Implement the following RESTful API:

  • A GET request on / returns the string “It works!” (this was provided for you in the sample code)
  • A GET on /guestbook should list all of the entries in our guestbook “database”
  • A GET on a url with an username looks up the corresponding guestbook entry
    • For example, /guestbook/anonymous1 would return the entry for that username, with a 200 status code
    • If the username does not exist, return a 400 status code and the string "bad input!"
    • If the URL has parameter lower set to true (e.g. /guestbook/anonymous1?lower=true), then the entry are returned in lowercase.
  • A POST request on /guestbook should result in adding a new entry.
    • The input should be set in the body of the HTTP request (as done in the test case)
    • If the username already exists, just overwrite what they wrote.
    • Warning: since we’re faking the database with just a Python list, our tests are not idempotent. This shouldn’t break our automated tests as written, but be aware of this as you’re debugging. When in doubt, restart your Flask server.
    • You shouldn’t need to add any headers to the test cases if you treat the data like a form (hint hint).
  • A very good sign that you are done is that you have 8 tests passing on the CI.
  • This API should be implemented using RESTful specfications, including naming conventions =

Submission

  • Please add all of your code to server.py for ease of grading
  • You are welcome to add new tests, but please do not modify the tests unless the instructor directs you to
  • Please tag your code as submission so we know what to grade
  • Grading breakdown is as follows:
    • (5pts) RESTful adherance and maintainability
    • (5pts) Submission instructions and Good CI practices followed
    • (5pts) GET /guestbook
    • (5pts) GET /guestbook/username
    • (5pts) GET /guestbook/nobody error handling
    • (5pts) GET /guestbook/username?lower=true
    • (5pts) POST /guestbook