-
Notifications
You must be signed in to change notification settings - Fork 61
Sharks - Jeannie Z. & Juli T. #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
85c8372
ba93e91
2576c0c
78c4603
9fccce8
dd61ed8
8a4fcb6
880cfea
456e4a0
ce18f36
604991f
feaf92e
ae0ab71
cd4ee9b
5513730
cca4d02
3242fb4
7aadde2
549f779
ef7d221
c2be4e0
fdde89e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,32 @@ | ||
| from flask import Flask | ||
| from flask_sqlalchemy import SQLAlchemy | ||
| from flask_migrate import Migrate | ||
| from dotenv import load_dotenv | ||
| import os | ||
|
|
||
|
|
||
| db = SQLAlchemy() | ||
| migrate = Migrate() | ||
| load_dotenv() | ||
|
|
||
|
|
||
| def create_app(test_config=None): | ||
| app = Flask(__name__) | ||
|
|
||
|
|
||
| app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False | ||
| if not test_config: | ||
| app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get( | ||
| "SQLALCHEMY_DATABASE_URI") | ||
| else: | ||
| app.config["TESTING"] = True | ||
| app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( | ||
| "SQLALCHEMY_TEST_DATABASE_URI") | ||
|
|
||
| db.init_app(app) | ||
| migrate.init_app(app, db) | ||
| from app.models.planet import Planet | ||
|
|
||
| from .routes.planet_routes import planet_bp | ||
| app.register_blueprint(planet_bp) | ||
| return app | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from app import db | ||
|
|
||
|
|
||
| class Planet(db.Model): | ||
| id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
| name = db.Column(db.String) | ||
| description = db.Column(db.String) | ||
| color = db.Column(db.String) | ||
|
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be able to make planets with no name? To prevent our API from creating a planet with a name = db.Column(db.String, nullable=False) |
||
|
|
||
| def to_json(self): | ||
| return { | ||
| "id": self.id, | ||
| "name": self.name, | ||
| "description": self.description, | ||
| "color": self.color | ||
| } | ||
|
|
||
| def update(self, request_body): | ||
| self.name = request_body["name"] | ||
| self.description = request_body["description"] | ||
| self.color = request_body["color"] | ||
|
|
||
| @classmethod | ||
| def create(cls, request_body): | ||
| new_planet = cls( | ||
| name=request_body['name'], | ||
| description=request_body['description'], | ||
| color=request_body['color'] | ||
| ) | ||
| return new_planet | ||
|
Comment on lines
+10
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of helper methods and a class method! |
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| from app.models.planet import Planet | ||
| from flask import abort, make_response, jsonify | ||
|
|
||
| # helper function to validate | ||
| def validate_planet(planet_id): | ||
| try: | ||
| planet_id = int(planet_id) | ||
| except: | ||
| return abort(make_response(jsonify(f"Planet {planet_id} is invalid"), 400)) | ||
|
|
||
| planet = Planet.query.get(planet_id) | ||
|
|
||
| if not planet: | ||
| return abort(make_response(jsonify(f"Planet {planet_id} does not exist"), 404)) | ||
| return planet | ||
|
Comment on lines
+5
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helpers look great! 👍 We may want to consider making the validation code as generic as possible to utilize any class and id or renaming this file to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A more generic validation helper method would look like: from flask import make_response, abort
def validate_object(cls, id):
try:
id = int(id)
except:
return abort(make_response({"message": f"{cls} {id} is invalid"}, 400))
obj = cls.query.get(id)
if not obj:
abort(make_response({"message": f"{cls} {id} not found"},404))
return obj |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| from app import db | ||
| from app.models.planet import Planet | ||
| from flask import Blueprint, jsonify, abort, make_response, request | ||
| from .helpers import validate_planet | ||
|
|
||
| planet_bp = Blueprint("planet", __name__, url_prefix="/planets") | ||
|
|
||
| @planet_bp.route("", methods=["POST"]) | ||
| def create_planet(): | ||
| request_body = request.get_json() | ||
|
|
||
| new_planet = Planet.create(request_body) | ||
|
|
||
| db.session.add(new_planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Planet {new_planet.name} has been created"), 201) | ||
|
Comment on lines
+8
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@planet_bp.route("", methods=["POST"])
def create_planet():
request_body = request.get_json()
new_planet = Planet.create(request_body)
db.session.add(new_planet)
db.session.commit()
return jsonify(f"Planet {new_planet.name} has been created"), 201 |
||
|
|
||
| @ planet_bp.route("", methods=["GET"]) | ||
| def get_planets(): | ||
| planet_query = request.args.get("name") | ||
| color_query = request.args.get("color") | ||
| if planet_query: | ||
| planets = Planet.query.filter_by(name=planet_query) | ||
| elif color_query: | ||
| planets = Planet.query.filter_by(color=color_query) | ||
| else: | ||
| planets = Planet.query.all() | ||
|
|
||
| planets_response = [planet.to_json() for planet in planets] | ||
|
|
||
| return make_response(jsonify(planets_response), 200) | ||
|
Comment on lines
+19
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work with filtering and using list comprehension! The comment about not needing |
||
|
|
||
| @ planet_bp.route("/<planet_id>", methods=["GET"]) | ||
| def get_one_planet(planet_id): | ||
| planet = validate_planet(planet_id) | ||
| return make_response(jsonify(planet.to_json()), 200) | ||
|
Comment on lines
+34
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Nice work! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, if a view function returns a dictionary ( @planets_bp.route("/<planet_id>", methods=["GET"])
def get_one_planet(planet_id):
planet = validate_planet(planet_id)
return planet.to_json(), 200Here's the documentation with more info: |
||
|
|
||
| @ planet_bp.route("/<planet_id>", methods=["PUT"]) | ||
| def update_planet(planet_id): | ||
| planet = validate_planet(planet_id) | ||
| request_body = request.get_json() | ||
|
|
||
| try: | ||
| Planet.update(request_body) | ||
| db.session.commit() | ||
| except KeyError: | ||
| return abort(make_response(jsonify("Missing information")), 400) | ||
|
|
||
| return make_response(jsonify(f"Planet #{planet.id} successfully updated"), 200) | ||
|
Comment on lines
+39
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work! We may want to consider validating the request_body data in a separate helper method. |
||
|
|
||
| @ planet_bp.route("/<planet_id>", methods=["DELETE"]) | ||
| def delete_one_planet(planet_id): | ||
| planet = validate_planet(planet_id) | ||
|
|
||
| db.session.delete(planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Planet {planet.id} has been deleted"), 200) | ||
|
Comment on lines
+52
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Comment about not needing |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import pytest | ||
| from app import create_app | ||
| from app import db | ||
| from flask.signals import request_finished | ||
| from app.models.planet import Planet | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def app(): | ||
| app = create_app({"TESTING": True}) | ||
|
|
||
| @request_finished.connect_via(app) | ||
| def expire_session(sender, response, **extra): | ||
| db.session.remove() | ||
|
|
||
| with app.app_context(): | ||
| db.create_all() | ||
| yield app | ||
|
|
||
| with app.app_context(): | ||
| db.drop_all() | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def client(app): | ||
| return app.test_client() | ||
|
|
||
| @pytest.fixture | ||
| def two_saved_planets(app): | ||
| # Arrange | ||
| planet_one = Planet(name="Mars", | ||
| description="Close enough", | ||
| color="Red") | ||
| planet_two = Planet(name="Earth", | ||
| description="we out here", | ||
| color = "Green") | ||
|
|
||
| db.session.add_all([planet_one, planet_two]) | ||
| db.session.commit() | ||
|
Comment on lines
+29
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| #`GET` `/planets` returns `200` and an empty array. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test routes look great 👍 ! |
||
| def test_get_all_planets_with_no_records(client): | ||
| # ACT | ||
| response = client.get("/planets") | ||
| response_body = response.get_json() | ||
|
|
||
| # ASSERT | ||
| assert response.status_code == 200 | ||
| assert response_body == [] | ||
|
|
||
|
|
||
| # GET /planets/1 returns a response body that matches our fixture | ||
| def test_get_one_planet(client, two_saved_planets): | ||
| #ACT | ||
| response = client.get("/planets/1") | ||
| response_body = response.get_json() | ||
|
|
||
| #ASSERT | ||
| assert response.status_code == 200 | ||
| assert response_body == { | ||
| "id" : 1, | ||
| "name": "Mars", | ||
| "description": "Close enough", | ||
| "color": "Red" | ||
| } | ||
|
|
||
|
|
||
| # GET /planets/1 with no data in test database (no fixture) returns a 404 | ||
| def test_get_one_planet_but_no_data(client): | ||
| # ACT | ||
| response = client.get("/planets/1") | ||
| response_body = response.get_json() | ||
|
|
||
| # ASSERT | ||
| assert response.status_code == 404 | ||
| assert response_body == "Planet 1 does not exist" | ||
|
|
||
|
|
||
| # GET /planets with valid test data (fixtures) returns a 200 with | ||
| # an array including appropriate test data | ||
| def test_get_planets_with_records(client, two_saved_planets): | ||
| # ACT | ||
| response = client.get("/planets") | ||
| response_body = response.get_json() | ||
|
|
||
| # ASSERT | ||
| assert response.status_code == 200 | ||
| assert response_body == [ | ||
| { | ||
| "id" : 1, | ||
| "name": "Mars", | ||
| "description": "Close enough", | ||
| "color": "Red"}, | ||
| { | ||
| "id" : 2, | ||
| "name": "Earth", | ||
| "description":"we out here", | ||
| "color" : "Green"}] | ||
|
|
||
|
|
||
| # POST /planets with a JSON request body returns a 201 | ||
| def test_create_one_planet(client): | ||
| #ACT | ||
| response = client.post("/planets", json={ | ||
| "name": "new planet", | ||
| "description": "with aliens", | ||
| "color": "rainbow" | ||
| }) | ||
| response_body = response.get_json() | ||
|
|
||
| #ASSERT | ||
| assert response.status_code == 201 | ||
| assert response_body == "Planet new planet has been created" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Generic single-database configuration. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # A generic, single database configuration. | ||
|
|
||
| [alembic] | ||
| # template used to generate migration files | ||
| # file_template = %%(rev)s_%%(slug)s | ||
|
|
||
| # set to 'true' to run the environment during | ||
| # the 'revision' command, regardless of autogenerate | ||
| # revision_environment = false | ||
|
|
||
|
|
||
| # Logging configuration | ||
| [loggers] | ||
| keys = root,sqlalchemy,alembic | ||
|
|
||
| [handlers] | ||
| keys = console | ||
|
|
||
| [formatters] | ||
| keys = generic | ||
|
|
||
| [logger_root] | ||
| level = WARN | ||
| handlers = console | ||
| qualname = | ||
|
|
||
| [logger_sqlalchemy] | ||
| level = WARN | ||
| handlers = | ||
| qualname = sqlalchemy.engine | ||
|
|
||
| [logger_alembic] | ||
| level = INFO | ||
| handlers = | ||
| qualname = alembic | ||
|
|
||
| [handler_console] | ||
| class = StreamHandler | ||
| args = (sys.stderr,) | ||
| level = NOTSET | ||
| formatter = generic | ||
|
|
||
| [formatter_generic] | ||
| format = %(levelname)-5.5s [%(name)s] %(message)s | ||
| datefmt = %H:%M:%S |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Good work in setting up the Flask app!