-
Notifications
You must be signed in to change notification settings - Fork 79
Tigers: Misha + Leimomi #24
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
6988a8c
1fd6bd8
a4e0a8e
6ec6e5d
e9030af
c509cd6
9b75377
13b5382
bb257e2
0541226
ae506cd
0a56590
84ba83d
e94fb40
6abfc1b
1a94408
d564717
fc56176
7c78c99
0637fa7
640717f
7c3f1fe
266a6de
0c97dd5
9bce6cc
83de446
4a398f9
9069d67
c8cd491
1eb2d9a
437893d
375836c
7143671
f59fe56
6d9f2ef
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,3 +1,4 @@ | ||
| .env | ||
| .vscode | ||
| .DS_Store | ||
|
|
||
|
|
||
| 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__) | ||
|
|
||
| return app | ||
| app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False | ||
| app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development' | ||
|
|
||
| if not test_config: | ||
| 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 planet_bp | ||
| app.register_blueprint(planet_bp) | ||
|
|
||
| return app |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| from app import db | ||
|
|
||
| class Planet(db.Model): | ||
| id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
| name = db.Column(db.String) | ||
| color = db.Column(db.String) | ||
| livability = db.Column(db.Integer) | ||
| moons = db.Column(db.Integer) | ||
| is_dwarf = db.Column(db.Boolean) | ||
|
|
||
| def to_dict(self): | ||
| planet_as_dict = {} | ||
| planet_as_dict["id"] = self.id | ||
| planet_as_dict["name"] = self.name | ||
| planet_as_dict["color"] = self.color | ||
| planet_as_dict["livability"] = self.livability | ||
| planet_as_dict["moons"] = self.moons | ||
| planet_as_dict["is_dwarf"] = self.is_dwarf | ||
| return planet_as_dict | ||
|
|
||
| @classmethod | ||
| def from_dict(cls, planet_data): | ||
| new_planet = Planet(name=planet_data["name"], | ||
| color=planet_data["color"], | ||
| moons=planet_data["moons"], | ||
| livability=planet_data["livability"], | ||
| is_dwarf=planet_data["is_dwarf"]) | ||
| return new_planet | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,69 @@ | ||
| from flask import Blueprint | ||
| from app import db | ||
| from app.models.planet import Planet | ||
| from flask import Blueprint, jsonify, abort, make_response, request | ||
|
|
||
| planet_bp = Blueprint("planets", __name__, url_prefix="/planets") | ||
|
|
||
| def validate_model(cls, model_id): | ||
| try: | ||
| model_id = int(model_id) | ||
| except: | ||
| abort(make_response({"message":f"the planet {cls.__name__} {model_id} is invalid, please search by planet_id."}, 400)) | ||
|
|
||
| planet = cls.query.get(model_id) | ||
|
|
||
| if not planet: | ||
| abort(make_response({"message":f"the planet {cls.__name__} {model_id} doesn't exist."}, 404)) | ||
| return planet | ||
|
|
||
|
Comment on lines
+7
to
+18
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 validation function |
||
| @planet_bp.route("/<model_id>", methods = ["GET"]) | ||
| def get_one_planet(model_id): | ||
| planet = validate_model(Planet, model_id) | ||
| # planet = cls.query.get(model_id) | ||
| return planet.to_dict() | ||
|
|
||
| @planet_bp.route("", methods=["GET"]) | ||
| def get_all_planets(): | ||
| name_query = request.args.get("name") | ||
| if name_query: | ||
| planets = Planet.query.filter_by(name=name_query) | ||
| else: | ||
| planets = Planet.query.all() | ||
|
|
||
| planet_response = [] | ||
| for planet in planets: | ||
| planet_response.append(planet.to_dict()) | ||
| return jsonify(planet_response) | ||
|
|
||
| @planet_bp.route("", methods=["POST"]) | ||
| def create_new_planet(): | ||
| request_body = request.get_json() | ||
|
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. Doing some validation on the request body would be appropriate. |
||
| new_planet = Planet.from_dict(request_body) | ||
|
|
||
| db.session.add(new_planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(f"Planet {new_planet.name} successfully created", 201) | ||
|
|
||
| @planet_bp.route("/<model_id>", methods = ["PUT"]) | ||
| def update_planet(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. Just noting this is untested. |
||
| planet = validate_model(Planet, model_id) | ||
| request_body = request.get_json() | ||
|
|
||
| planet.name = request_body["name"], | ||
| planet.color = request_body["color"], | ||
| planet.moons = request_body["moons"], | ||
| planet.livability = request_body["livability"], | ||
| planet.is_dwarf = request_body["is_dwarf"] | ||
|
Comment on lines
+53
to
+57
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. Doing some validation on the request body here would also be appropriate. |
||
|
|
||
| db.session.commit() | ||
|
|
||
| return make_response(f"Planet #{model_id} successfully updated.") | ||
|
|
||
| @planet_bp.route("/<model_id>", methods = ["DELETE"]) | ||
| def delete_planet(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. Just noting this is untested. |
||
| planet = validate_model(Planet, model_id) | ||
| db.session.delete(planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(f"Planet #{model_id} successfully deleted.") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import pytest | ||
| from app import create_app | ||
| from app import db | ||
| from app.models.planet import Planet | ||
| from flask.signals import request_finished | ||
|
|
||
| @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 saved_test_planets(app): | ||
|
|
||
| test_planet1 = Planet(color= "pink", is_dwarf= True, livability= 3, moons= 99, name= "Pretend Planet X") | ||
| test_planet2 = Planet(color= "purple", is_dwarf= False, livability= 7.4, moons= 1, name= "Planet Pretend Z") | ||
|
|
||
| db.session.add_all([test_planet1, test_planet2]) | ||
| db.session.commit() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| from app.models.planet import Planet | ||
|
|
||
|
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. I love these tests ❤️ |
||
| def test_to_dict_no_missing_data(): | ||
| # Arrange | ||
| test_data = Planet( | ||
| id = 1, | ||
| color = "pink", | ||
| is_dwarf = True, | ||
| livability = 3, | ||
| moons = 99, | ||
| name = "Pretend Planet X") | ||
| # Act | ||
| result = test_data.to_dict() | ||
|
|
||
| # Assert | ||
| assert len(result) == 6 | ||
| assert result["id"] == 1 | ||
| assert result["color"] == "pink" | ||
| assert result["livability"] == 3 | ||
| assert result["moons"] == 99 | ||
| assert result["name"] == "Pretend Planet X" | ||
|
|
||
| def test_to_dict_missing_id(): | ||
| # Arrange | ||
| test_data = Planet( | ||
| color = "pink", | ||
| is_dwarf = True, | ||
| livability = 3, | ||
| moons = 99, | ||
| name = "Pretend Planet X") | ||
| # Act | ||
| result = test_data.to_dict() | ||
|
|
||
| # Assert | ||
| assert len(result) == 6 | ||
| assert result["id"] is None | ||
| assert result["color"] == "pink" | ||
| assert result["livability"] == 3 | ||
| assert result["moons"] == 99 | ||
| assert result["name"] == "Pretend Planet X" | ||
|
|
||
|
|
||
|
|
||
| def test_to_dict_missing_name(): | ||
| # Arrange | ||
| test_data = Planet(id = 1, | ||
| color = "pink", | ||
| is_dwarf = True, | ||
| livability = 3, | ||
| moons = 99) | ||
|
|
||
| # Act | ||
| result = test_data.to_dict() | ||
|
|
||
| # Assert | ||
| assert len(result) == 6 | ||
| assert result["id"] == 1 | ||
| assert result["color"] == "pink" | ||
| assert result["livability"] == 3 | ||
| assert result["moons"] == 99 | ||
| assert result["name"] is None | ||
|
|
||
| def test_to_dict_missing_moons(): | ||
| # Arrange | ||
| test_data = Planet(id = 1, | ||
| color = "pink", | ||
| is_dwarf = True, | ||
| livability = 3, | ||
| name = "Pretend Planet X") | ||
|
|
||
| # Act | ||
| result = test_data.to_dict() | ||
|
|
||
| # Assert | ||
| assert len(result) == 6 | ||
| assert result["id"] == 1 | ||
| assert result["color"] == "pink" | ||
| assert result["livability"] == 3 | ||
| assert result["moons"] is None | ||
| assert result["name"] == "Pretend Planet X" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| from app.models.planet import Planet | ||
|
|
||
| def test_get_all_planets_in_database(client, saved_test_planets): | ||
| # Act | ||
| response = client.get('/planets') | ||
| response_body = Planet.query.all() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 200 | ||
|
|
||
| def test_missing_planet_id(client): | ||
| # Act | ||
| response = client.get('planets/3') | ||
| response_body = response.get_json() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 404 | ||
|
|
||
| def test_get_planets_for_empty_database(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_by_id(client, saved_test_planets): | ||
| # Act | ||
| response = client.get('/planets/1') | ||
| response_body = response.get_json() | ||
|
|
||
| # Assert | ||
| assert response.status_code == 200 | ||
| assert response_body == {'id': 1, 'color': "pink", 'is_dwarf': True, 'livability': 3, 'moons': 99, 'name': "Pretend Planet X"} | ||
|
|
||
| def test_create_one_new_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. Also testing a create action with an invalid request body would be appropriate. |
||
| # Act | ||
| EXPECTED_PLANET = { | ||
| "color": "green", | ||
| "is_dwarf": False, | ||
| "livability": 6, | ||
| "moons": 43, | ||
| "name": "New Pretend Planet Y", | ||
| } | ||
|
|
||
| response = client.post("/planets", json=EXPECTED_PLANET) | ||
| response_body = response.get_data(as_text=True) | ||
| actual_planet = Planet.query.get(1) | ||
|
|
||
| # Assert | ||
| assert response.status_code == 201 | ||
| assert response_body == f"Planet {EXPECTED_PLANET['name']} successfully created" | ||
| assert actual_planet.color == EXPECTED_PLANET["color"] | ||
| assert actual_planet.is_dwarf == EXPECTED_PLANET["is_dwarf"] | ||
| assert actual_planet.livability == EXPECTED_PLANET["livability"] | ||
| assert actual_planet.moons == EXPECTED_PLANET["moons"] | ||
| assert actual_planet.name == EXPECTED_PLANET["name"] | ||
| 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 helper methods