RESTful API Practicum
For this practicum, we’re simplifying our code base to just a Flask API.
Setup
- Create a new project on https://git.gccis.rit.edu (in your own account, not in the REST group) called
rest-practicum
- Add your instructor as Reporter (Prof. Meneely’s is
andy
on GitLab) - Clone the new repo to your local machine
- Download our starter code (which is the zip link from this repo)
- 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.
- 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
- Commit and push to your repo
- 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 totrue
(e.g./songs/1?lower=true
), then the song lyrics are returned in lowercase.
- For example,
- 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