-
Notifications
You must be signed in to change notification settings - Fork 61
Sharks - Poppy and Danielle #8
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
37a5253
6029082
5445087
fd60f2e
b3a981f
421d3d1
d5b0ed6
b12b280
a3f508d
89f97cc
a40971f
3954ff3
4707883
61d7100
05712cd
3d22747
13a75e5
f3c0e71
59893ad
5be6929
f860662
ab981c1
8b06e08
0b12beb
8c05d35
c2c8c52
739dfc6
c34fea3
b01f8d8
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 |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| web: gunicorn 'app:create_app()' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,31 @@ | ||
| 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 import solar_bp | ||
| app.register_blueprint(solar_bp) | ||
|
|
||
| return app | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Validate if GET by Planet ID does not exist | ||
| from flask import Blueprint, jsonify, request, make_response, abort | ||
| from app.models.planet import Planet | ||
|
|
||
|
|
||
| def validate_planet(planet_id): | ||
| try: | ||
| planet_id = int(planet_id) | ||
| except: | ||
| abort(make_response({"message": f"planet {planet_id} invalid"}, 400)) | ||
|
|
||
| planet = Planet.query.get(planet_id) | ||
|
|
||
| if not planet: | ||
| abort(make_response({"message": f"planet {planet_id} not found"}, 404)) | ||
|
|
||
| return planet | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| from ctypes import sizeof | ||
| from app import db | ||
|
|
||
| class Moon(db.Model): | ||
| id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
| size = db.Column(db.String) | ||
| description = db.Column(db.String) | ||
| color = db.Column(db.String) | ||
| planet_id = db.Column(db.Integer, db.ForeignKey('planet.id')) | ||
| planet = db.relationship("Planet", back_populates="moons") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| 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) | ||
| moons = db.relationship("Moon", back_populates="planet") | ||
|
|
||
| def to_json(self): | ||
| return {"id": self.id, | ||
| "name": self.name, | ||
| "description": self.description, | ||
| "color": self.color | ||
| } | ||
|
|
||
| def update(self, req_body): | ||
|
|
||
| self.name = req_body["name"] | ||
| self.description = req_body["description"] | ||
| self.color = req_body["color"] | ||
|
|
||
| @classmethod | ||
| def create(cls, req_body): | ||
| new_planet = cls( | ||
| name=req_body['name'], | ||
| description=req_body['description'], | ||
| color=req_body['color'] | ||
| ) | ||
|
|
||
| return new_planet | ||
|
Comment on lines
+17
to
+31
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. This comment applies to your create() and update() methods: How would you add in validation to make sure that all the required fields are sent in the request to your server? For example, if someone sent a POST request but left off color, then line 27 would throw KeyError that it couldn't find color. it would be nice to handle the error and return a message so that the client knows their request was invalid and they need to include color. Something to think about for Task List. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,61 @@ | ||
| from flask import Blueprint | ||
| from flask import Blueprint, jsonify, request, make_response, abort | ||
| from app import db | ||
| from app.models.planet import Planet | ||
| from .helper import validate_planet | ||
|
|
||
| solar_bp = Blueprint("planets", __name__, url_prefix="/planets") | ||
|
|
||
| # GET and POST planets | ||
| @solar_bp.route("", methods=["POST", "GET"]) | ||
|
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. It's good practice to keep in mind SRP - single responsibility principle for methods here. Each method should handle its own route so you can split the POST and the GET requests into 2 separate methods. Maybe one called create_planet() for POST and get_planets() for GET |
||
| def handle_planets(): | ||
| # Create a planet | ||
| if request.method== "POST": | ||
| 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} successfully created"), 201) | ||
|
|
||
| # Get all planets, or filter by planet name | ||
| elif request.method== "GET": | ||
| name_query=request.args.get('name') | ||
| if name_query: | ||
| planets=Planet.query.filter_by(name=name_query) | ||
| else: | ||
| planets=Planet.query.all() | ||
| planets_response=[] | ||
| for planet in planets: | ||
| planets_response.append(planet.to_json()) | ||
|
|
||
| return jsonify(planets_response), 200 | ||
|
|
||
|
|
||
| # GET ONE Planet | ||
| @solar_bp.route("/<planet_id>", methods=["GET"]) | ||
| def read_one_planet(planet_id): | ||
| planet = validate_planet(planet_id) | ||
| return jsonify(planet.to_json()), 200 | ||
|
|
||
| # UPDATE Planet | ||
| @solar_bp.route("/<planet_id>", methods=["PUT"]) | ||
| def update_planet(planet_id): | ||
| planet = validate_planet(planet_id) | ||
| request_body = request.get_json() | ||
|
|
||
| planet.update(request_body) | ||
|
|
||
| db.session.commit() | ||
|
|
||
| return make_response(f"Planet #{planet.id} successfully updated") | ||
|
|
||
| # DELETE Planet | ||
| @solar_bp.route("/<planet_id>", methods=["DELETE"]) | ||
| def delete_planet(planet_id): | ||
| planet = validate_planet(planet_id) | ||
|
|
||
| db.session.delete(planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(f"Planet #{planet.id} successfully deleted") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
|
|
||
| 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): | ||
|
Comment on lines
+16
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. 👍 |
||
| planet_pluto = Planet(name="Pluto", | ||
| description="nobody loves me :(", | ||
| color="blue") | ||
| planet_sun = Planet(name="Sun", | ||
| description="im hot as heck son", | ||
| color="fiery red") | ||
| db.session.add_all([planet_pluto, planet_sun]) | ||
| db.session.commit() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
|
|
||
| def test_get_all_planets_with_no_records(client): | ||
|
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. These tests look good to me! 👍 |
||
| # Act | ||
| response = client.get("/planets") | ||
| response_body = response.get_json() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 200 | ||
| assert response_body == [] | ||
|
|
||
| def test_get_all_planets(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": "Pluto", | ||
| "description": "nobody loves me :(", | ||
| "color": "blue" | ||
| }, | ||
| { | ||
| "id": 2, | ||
| "name": "Sun", | ||
| "description": "im hot as heck son", | ||
| "color": "fiery red" | ||
| } | ||
| ] | ||
|
|
||
| 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": "Pluto", | ||
| "description": "nobody loves me :(", | ||
| "color": "blue" | ||
| } | ||
|
|
||
| def test_get_one_planet_with_no_records(client): | ||
| # Act | ||
| response = client.get("/planets/1") | ||
| response_body = response.get_json() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 404 | ||
| assert response_body == {"message": "planet 1 not found"} | ||
|
|
||
|
|
||
| def test_create_one_planet(client): | ||
| # Act | ||
| response = client.post("/planets", json={ | ||
| "name": "Mars", | ||
| "description": "Is Bruno Mars counted as Mars?", | ||
| "color": "orange" | ||
| }) | ||
| response_body = response.get_json() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 201 | ||
| assert response_body == "Planet Mars successfully 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 |
Uh oh!
There was an error while loading. Please reload this page.
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.
Nice work pulling this into a helper to keep your routes short and sweet. In the future, you might need to make 'helper.py' more specific. Think about Task List, you have Goal and Task, you may need helpers for each. If you refactor helper methods into a helper file then you could have goal_helper.py and task_helper.py