-
Notifications
You must be signed in to change notification settings - Fork 11
AC2-Ocelots:Catherine_Bandarchuk(co-worker: Yuan Du -Anna) - Solar_system_API #12
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
bf10f41
32b71e6
f1cad19
4f57a3f
f9c7def
e397304
427f3b4
e910bf0
fd7ec77
ce89a0f
e709c0d
98f167e
c6307d2
9532ffc
d854dcb
7d773e8
6553fd7
83ce4d2
89653de
a8f3aad
9622828
75936ee
2234168
2f45c07
d4bd163
e2e3281
3c6810e
c622c99
2160c36
966765e
1b8628d
d4008fe
ade3c8f
23e6c4f
b5f5152
7d30d75
f4c7555
1a30102
1810448
295d14d
26f571b
a4b72a0
ff76b05
fca6056
1b474f3
56306ef
3653d9f
8a8db01
680ab2a
067596f
c4430fe
1ba96d1
0eccad5
0d34810
48eb59b
6ceeabd
9cd64d2
2d8d9ce
d8c9a46
df09d72
9e26dd0
e7c7a36
9c2b426
06b9340
c5a68f7
b58029f
e95fc92
78fe3fd
dabc795
c71652c
3e63386
900d2ca
a7bf91d
a589a31
7a60057
9c5ec28
9bf3d44
9b29f41
7b82c79
80a8862
220c06b
799b016
826486a
30c34a7
ebd6896
a9e828b
8faedbe
6436d7b
bc1be3b
8b25f44
bc591e5
5808f9d
916cd9b
a484a82
c33f7ca
d7d7a26
6c4375e
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): | ||
| def create_app(test_config = None): | ||
| app = Flask(__name__) | ||
| app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||
|
|
||
| if test_config: | ||
| app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") | ||
| app.config["Testing"] = True | ||
| else: | ||
| app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_DATABASE_URI") | ||
|
|
||
| from app.models.planet import Planet | ||
|
|
||
| return app | ||
| db.init_app(app) | ||
| migrate.init_app(app, db) | ||
|
|
||
| from app.models.planet import Planet | ||
| from app.models.moon import Moon | ||
|
|
||
| from .routes.planet_routes import planets_bp | ||
| app.register_blueprint(planets_bp) | ||
|
|
||
| from .routes.moon_routes import moons_bp | ||
| app.register_blueprint(moons_bp) | ||
| return app | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| from app import db | ||
|
|
||
| class Moon(db.Model): | ||
| id = db.Column(db.Integer, primary_key = True, autoincrement = True) | ||
| name = db.Column(db.String, nullable = False) | ||
| size = db.Column(db.Float, nullable = False) | ||
| description = db.Column(db.String, nullable = False) | ||
| planet_id = db.Column(db.Integer, db.ForeignKey("planet.id")) | ||
| planet = db.relationship("Planet", back_populates="moons") | ||
|
|
||
| def to_dict(self): | ||
| moon_as_dict = {} | ||
| moon_as_dict["id"] = self.id | ||
| moon_as_dict["name"] = self.name | ||
| moon_as_dict["size"] = self.size | ||
| moon_as_dict["description"] = self.description | ||
| return moon_as_dict | ||
|
|
||
| @classmethod | ||
| def from_dict(cls,moon_data): | ||
| new_moon = Moon(name=moon_data["name"], | ||
| size=float(moon_data["size"]), | ||
| description=moon_data["description"]) | ||
| return new_moon |
| 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, nullable = False) | ||
| length_of_year = db.Column(db.Integer, nullable = False) | ||
| description = db.Column(db.String, nullable = False) | ||
| moons = db.relationship("Moon", back_populates="planet") | ||
|
|
||
| def to_dict(self): | ||
| planet_as_dict = {} | ||
| planet_as_dict["id"] = self.id | ||
| planet_as_dict["name"] = self.name | ||
| planet_as_dict["length_of_year"] = self.length_of_year | ||
| planet_as_dict["description"] = self.description | ||
|
|
||
| moon_names = [] | ||
| for moon in self.moons: | ||
| moon_names.append(moon.name) | ||
| planet_as_dict["moons"] = moon_names | ||
|
Comment on lines
+17
to
+20
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 know this is the way I grabbed cat names during the class livecode, but this is a great place for a list comprehension! planet_as_dict["moons"] = [moon.name for moon in self.moons]
Author
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'm still working to be comfortable to use comprehension the same as lambda function. But I'll try to use it more often. |
||
| return planet_as_dict | ||
|
|
||
| @classmethod | ||
| def from_dict(cls,planet_data): | ||
| new_planet = Planet(name=planet_data["name"], | ||
| length_of_year=planet_data["length_of_year"], | ||
| description=planet_data["description"]) | ||
|
Comment on lines
+25
to
+27
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 is a completely valid way to break the line up, but I wanted to share another format that's common across languages. This involves dropping all the arguments to their own lines. The benefit of this is making the arguments very clear from other statements and the use of indentation to show we're inside something spanning multiple lines: # Original code
new_planet = Planet(name=planet_data["name"],
length_of_year=planet_data["length_of_year"],
description=planet_data["description"])
# Alternate formatting
new_planet = Planet(
name=planet_data["name"],
length_of_year=planet_data["length_of_year"],
description=planet_data["description"]
) |
||
| return new_planet | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| from app import db | ||
| from app.models.moon import Moon | ||
| from app.models.planet import Planet | ||
| from flask import Blueprint, jsonify, abort, make_response, request | ||
| from .validate_routes import validate_model, validate_moon_user_input, validate_planet_user_input | ||
|
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 looks like |
||
|
|
||
| moons_bp = Blueprint("moons_bp", __name__, url_prefix = "/moons") | ||
|
|
||
| # Get all moons info | ||
| # /moons | ||
| # Return JSON list | ||
| @moons_bp.route("", methods = ["GET"]) | ||
| def get_all_moons_query(): | ||
| moon_query = Moon.query | ||
| # Filtering by moon name (return all records which name contains planet_name_query) | ||
| moon_name_query = request.args.get("name") | ||
|
|
||
| if moon_name_query: | ||
| moon_query = moon_query.filter(Moon.name.ilike(f"%{moon_name_query}%")) | ||
|
Comment on lines
+15
to
+19
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. Since these lines of code are tightly related, I would consider removing the blank line on line 17. This feedback applies across this function where there is a separation between getting a request argument and immediately using it.
Author
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. Ok. Thank you. I used the same structure during all project, sorry. 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 all good, teams and languages may have guidelines, but there is a lot in code style that is subjective. This feedback is not a strict requirement, it is based on the principles of keeping tightly related code closer together and what reads easiest for me. A lot of these small code adjustments often come when you have time to review and refactor your code once it's working, which I know there wasn't a lot of space for with this project and retro video store. |
||
|
|
||
| # Sorting by moon name | ||
| sort_by_name_query = request.args.get("sort_by_name") | ||
|
|
||
| if sort_by_name_query == "desc": | ||
| moon_query = moon_query.order_by(Moon.name.desc()).all() | ||
| elif sort_by_name_query == "asc": | ||
| moon_query = moon_query.order_by(Moon.name).all() | ||
|
|
||
| # Sorting by moon size | ||
| moon_sort_size_query = request.args.get("sort_by_size") | ||
|
|
||
| if moon_sort_size_query == "desc": | ||
| moon_query = moon_query.order_by(Moon.size.desc()).all() | ||
| elif sort_by_name_query == "asc": | ||
| moon_query = moon_query.order_by(Moon.size).all() | ||
|
|
||
| # Build response | ||
| moon_response = [] | ||
| for moon in moon_query: | ||
| moon_response.append(moon.to_dict()) | ||
|
Comment on lines
+38
to
+40
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 would be another great place for a list comprehension - what could that look like?
Author
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. moon_response+= [moon.to_dict() for moon in moon_query] 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, you're about there! We would use |
||
|
|
||
| return jsonify(moon_response), 200 | ||
|
|
||
| # Read one moon | ||
| # Return one moon info in JSON format | ||
| @moons_bp.route("/<moon_id>",methods=["GET"] ) | ||
| def get_one_moon(moon_id): | ||
| moon = validate_model(Moon, moon_id) | ||
| return moon.to_dict() | ||
|
|
||
| # Create one moon | ||
| @moons_bp.route("", methods = ["POST"]) | ||
| def create_moon(): | ||
| moon_value = validate_moon_user_input(request.get_json()) | ||
| new_moon = Moon.from_dict(moon_value) | ||
|
|
||
| db.session.add(new_moon) | ||
| db.session.commit() | ||
| return make_response(jsonify(f"Moon {new_moon.name} successfully created"), 201) | ||
|
|
||
| # Delete moon by id | ||
| @moons_bp.route("/<moon_id>",methods=["DELETE"]) | ||
| def delete_moon(moon_id): | ||
| moon = validate_model(Moon, moon_id) | ||
|
|
||
| db.session.delete(moon) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Moon {moon.id} successfully deleted"), 200) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| from app import db | ||
| from app.models.planet import Planet | ||
| from app.models.moon import Moon | ||
| from .validate_routes import validate_model, validate_moon_user_input, validate_planet_user_input | ||
| from flask import Blueprint, jsonify, abort, make_response, request | ||
|
|
||
| planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") | ||
|
|
||
| # Creating new planet | ||
| @planets_bp.route("", methods = ["POST"]) | ||
| def create_planet(): | ||
| planet_value = validate_planet_user_input(request.get_json()) | ||
| new_planet = Planet.from_dict(planet_value) | ||
|
|
||
| db.session.add(new_planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Planet {new_planet.name} successfully created"), 201) | ||
|
|
||
| # Get all planets info | ||
| # Return JSON list | ||
| @planets_bp.route("", methods = ["GET"]) | ||
| def get_all_planets_query(): | ||
| planet_query = Planet.query | ||
|
|
||
| # Filtering by name (return all records which name contains planet_name_query) | ||
| planet_name_query = request.args.get("name") | ||
|
|
||
| if planet_name_query: | ||
| planet_query = planet_query.filter(Planet.name.ilike(f"%{planet_name_query}%")) | ||
|
|
||
| # Sort | ||
| sort_query = request.args.get("sort") | ||
|
|
||
| if sort_query == "name": | ||
| planet_query = planet_query.order_by(Planet.name).all() | ||
|
|
||
| if sort_query == "length_of_year": | ||
| planet_query = planet_query.order_by(Planet.length_of_year).all() | ||
|
|
||
| planet_response = [] | ||
| for planet in planet_query: | ||
| planet_response.append(planet.to_dict()) | ||
|
|
||
| return jsonify(planet_response), 200 | ||
|
|
||
| # Read one planet | ||
| # Return one planet info in JSON format | ||
| @planets_bp.route("/<planet_id>",methods=["GET"] ) | ||
| def get_one_planet(planet_id): | ||
| planet = validate_model(Planet, planet_id) | ||
| return planet.to_dict() | ||
|
|
||
| # Update one planet | ||
| @planets_bp.route("/<planet_id>",methods=["PUT"] ) | ||
| def update_planet(planet_id): | ||
| planet = validate_model(Planet, planet_id) | ||
| request_body = validate_planet_user_input(request.get_json()) | ||
|
|
||
| planet.name = request_body["name"] | ||
| planet.length_of_year = request_body["length_of_year"] | ||
| planet.description = request_body["description"] | ||
|
|
||
| db.session.commit() | ||
| message = f"Planet {planet_id} successfully updated" | ||
| return make_response(jsonify(message), 200) | ||
|
|
||
| # Delete one planet and all the moons dependent of the planet | ||
| @planets_bp.route("/<planet_id>",methods=["DELETE"]) | ||
| def delete_planet(planet_id): | ||
| planet = validate_model(Planet, planet_id) | ||
|
|
||
| if len(planet.moons)>0: | ||
| i = 0 | ||
| while i < len(planet.moons): | ||
| moon_id = planet.moons[i].id | ||
| moon = validate_model(Moon, moon_id) | ||
| db.session.delete(moon) | ||
| db.session.commit() | ||
| i += 1 | ||
|
|
||
| db.session.delete(planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(jsonify(f"Planet {planet.id} successfully deleted"), 200) | ||
|
|
||
| # Add moon info to the planet using planet id | ||
| @planets_bp.route("/<planet_id>/moons", methods=["POST"]) | ||
| def add_new_moon_to_planet(planet_id): | ||
| planet = validate_model(Planet, planet_id) | ||
| moon_value = validate_moon_user_input(request.get_json()) | ||
|
|
||
| moon = Moon.from_dict(moon_value) | ||
| moon.planet_id = planet.id | ||
|
|
||
| db.session.add(moon) | ||
| db.session.commit() | ||
|
|
||
| message = f"Moon {moon.name} added to the planet {planet.name}." | ||
| return make_response(jsonify(message), 201) | ||
|
|
||
| # Get all moons info for chosen planet (by planet id) | ||
| @planets_bp.route("/<planet_id>/moons", methods=["GET"]) | ||
| def get_all_moons_for_planet(planet_id): | ||
| planet = validate_model(Planet, planet_id) | ||
|
|
||
| moons_response = [] | ||
| for moon in planet.moons: | ||
| moons_response.append(moon.to_dict()) | ||
|
|
||
| return jsonify(moons_response) | ||
|
|
||
| # Get moon by id | ||
| # Return one moon info in JSON format | ||
| @planets_bp.route("/<planet_id>/moons/<moon_id>",methods=["GET"] ) | ||
| def get_one_moon_of_planet(planet_id, moon_id): | ||
| planet = validate_model(Planet, planet_id) | ||
| moon = validate_model(Moon, moon_id) | ||
| return moon.to_dict() | ||
|
|
||
| # Update moon info (by moon_id) of the planet using planet_id | ||
| @planets_bp.route("/<planet_id>/moons/<moon_id>", methods=["POST"]) | ||
| def update_moon_of_planet(planet_id, moon_id): | ||
|
|
||
| planet = validate_model(Planet, planet_id) | ||
| moon = validate_model(Moon, moon_id) | ||
|
|
||
| request_body = validate_moon_user_input(request.get_json()) | ||
|
|
||
| moon.name = request_body["name"] | ||
| moon.size = request_body["size"] | ||
| moon.description = request_body["description"] | ||
|
|
||
| db.session.commit() | ||
|
|
||
| message = f"Moon {moon.id} of the planet {planet.name} successfully updated." | ||
| return make_response(jsonify(message), 201) | ||
|
|
||
| # Delete moon of the specific planet (using planet_id and moon_id) | ||
| @planets_bp.route("/<planet_id>/moons/<moon_id>", methods=["DELETE"]) | ||
| def delete_moon_of_planet(planet_id, moon_id): | ||
|
|
||
| planet = validate_model(Planet, planet_id) | ||
| moon = validate_model(Moon, moon_id) | ||
|
|
||
| db.session.delete(moon) | ||
| db.session.commit() | ||
|
|
||
| message = f"Moon {moon.id} of the planet {planet.name} successfully deleted." | ||
| return make_response(jsonify(message), 201) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| from app import db | ||
| from app.models.planet import Planet | ||
| from app.models.moon import Moon | ||
| from flask import Blueprint, jsonify, abort, make_response, request | ||
|
|
||
| # Validating the id of the instance: id needs to be int and exists the instance with the id. | ||
| # Returning the valid Class instance if valid id | ||
| # Work for Planet and Moon classes | ||
| def validate_model(cls, model_id): | ||
| try: | ||
| model_id = int(model_id) | ||
| except: | ||
| abort(make_response(jsonify(f"{cls.__name__} {model_id} invalid"), 400)) | ||
|
|
||
| class_obj = cls.query.get(model_id) | ||
|
|
||
| if not class_obj: | ||
| abort(make_response(jsonify(f"{cls.__name__} {model_id} not found"), 404)) | ||
|
|
||
| return class_obj | ||
|
|
||
| # Validating the user input to create or update the table planet | ||
| # Returning the valid JSON if valid input | ||
| def validate_planet_user_input(planet_value): | ||
|
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 function is only used by |
||
|
|
||
| if "name" not in planet_value \ | ||
| or not isinstance(planet_value["name"], str) \ | ||
| or planet_value["name"] == "" \ | ||
| or "length_of_year" not in planet_value \ | ||
| or not isinstance(planet_value["length_of_year"], int) \ | ||
| or planet_value["length_of_year"] <=0 \ | ||
|
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. Reminder that we want to leave a space on either side of comparison operators. |
||
| or "description" not in planet_value \ | ||
| or not isinstance(planet_value["description"], str) \ | ||
| or planet_value["description"] == "": | ||
|
|
||
| return abort(make_response(jsonify("Invalid request"), 400)) | ||
|
|
||
| return planet_value | ||
|
|
||
| # Validating the user input to create or update the table moon | ||
| # Returning the valid JSON if valid input | ||
| def validate_moon_user_input(moon_value): | ||
|
|
||
| if "name" not in moon_value \ | ||
| or not isinstance(moon_value["name"], str) \ | ||
| or moon_value["name"] == "" \ | ||
| or "size" not in moon_value \ | ||
| or not (isinstance(moon_value["size"], float) or isinstance(moon_value["size"], int)) \ | ||
| or moon_value["size"] <=0 \ | ||
| or "description" not in moon_value \ | ||
| or not isinstance(moon_value["description"], str) \ | ||
| or moon_value["description"] == "": | ||
|
|
||
| return abort(make_response(jsonify("Invalid request"), 400)) | ||
|
|
||
| return moon_value | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Single-database configuration for Flask. |
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.
It looks like Planet is imported twice in this file, we can remove one of these lines.
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.
Thank you. :) I didn't notice. Fixed it.