RESTful API Practicum

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

Setup

  1. Create a new project on https://git.gccis.rit.edu (in your own account, not in the REST group) called rest-practicum
  2. Add your instructor as Reporter (Prof. Meneely’s is andy on GitLab)
  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

Practicum

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 weird_al import WEIRD_AL_SONGS_DB

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

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

api.add_resource(RootLevel, '/')

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

Step 2. Add weird_al.py

Create a file called weird_al.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
WEIRD_AL_SONGS_DB = [ # source: genius.com
    """
        At 4:30 in the mornin' I'm milkin' cows
        Jebediah feeds the chickens and Jacob plows, fool
        And I've been milkin' and plowin' so long that
        Even Ezekiel thinks that my mind is gone
        I'm a man of the land, I'm into discipline
        Got a Bible in my hand and a beard on my chin
        But if I finish all of my chores, and you finish thine
        Then tonight we're gonna party like it's 1699

        We been spending most our lives
        Living in an Amish paradise
        I've churned butter once or twice
        Living in an Amish paradise
        It's hard work and sacrifice
        Living in an Amish paradise
        We sell quilts at discount price
        Living in an Amish paradise
    """,
    """
        They see me mowin' my front lawn
        I know they're all thinking I'm so white and nerdy
        Think I'm just too white and nerdy
        Think I'm just too white and nerdy
        Can't you see I'm white and nerdy?
        Look at me, I'm white and nerdy
    """,
    """
        It's all about the Pentiums!
        (Yeah!!)

        What y'all wanna do?
        Wanna be hackers? Code crackers? Slackers
        Wastin' time with all the chatroom yakkers?
        9 to 5, chillin' at Hewlett Packard?
        Workin' at a desk with a dumb little placard?
        Yeah, payin' the bills with my mad programming skills
        Defraggin' my hard drive for thrills
        I got me a hundred gigabytes of RAM
        I never feed trolls and I don't read spam
    """
]

Step 3. Update test_practicum.py

Copy this on top of your existing tests. NOTE: We’ve updated our old test cases to use the 5344 port instead of 5000

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_songs(self):
        songs = get_rest_call(self, 'http://127.0.0.1:5344/songs')
        self.assertTrue(len(songs) >= 3, "At least three songs in database")
        # does any song in the response have these phrases?
        self.assertTrue(any("Amish paradise" in song for song in songs))
        self.assertTrue(any("white and nerdy" in song for song in songs))
        self.assertTrue(any("Pentiums" in song for song in songs))

    def test_get_one_song(self):
        song = get_rest_call(self, 'http://127.0.0.1:5344/songs/1')
        self.assertIn("white and nerdy",song)

    def test_get_one_song_lowercase(self):
        song = get_rest_call(self, 'http://127.0.0.1:5344/songs/0?lower=true')
        self.assertIn("amish", song)
        self.assertNotIn("Amish", song)

    def test_add_new_song(self):
        lyric = 'Stick your head in the microwave to give yourself a tan'
        post_rest_call(self, 'http://127.0.0.1:5344/songs', {'lyrics': lyric})

        # Note: this test requires previous tests to work since it uses /songs
        songs = get_rest_call(self, 'http://127.0.0.1:5344/songs')
        any_song_has_microwave = any('microwave' in song for song in songs)
        self.assertTrue(any_song_has_microwave, 'New song should be added')

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 /songs should list all of the songs in the song “database”
    • An list of just the lyrics is sufficient, as opposed to a dictionary
  • A GET on a url with an ID looks up the corresponding song
    • For example, /songs/1 would return the second (1-index) song in the “database”.
    • No need to worry about array boundaries - assume friendly input (e.g. we won’t test /songs/99)
    • If the URL has parameter lower set to true (e.g. /songs/1?lower=true), then the song lyrics are returned in lowercase.
  • A POST request on /songs should result in adding a new song.
    • The input should be set in the body of the HTTP request (as done in the test case)
    • Append the lyrics string to the end of the “database”
    • 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).
  • 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) Good CI practices followed
    • (5pts) GET /songs
    • (5pts) GET /songs/1
    • (5pts) GET /songs/1?lower=true
    • (5pts) POST /songs