Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
bf10f41
Co-authored-by: CatherineBandarchuk <[email protected]
anjing0921 Dec 14, 2022
32b71e6
wave1-worked
CatherineBandarchuk Dec 14, 2022
f1cad19
import the planets.py
anjing0921 Dec 14, 2022
4f57a3f
Merge branch 'main' of https://github.com/CatherineBandarchuk/solar-s…
anjing0921 Dec 14, 2022
f9c7def
Co-authored-by: CatherineBandarchuk <[email protected]
anjing0921 Dec 15, 2022
e397304
add endpoint
anjing0921 Dec 15, 2022
427f3b4
My changes
CatherineBandarchuk Dec 15, 2022
e910bf0
Merge branch 'main' of github.com:CatherineBandarchuk/solar-system-api
CatherineBandarchuk Dec 15, 2022
fd7ec77
wave_3 complite
CatherineBandarchuk Dec 21, 2022
ce89a0f
wave 03 done
anjing0921 Dec 21, 2022
e709c0d
wave 3 done
anjing0921 Dec 21, 2022
98f167e
add routes: read_one_planet, update, delete
CatherineBandarchuk Dec 21, 2022
c6307d2
wave4 complite
CatherineBandarchuk Dec 21, 2022
9532ffc
Added comments and helper functions
CatherineBandarchuk Dec 21, 2022
d854dcb
Added validation for user's input datatypes
CatherineBandarchuk Dec 21, 2022
7d773e8
change class planet
anjing0921 Dec 21, 2022
6553fd7
Merge branch 'main' of https://github.com/CatherineBandarchuk/solar-s…
anjing0921 Dec 21, 2022
83ce4d2
delete wrong data
anjing0921 Dec 21, 2022
89653de
update with kate
anjing0921 Dec 21, 2022
a8f3aad
change with kate
anjing0921 Dec 21, 2022
9622828
planet table created
anjing0921 Dec 21, 2022
75936ee
add new class planet attribute livable, type is boolean test work
anjing0921 Dec 21, 2022
2234168
add color attribute
anjing0921 Dec 22, 2022
2f45c07
Added attribute livable. change the path to DB.
CatherineBandarchuk Dec 22, 2022
d4bd163
delete color
anjing0921 Dec 22, 2022
e2e3281
not search by name
anjing0921 Dec 22, 2022
3c6810e
separate work
anjing0921 Dec 22, 2022
c622c99
Merge branch 'main' of github.com:CatherineBandarchuk/solar-system-api
CatherineBandarchuk Dec 22, 2022
2160c36
merge conflict solved
CatherineBandarchuk Dec 22, 2022
966765e
wave 5 done
anjing0921 Dec 22, 2022
1b8628d
change method name to get planets_query
anjing0921 Dec 22, 2022
d4008fe
Merge branch 'main' of github.com:CatherineBandarchuk/solar-system-api
CatherineBandarchuk Dec 22, 2022
ade3c8f
two param query worked
CatherineBandarchuk Dec 22, 2022
23e6c4f
Merge branch 'main' of github.com:CatherineBandarchuk/solar-system-api
CatherineBandarchuk Dec 22, 2022
b5f5152
add filtering by livable
CatherineBandarchuk Dec 22, 2022
7d30d75
add sorting by additional parameters
CatherineBandarchuk Dec 22, 2022
f4c7555
modify color
anjing0921 Dec 23, 2022
1a30102
Merge branch 'main' of https://github.com/CatherineBandarchuk/solar-s…
anjing0921 Dec 23, 2022
1810448
wave 5 done
anjing0921 Dec 23, 2022
295d14d
change some column
anjing0921 Jan 3, 2023
26f571b
delete wrong version
anjing0921 Jan 3, 2023
a4b72a0
delete some column
anjing0921 Jan 3, 2023
ff76b05
delete class Planet some attributes
anjing0921 Jan 3, 2023
fca6056
add tests/test_routes.py
anjing0921 Jan 3, 2023
1b474f3
tests/conftest.py
anjing0921 Jan 3, 2023
56306ef
tests/__init__.py
anjing0921 Jan 3, 2023
3653d9f
Update the planet model, create .env file
CatherineBandarchuk Jan 3, 2023
8a8db01
update planet model
CatherineBandarchuk Jan 3, 2023
680ab2a
finish config test environment
anjing0921 Jan 3, 2023
067596f
add os.environ to read variable SQLALCHEMY_DATABASE_URI
anjing0921 Jan 3, 2023
c4430fe
Add test_create_planet_valid_request.
CatherineBandarchuk Jan 3, 2023
1ba96d1
add edge cases to test create_planet
CatherineBandarchuk Jan 4, 2023
0eccad5
add test
anjing0921 Jan 4, 2023
0d34810
delete some column
anjing0921 Jan 4, 2023
48eb59b
Merge branch 'main' of https://github.com/CatherineBandarchuk/solar-s…
anjing0921 Jan 4, 2023
6ceeabd
added tests for update_planet_route
CatherineBandarchuk Jan 4, 2023
9cd64d2
Merge branch 'main' of github.com:CatherineBandarchuk/solar-system-api
CatherineBandarchuk Jan 4, 2023
2d8d9ce
delete file
anjing0921 Jan 4, 2023
d8c9a46
merge
anjing0921 Jan 4, 2023
df09d72
test get planet 1 from fixture one_planet
anjing0921 Jan 4, 2023
9e26dd0
add test get planet1 no fixture return 404
anjing0921 Jan 4, 2023
e7c7a36
add test get planets in array with fixture one_planet 200
anjing0921 Jan 4, 2023
9c2b426
add len()
anjing0921 Jan 4, 2023
06b9340
add test delete_planet_by_id_valid_id
CatherineBandarchuk Jan 4, 2023
c5a68f7
add test get planets in array with fixture three planets
anjing0921 Jan 4, 2023
b58029f
Merge branch 'main' of github.com:CatherineBandarchuk/solar-system-api
CatherineBandarchuk Jan 4, 2023
e95fc92
all test pass
anjing0921 Jan 4, 2023
78fe3fd
pass wave 6
anjing0921 Jan 4, 2023
dabc795
Wave 06: complete
CatherineBandarchuk Jan 4, 2023
c71652c
Merge branch 'main' of github.com:CatherineBandarchuk/solar-system-api
CatherineBandarchuk Jan 4, 2023
3e63386
added the edge case for delete_planet_by_id
CatherineBandarchuk Jan 4, 2023
900d2ca
Added test_delete_planet with invalid id.
CatherineBandarchuk Jan 4, 2023
a7bf91d
added two edge cases for delete_by_id and update_by_id
CatherineBandarchuk Jan 4, 2023
a589a31
Refactoring validate_id
CatherineBandarchuk Jan 4, 2023
7a60057
test_to_dict
CatherineBandarchuk Jan 4, 2023
9c5ec28
Added test_from_dict cases
CatherineBandarchuk Jan 4, 2023
9bf3d44
test validate_model
CatherineBandarchuk Jan 4, 2023
9b29f41
complete wave7
CatherineBandarchuk Jan 4, 2023
7b82c79
added moon model
CatherineBandarchuk Jan 5, 2023
80a8862
add routes for moon model
CatherineBandarchuk Jan 5, 2023
220c06b
add routes for moon
CatherineBandarchuk Jan 5, 2023
799b016
added test_moon_routes.py
CatherineBandarchuk Jan 5, 2023
826486a
added moon_database_file
CatherineBandarchuk Jan 5, 2023
30c34a7
Added moon model
CatherineBandarchuk Jan 6, 2023
ebd6896
add planet_moon_routes
CatherineBandarchuk Jan 6, 2023
a9e828b
refactor helper function
CatherineBandarchuk Jan 6, 2023
8faedbe
added moon_routes and planet_routes
CatherineBandarchuk Jan 6, 2023
6436d7b
added feature: delete all moons of the deleted planet.
CatherineBandarchuk Jan 6, 2023
bc1be3b
Complite Wave 08
CatherineBandarchuk Jan 6, 2023
8b25f44
update tests
CatherineBandarchuk Jan 8, 2023
bc591e5
added moon tests
CatherineBandarchuk Jan 8, 2023
5808f9d
Added test_moons_inside_planet_routes.py
CatherineBandarchuk Jan 8, 2023
916cd9b
added tests for create moon of planet with planet id
CatherineBandarchuk Jan 8, 2023
a484a82
Complete tests
CatherineBandarchuk Jan 8, 2023
c33f7ca
Merge pull request #1 from CatherineBandarchuk/kate_solar_system
CatherineBandarchuk Jan 8, 2023
d7d7a26
Added comments, cleanup code
CatherineBandarchuk Jan 8, 2023
6c4375e
create procfile, added gunicorn to requirements.txt
CatherineBandarchuk Jan 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn 'app:create_app()'
31 changes: 29 additions & 2 deletions app/__init__.py
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
Comment on lines +21 to +26

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.

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.

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
Empty file added app/models/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions app/models/moon.py
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
28 changes: 28 additions & 0 deletions app/models/planet.py
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

Choose a reason for hiding this comment

The 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]

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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
2 changes: 0 additions & 2 deletions app/routes.py

This file was deleted.

Empty file added app/routes/__init__.py
Empty file.
69 changes: 69 additions & 0 deletions app/routes/moon_routes.py
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like validate_planet_user_input is imported to this file but isn't used. We should check our imports & remove ones that aren't used.


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

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Thank you. I used the same structure during all project, sorry.

Copy link

@kelsey-steven-ada kelsey-steven-ada Jan 11, 2023

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moon_response+= [moon.to_dict() for moon in moon_query]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, you're about there! We would use = since the comprehension creates the whole list we want, we don't need to append to, or extend a list that already exists.


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)
150 changes: 150 additions & 0 deletions app/routes/planet_routes.py
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)
56 changes: 56 additions & 0 deletions app/routes/validate_routes.py
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):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is only used by planet_routes.py, as such, I'd consider placing this function in that file to keep it close to where it is used.


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 \

Choose a reason for hiding this comment

The 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
1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Single-database configuration for Flask.
Loading