Skip to content

Zoisite - Jackie Aponte#28

Open
japonte08 wants to merge 8 commits intoAda-C19:mainfrom
japonte08:main
Open

Zoisite - Jackie Aponte#28
japonte08 wants to merge 8 commits intoAda-C19:mainfrom
japonte08:main

Conversation

@japonte08
Copy link
Copy Markdown

No description provided.

Copy link
Copy Markdown

@yangashley yangashley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job with Solar System!

I left some comments that I'm hoping you can apply to your work on Task List!

Comment thread app/routes.py
Comment on lines +17 to +23
planets_response = []
for planet in planets:
planets_response.append({
"id": planet.id,
"name": planet.name,
"description": planet.description
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good spot to try out list comprehension. You could create an instance method in the Planet class to_dict that turns an instance of planet into a dictionary representing a planet. We need to send back objects represented by dictionaries since our client doesn't understand python.

Comment thread app/models/planet.py
class Planet(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String)
description = db.Column(db.String) No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add the instance method to_dict and class method from_dict to this class, which will shorten up the code in your routes.

    def to_dict(self):
        return dict(
            id = self.id,
            name = self.name,
            description = self.description
        )

    @classmethod
    def from_dict(cls, planet_data):
        return cls(
            name = planet_data["name"],
            description = planet_data["description"]
        )

No problem if you don't have time to refactor here, but try implementing this for Task List.

Comment thread app/routes.py

planets_bp = Blueprint("planets", __name__, url_prefix="/planets")

@planets_bp.route("", methods=["GET", "POST"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer that GET and POST are separate routes

Comment thread app/routes.py
})
return jsonify(planets_response)

elif request.method == "POST":
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Older patterns for designing routes in Flask have shown a route that handles different REST methods and then uses if/elif to conditionally return the correct response. By separating the two request types out into their own methods, the code becomes more readable and each function has a single responsibility.

Comment thread app/routes.py
Comment on lines +27 to +29
request_body = request.get_json()
new_planet = Planet(name=request_body["name"],
description=request_body["description"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add in some error handling here since request_body is the data that a client sends via the request body. We should assume that requests could have bad data, either missing keys or values or the keys could be misspelled.

If the request_body didn't have "name" but had "planet_name" then line 28 would throw an unhandled exception.

Comment thread app/routes.py
return planet


@planets_bp.route("/<planet_id>", methods=["GET", "PUT", "DELETE"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer each request type to have their own function.

Comment thread tests/conftest.py

db.session.add_all([planet_mars, planet_jupiter])

db.session.commit() No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that the Learn reading has a two_saved_books that looks like this. It creates two objects, adds them and commits to the DB. It would be nice if this fixture (while not shown in Learn) actually returns the objects it creates so that you can use them for comparison in your unit tests, like this and this

Comment thread tests/test_routes.py
Comment on lines +31 to +40
assert response_body == [{
"id": 1,
"name": "Mars",
"description": "Smallest planet"
},
{
"id": 2,
"name": "Jupiter",
"description": "Largest planet"
}]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the fixture two_saved_planets returned two planet objects, you'd be able to compare what's in the response body against what was used to create records in the database (as we do in Flasky)

Comment thread tests/test_routes.py
Comment on lines +45 to +53
response = client.post("/planets", json={
"name": "Earth",
"description": "Water planet"
})
response_body = response.get_json()

# Assert
assert response.status_code == 201
assert response_body == "Planet Earth successfully created" No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to using the response to write assertions, your test would be more robust if after the post request, you query the database for the object you just created. Since we run fixtures before each test, we know that before your test runs we will have a database with empty tables, after your post request in the test we will have one planet record. We can query for that one record by assuming it's id will be 1 and compare its attributes to the request body you sent with the post request. Like this

Comment thread app/routes.py

db.session.commit()

return make_response(f"Planet #{planet.id} successfully updated")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we pass a string to make_response(), the datatype that gets sent back as a header in the response is HTML. However, your GET /planets and POST /planets routes return jsonify'd objects which have a datatype of JSON. Therefore, one API is sending a mix of HTML and JSON responses, which isn't ideal.

I know this is what was shown in Learn 😖. In the last reading from Part 6 Testing, there's mention of using jsonify like this:
return make_response(jsonify(f"Planet #{planet.id} successfully updated"))

Doing so changes the header data type from HTML to JSON. Making the changes in the DELETE requests would make your API only return JSON responses, which is good.

An even better way to go about this, I think, would be for your PUT and your POST requests to just return a dictionary representing the planet that was inserted into the DB or the planet that was updated in the DB. So for your PUT and your POST request you could return a response like:

return jsonify(planet.to_dict()), 201 # or whatever correct status code you want to pass in the response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants