-
Notifications
You must be signed in to change notification settings - Fork 79
Tigers - Kat and Sunny - Wave 03-06 #34
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
ff392d5
73f1859
6b69c6a
57d860a
158844e
d8cc2b0
ba16cc0
34e539e
914af00
80a4a0b
00759f9
7079622
df05165
0b4f04a
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,34 @@ | ||
| 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__) | ||
|
|
||
| if not test_config: | ||
| app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False | ||
| app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get( | ||
| "SQLALCHEMY_DATABASE_URI") | ||
| else: | ||
| app.config["TESTING"] = True | ||
| app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||
| 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 planets_bp | ||
| app.register_blueprint(planets_bp) | ||
|
|
||
|
|
||
| return app | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| from app import db | ||
| from flask import abort, make_response | ||
|
|
||
| class Planet(db.Model): | ||
| id = db.Column(db.Integer,primary_key = True, autoincrement= True) | ||
| name = db.Column(db.String) | ||
| description = db.Column(db.String) | ||
| diameter = db.Column(db.String) | ||
|
|
||
| def to_dict(self): | ||
| return { | ||
| "id": self.id, | ||
| "name": self.name, | ||
| "description": self.description, | ||
| "diameter": self.diameter | ||
| } | ||
|
|
||
| @classmethod | ||
| def from_dict(cls, planet_data): | ||
| new_planet = Planet(name=planet_data["name"], | ||
| description=planet_data["description"], | ||
| diameter=planet_data["diameter"]) | ||
| return new_planet | ||
|
|
||
| def update(self, req_body): | ||
| try: | ||
| self.name = req_body["name"] | ||
| self.description = req_body["description"] | ||
| self.diameter = req_body["diameter"] | ||
| except KeyError as error: | ||
| abort(make_response({"message": f"Missing attribute: {error}"},400)) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,76 @@ | ||
| from flask import Blueprint | ||
| from app import db | ||
| from app.models.planet import Planet | ||
| from flask import Blueprint, jsonify, make_response, request, abort | ||
|
|
||
|
|
||
| planets_bp = Blueprint("planets", __name__, url_prefix="/planets") | ||
|
|
||
| @planets_bp.route("", methods=["POST"]) | ||
| def add_planet(): | ||
| request_body = request.get_json() | ||
| new_planet = Planet.from_dict(request_body) | ||
|
|
||
|
Comment on lines
+10
to
+12
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. Some some validation on request body would be nice here to ensure required fields are present. |
||
| db.session.add(new_planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Planet {new_planet.name} successfully created"), 201) | ||
|
|
||
| @planets_bp.route("", methods=["GET"]) | ||
| def read_all_planets(): | ||
| name_query = request.args.get("name") | ||
| description_query = request.args.get("description") | ||
| diameter_query = request.args.get("diameter") | ||
| if name_query: | ||
| planets = Planet.query.filter_by(name=name_query) | ||
| elif description_query: | ||
| planets = Planet.query.filter_by(description=description_query) | ||
| elif diameter_query: | ||
| planets = Planet.query.filter_by(diameter=diameter_query) | ||
| else: | ||
| planets = Planet.query.all() | ||
|
|
||
| planets_response = [planet.to_dict() for planet in planets] | ||
| return jsonify(planets_response) | ||
|
|
||
| def validate_model(cls, model_id): | ||
|
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. Good helper function |
||
| try: | ||
| model_id = int(model_id) | ||
| except: | ||
| abort(make_response({"message": f"{cls.__name__} {model_id} invalid"}, 400)) | ||
|
|
||
| model = cls.query.get(model_id) | ||
|
|
||
| if not model: | ||
| abort(make_response({"message": f"{cls.__name__} {model_id} not found"}, 404)) | ||
|
|
||
| return model | ||
|
|
||
| @planets_bp.route("/<planet_id>", methods = ["GET"]) | ||
| def read_one_planet(planet_id): | ||
| planet = validate_model(Planet,planet_id) | ||
| return planet.to_dict() | ||
|
|
||
| @planets_bp.route("/<planet_id>", methods=["PUT"]) | ||
| def update_planet(planet_id): | ||
| planet = validate_model(Planet,planet_id) | ||
|
|
||
| request_body = request.get_json() | ||
|
|
||
| planet.update(request_body) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Planet #{planet.id} successfully updated")) | ||
|
|
||
| @planets_bp.route("/<planet_id>", methods=["DELETE"]) | ||
| def delete_planet(planet_id): | ||
| planet = validate_model(Planet,planet_id) | ||
|
|
||
| db.session.delete(planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Planet #{planet.id} successfully deleted")) | ||
|
|
||
|
|
||
|
Comment on lines
+54
to
+73
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. Just noting these functions are untested. |
||
|
|
||
|
|
||
|
|
||
| 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 | ||
| ocean_planet = Planet(name="Ocean Planet", | ||
| description="watr 4evr", | ||
| diameter = "0 miles") | ||
| mountain_planet = Planet(name="Mountain Planet", | ||
| description="i luv 2 climb rocks", | ||
| diameter = "1 mile") | ||
|
|
||
| db.session.add_all([ocean_planet, mountain_planet]) | ||
| db.session.commit() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| 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 == [] | ||
|
|
||
| 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": "Ocean Planet", | ||
| "description": "watr 4evr", | ||
| "diameter": "0 miles" | ||
| } | ||
|
|
||
| def test_get_one_planet_with_no_data(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_get_one_planet_with_invalid_id(client): | ||
| # Act | ||
| response = client.get("/planets/**") | ||
| response_body = response.get_json() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 400 | ||
| assert response_body == {"message": "Planet ** invalid"} | ||
|
|
||
| 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": "Ocean Planet", | ||
| "description": "watr 4evr", | ||
| "diameter": "0 miles" | ||
| }, | ||
| { | ||
| "id": 2, | ||
| "name": "Mountain Planet", | ||
| "description": "i luv 2 climb rocks", | ||
| "diameter": "1 mile" | ||
| }] | ||
| assert len(response_body) == 2 | ||
|
|
||
| def test_create_one_planet(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. Having a test with an invalid request body would be nice. |
||
| # Act | ||
| response = client.post("/planets", json={ | ||
| "name": "New Planet", | ||
| "description": "The Best!", | ||
| "diameter": "5 miles" | ||
| }) | ||
| response_body = response.get_json() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 201 | ||
| assert response_body == "Planet New Planet 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 |
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.
Great helper methods!