Assignments > HW5: Build a REST API
Due on Fri, 05/02 @ 11:59PM. 40 Points.
Video Walkthroughs for Homework 5
I have created some video walkthroughs to help you get started on Homework 5, which have been posted to Google Drive. Note that a few of these videos were created last semester (before we were using poetry). So, just remember that to run any of your python files, you’ll need to add “poetry run” before any of your command line commands. For instance:
- Instead of
flask run --debug
, you’ll type:poetry run flask run --debug
- Instead of
python run_tests.py -v
you’ll type:poetry run python run_tests.py -v
Introduction
For homework 5, you are going to create a REST API using Flask and PostgreSQL. To do this, you will:
- Create and populate a local database
- Configure your python code to interact with the database using SQL Alchemy
- Use Flask RESTful to implement a few API endpoints
- Run the tests to see that your API requirements have been met
Set Up
Before you get into the concepts and code, let’s first download and configure the Homework 5 files. Please complete the following steps:
1. Download the HW5 files and save them inside of your csci344 folder
Download hw05.zip and unzip it.
You should see a directory structure that looks like this:
hw05
├── README.md
├── app.py
├── models
├── populate.py
├── pyproject.toml
├── static
├── templates
├── tests
├── users.csv
├── utilities
└── views
2. Install the dependencies using poetry
Navigate to the hw05
folder on your command line and install the dependencies as follows:
poetry install
3. Create a new database and populate it
1. Create a new database
Create a new database called photo-app
. In my opinion, the easiest way to do this is via the command line:
- Type:
psql -U postgres
- From within the psql shell, type:
CREATE DATABASE "photo-app";
- Type:
\q
to exit the shell
If
psql
is not recognized on your command line, see the Tutorial 10 instructions for configuring your path.
2. Add DB_URL to your .env file
Create an environment variable called DB_URL
in your .env
file. This variable will store the path to your database in a format that SQLAlchemy can understand. Your database connection information is represented as follows…
postgresql+psycopg://{user}:{password}@{host}/{database}
Each parameter of the connection string is described in the table below:
user | The database user (postgres ) |
password | The password associated with the postgres user (that you set in Tutorial 10) |
host | The address of the computer where your database is stored. For now, it will be localhost (since it’s stored on your local computer). When we set up your database using Heroku Postgres, your host will be in the cloud. |
database | The name of the database to which you want ot connect (photo-app ) |
Here is Sarah’s .env
file (your postgres password will be different):
FLASK_APP=app.py
# postgresql+psycopg://{user}:{password}@{host}/{database}
DB_URL=postgresql+psycopg://postgres:12345@localhost/photo-app
3. Populate your database
When you’re done, populate your database by running the following command on the command line (from the photo-app
directory):
# run from within the photo-app directory on the command line (with your venv activated):
poetry run python populate.py
Each time the populate.py
script is run, it will delete all of the tables and recreate them with the people in this class (reads from users.csv
). Feel free to take a look at how this script works if you’re curious! As you’re testing, feel free to run the populate.py
script to get a fresh copy of your database.
4. Verify Your Installation
To verify your installation…
1. Run Flask
Run flask with your virtual environment activated as follows:
poetry run flask run --debug
You should see output that looks similar to this:
* Serving Flask app 'app.py' (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 802-745-136
2. Verify that the API Tester Loads
Next, navigate to http://127.0.0.1:5000/api and you should see a screen that looks like this:
This UI can help you test your API (as can the automated tests and Postman).
3. Run the automated test suite
From a new terminal / shell window, navigate into the hw05/tests
and run the test suite while Flask is running. Note that you will need to have 2 terminals open: one that’s running flask and one that’s for the test suite.
Run the tests:
cd tests
poetry run python run_tests.py
The test suite should run 44 tests. You should see a bunch of error messages output to the screen with a summary at the bottom telling you that there were a bunch of failures and errors
run python run_tests.py
F.F.FFFFFFFFF...FFFF.F.FFF.FFFF.FFFFFF..FFFF
A bunch of error messages....
Ran 44 tests in 1.121s
FAILED (failures=33)
Your goal is to get all of the tests to pass.
Background Readings and Concepts
Before you start coding, let’s spend a few minutes reviewing some of the libraries and principles that we’ll be using for this assignment. Specifically:
1. SQL Alchemy
Please see the SQL Alchemy activity (to be done in class) to get guidance re: how to interact with the database. There are many examples in the activity that are directly related to completing your homework.
2. Flask RESTful
Now that you’ve had some practice interacting with your database using SQL Alchemy, you’re ready to create some REST API endpoints. As a reminder, REST is an architectural style that allows other clients to interact with some subset of your database over the web (using HTTP). To help us build these endpoints and keep them organized, we’re going to use a convenience library called Flask RESTful. You can learn more about this library by reading the documentation.
You will be using the Resource
class from Flask RESTful to build our various endpoints. I have already created a bunch of starter endpoints for you in the views
directory that you will be editing for this assignment. Note that each file in the views
directory has a List
and Detail
resource defined:
- The
List
resource is for returning lists of resources and creating new resources. - The
Detail
resource is for reading, updating, and deleting individual resources.
Examine the views/posts.py file
Open the views/posts.py
file so that you can take a look at the code for creating, reading, updating, and deleting Post
models.
- The
PostListEndpoint
class inviews/posts.py
is in charge of getting a list of posts (GET) and creating a new posts (POST). It is accessible with this endpoint: http://localhost:5000/api/posts/. - The
PostDetailEndpoint
class inviews/posts.py
is in charge of getting individual posts (GET), updating a post (PATCH), and deleting a post (DELETE). It is accessible with this endpoint: http://localhost:5000/api/posts/<id> (but replace <id> with a valid Post id).
Verify the Postman can interact with your various endpoints
When you’re done inspecting this file, please open Postman to test one of the Post
endpoints. You will have to download “Postman Agent” in order for Postman to be able to access endpoints on your local computer.
- Paste http://127.0.0.1:5000/api/posts/ into the address bar
- Ensure that the GET method is selected
- Click the “Send” button.
If it worked, you will see an empty list []
in the output panel. This is because the /api/posts/
endpoint for the GET method has not yet been implemented:
Requirements
1. Resources
The API should make 8 resources available to Photo App. Note that the to_dict()
methods in each Model definition should already generate the data structures shown below. This section just describes what these resources look like:
1. Post
Post resources should contain data about the post, the user, and any associated comments (order within the json object doesn’t matter). It should also contain some convenience data fields such as whether the current user has liked or bookmarked the post (e.g. current_user_like_id and current_user_bookmark_id). The to_dict()
method in each of the models already does this for you, so you will invoke the model’s to_dict() method when returning resources.
{
"id": 99,
"image_url": "https://picsum.photos/600/430?id=605",
"user": {
"id": 11,
"first_name": "Jason",
"last_name": "Lopez",
"username": "jason_lopez",
"email": "jason_lopez@yahoo.com",
"image_url": "https://picsum.photos/300/200?id=368",
"thumb_url": "https://picsum.photos/30/30?id=582"
},
"caption": "Sometimes degree food never sit probably remember main education race machine.",
"alt_text": "Descriptive text",
"display_time": "7 days ago",
"current_user_like_id": 56,
"current_user_bookmark_id": 20,
"likes": [
{
"id": 263,
"user_id": 2,
"post_id": 99
},
{
"id": 264,
"user_id": 20,
"post_id": 99
},
...
],
"comments": [
{
"id": 256,
"text": "Night ability such already study make bed there total tonight military democratic expect our serious second perform interesting modern send table window kid dinner message although degree law town standard head special image.",
"post_id": 99,
"user": {
"id": 15,
"first_name": "Lisa",
"last_name": "Parrish",
"username": "lisa_parrish",
"email": "lisa_parrish@yahoo.com",
"image_url": "https://picsum.photos/300/200?id=982",
"thumb_url": "https://picsum.photos/30/30?id=999"
}
},
{
"id": 257,
"text": "Start difference news gas administration hot deal support anyone explain task water anything more street better herself yourself its guess sport fall collection war natural foreign stage training example act eat television over happy dark bring character foreign low black establish skill rock science food close people help thought garden task test option help agency.",
"post_id": 99,
"user": {
"id": 12,
"first_name": "Samantha",
"last_name": "Acosta",
"username": "samantha_acosta",
"email": "samantha_acosta@yahoo.com",
"image_url": "https://picsum.photos/300/200?id=220",
"thumb_url": "https://picsum.photos/30/30?id=79"
}
},
...
]
}
2. User
The User resource should be structured like this:
{
"id": 1,
"first_name": "Roger",
"last_name": "Graves",
"username": "roger_graves",
"email": "roger_graves@gmail.com",
"image_url": "https://picsum.photos/300/200?id=994",
"thumb_url": "https://picsum.photos/30/30?id=953"
}
3. Bookmark
The Bookmark resource should just be an id and a post object without the comments:
{
"id:": 20,
"post": {
"id": 1,
"image_url": "https://picsum.photos/600/430?id=625",
"caption": "Many down a reveal woman key center technology citizen truth glass data food arm continue head cup career fear life write talk strong build hospital painting city rest wrong thought give light marriage something trial produce whom total cut third series same personal stage ahead move face personal gas tend religious wish.",
"alt_text": "Some alt text",
"user": {
"id": 1,
"first_name": "Roger",
"last_name": "Graves",
"username": "roger_graves",
"email": "roger_graves@gmail.com",
"image_url": "https://picsum.photos/300/200?id=994",
"thumb_url": "https://picsum.photos/30/30?id=953"
}
}
}
4. LikePost
The LikePost resource should be structured like this:
{
"id": 662,
"user_id": 12,
"post_id": 3
}
5. Story
The Story resource should be structured like this (mostly just a placeholder for now):
{
"id": 7,
"text": "Produce Democrat what city professor young though since southern history deep mother.",
"user": {
"id": 11,
"first_name": "Lindsey",
"last_name": "Shepard",
"username": "lindsey_shepard",
"email": "lindsey_shepard@gmail.com",
"image_url": "https://picsum.photos/300/200?id=159",
"thumb_url": "https://picsum.photos/30/30?id=689"
}
}
6. Comment Optional
The Comment resource should be structured like this:
{
"id": 2,
"text": "Than join finish special force according heart beautiful actually him candidate down site when or threat much commercial own suddenly food investment finally class into base friend still understand.",
"post_id": 1,
"user": {
"id": 24,
"first_name": "Joseph",
"last_name": "Mclaughlin",
"username": "joseph_mclaughlin",
"email": "joseph_mclaughlin@yahoo.com",
"image_url": "https://picsum.photos/300/200?id=474",
"thumb_url": "https://picsum.photos/30/30?id=987"
}
}
7. Following Optional
The Following resource should just be an id and a user object structured like this:
{
"id": 302,
"following": {
"id": 11,
"first_name": "Ashley",
"last_name": "Thornton",
"username": "ashley_thornton",
"email": "ashley_thornton@hotmail.com",
"image_url": "https://picsum.photos/300/200?id=939",
"thumb_url": "https://picsum.photos/30/30?id=465"
}
}
8. Follower Optional
The Follower resource (which is created using the “Following” model)resource should just be an id and a user object structured like this:
{
"id": 302,
"follower": {
"id": 11,
"first_name": "Ashley",
"last_name": "Thornton",
"username": "ashley_thornton",
"email": "ashley_thornton@hotmail.com",
"image_url": "https://picsum.photos/300/200?id=939",
"thumb_url": "https://picsum.photos/30/30?id=465"
}
}
2. Security
Access to data is contextual, based on the user who is logged into the system. For now, the person logged into the system is represented by the current_user
variable, which is set in app.py
.
Who is currently logged in?
The current user is hardcoded as user_id=12
Read Permissions
Our photo sharing app is more private than Instagram, and the rules are simple:
- The user can only see their own posts and stories, and the posts and stories (and associated comments) of people they’re following.
- The user can only see their own bookmarks, followers, and who is following them (this is private information).
Write Permissions
- The user can only bookmark, like, and comment on their own posts and the posts of people they’re following.
- The user can only edit and delete comments, posts, and stories that they created themselves.
3. HTTP Status Codes
All HTTP responses have an attached status code, which represents additional information about the request. A list of all valid HTTP status codes can be found here. For this assignment:
- All routes should return a 200 status code for a valid request unless POST is used, in which case a 201 status should be used.
- If a requests asks for information about data that does not exist (e.g. retrieving a Post with an id that isn’t present in the data), a 404 code should be used.
- If the user sent a request that is invalid, a 400 code should be used.
Your Tasks
Implement the following GET routes (20 Points)
The links included below point to a deployment of the hw05 solutions.
Method/Route | Description and Examples | Parameters | Response Type | Points | |
---|---|---|---|---|---|
1. | GET /api/posts | All posts in the current users' feed. This includes the current user's posts, as well as the people that the current user is following. You will need to modify this endpoint to get it to work as specified. |
|
List of Post objects | 4 |
2. | GET /api/posts/<int:id> | The post associated with the id (already started for you). | Post object | 4 | |
3. | GET /api/profile | The current user's profile. Please use self.current_user (which is holding an instance of the User model) to get this information. | User object | 4 | |
4. | GET /api/stories | List of stories of users you're following as well as your own story (if you have one). Please use the Story data model to get this information. | List of Story objects | 4 | |
5. | GET /api/bookmarks/ | Should display all of the current user's bookmarks (saved posts). Please use the Bookmark data model to get this information. | List of Bookmark objects | 4 |
Implement POST, PATCH, and DELETE routes (26 points)
The next set of routes involves storing and manipulating data in your PostgreSQL. A few notes:
- PATCH and DELETE methods can fail to find an object if it does not exist within data or if the current user does not have access to it. In these cases, you should mark the status of the response as 404, indicating that the requested resource could not be found.
- Receiving two POST requests with identical bodies should create two different objects with distinct ids.
- PostgreSQL will create ids for you automatically.
Method/Route | Description and Examples | Parameters | Response Type | Points | |
---|---|---|---|---|---|
1. | POST /api/posts | Should create a new post (already started for you). |
|
Post Object | 4 |
2. | PATCH /api/posts/<int:id> | Should update the post (already started for you). |
|
Post Object | 4 |
3. | DELETE /api/posts/<int:id> | Should delete the post (already started for you). Note that the delete is configured to cascade (all associated comments, likes, bookmarks, etc. will also be deleted). | Some JSON message | 2 | |
4. | POST /api/bookmarks/ | Should create a bookmark (saves a post to bookmarks). |
|
Bookmark object | 2 |
5. | DELETE /api/bookmarks/<int:id> | Should delete a bookmark. Remember, you can only delete a bookmark that you created. | Some JSON message | 2 | |
6. | POST /api/posts/likes | Should create a like. |
|
LikePost object | 2 |
7. | DELETE /api/posts/likes/<int:id> | Should delete a like. Remember, you can only delete a "like" that you created. | Some JSON message | 2 | |
8. | Optional POST /api/comments | Should create a new comment associated with the specified post. |
|
Comment Object | 2 |
9. | Optional DELETE /api/comments/<int:id> | Should delete a comment. Remember, you can only delete a comment that you created. | Some JSON message | 2 | |
10. | Optional POST /api/following | Should create a following record. |
|
Following object | 2 |
11. | Optional DELETE /api/following/<int:id> | Should delete a following record. Remember, you can only delete a "following" that you created. | Some JSON message | 2 |
What to Turn In
Please review the requirements above and ensure you have met them. Specifically:
- 2 points for getting everything set up and running
- 20 points for GET requests
- 18 points for POST / PATCH / DELETE requests
- Extra credit 8 points (Comments, Follow / Unfollow)
When you’re done, please submit the following to the Moodle:
- A link to your GitHub Repo
- If you worked with a partner, please list your partner