Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
e50ac41
Monica's update: set Planet class, Blueprint, and __init__ file
Monica-Yfb Dec 14, 2022
8080e4b
Updated Planets class attribute
Monica-Yfb Dec 14, 2022
fe2a6b1
"routes updates"
Jewelhae Dec 14, 2022
139fd95
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Jewelhae Dec 14, 2022
8ce8669
created planets route and tested in postman
Jewelhae Dec 14, 2022
d34fd18
wave 2 ready for review
Jewelhae Dec 15, 2022
9d66b12
Wave2 Completed, add routes by_name and by_id
Monica-Yfb Dec 15, 2022
2ddae9d
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Monica-Yfb Dec 15, 2022
b9116c2
Remove additional imports
Monica-Yfb Dec 15, 2022
cfc1857
add register
Jewelhae Dec 15, 2022
27225ac
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Jewelhae Dec 15, 2022
5bfeaa1
Wave 3 Completed
Monica-Yfb Dec 20, 2022
c35a3bb
Wave 3 Completed
Monica-Yfb Dec 20, 2022
1c60c4b
wave 3
Jewelhae Dec 20, 2022
1de39f4
Wave3 update
Monica-Yfb Dec 20, 2022
abc1a16
Completed wave 4
Monica-Yfb Dec 21, 2022
e45b359
wave 4 completed
Jewelhae Dec 21, 2022
d20aa21
wave 4 completed and added distance attribute
Jewelhae Dec 21, 2022
6f01bc3
add gravity attr
Jewelhae Dec 21, 2022
572fd2f
Merge branch 'dev_jd'
Jewelhae Dec 21, 2022
b8d489a
"merge dev_jd to main."
Jewelhae Dec 21, 2022
1960d7f
updated
Monica-Yfb Dec 21, 2022
706bd4b
migrations version file upload
Jewelhae Dec 21, 2022
5f39db4
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Jewelhae Dec 21, 2022
ec36909
Completed wave 4, error free
Monica-Yfb Dec 21, 2022
77c30dd
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Monica-Yfb Dec 21, 2022
bad1123
change class planets to planet in planet.py
Jewelhae Dec 21, 2022
b975325
Delete app/Models directory
Jewelhae Dec 22, 2022
edac9e7
updated code
Monica-Yfb Dec 22, 2022
81de35a
Error Handling added
Monica-Yfb Dec 22, 2022
30f21f3
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Monica-Yfb Dec 22, 2022
ff6a521
Error handling fixed
Monica-Yfb Dec 22, 2022
22793cb
Added compare_type to Migrate() to detect type changes
Monica-Yfb Dec 22, 2022
2fd38ae
migrations file updated
Monica-Yfb Dec 22, 2022
4f43976
Wave5 in progress
Monica-Yfb Dec 22, 2022
23e7900
Wave5 Completed
Monica-Yfb Dec 23, 2022
a877c4d
Add feature: Sort records based on clients' requested attribute
Monica-Yfb Dec 23, 2022
3347143
Error Handling added
Monica-Yfb Dec 23, 2022
2fb4764
Added helper function to clean up the code
Monica-Yfb Dec 23, 2022
1a82e92
testing config added
Monica-Yfb Jan 2, 2023
caebe33
Tests added
Monica-Yfb Jan 2, 2023
946e824
Testing files modified
Monica-Yfb Jan 3, 2023
5252ab2
Tests
Monica-Yfb Jan 4, 2023
0dc2698
refactor to_dict function and add models tests.
Jewelhae Jan 4, 2023
4b052e2
add edge cases for create routes, and refactor from_dict function
Jewelhae Jan 4, 2023
60bd048
bug free tests
Jewelhae Jan 4, 2023
1ae8399
Refactored validate_model
Monica-Yfb Jan 4, 2023
53d515c
More tests added
Monica-Yfb Jan 4, 2023
0adc24f
Wave 8 done.
Monica-Yfb Jan 5, 2023
7808026
modified to_dict
Monica-Yfb Jan 5, 2023
86a3bdf
added 10 tests to test moon route
Jewelhae Jan 6, 2023
9d83191
old test file
Monica-Yfb Jan 6, 2023
c1dba0c
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Monica-Yfb Jan 6, 2023
44a3426
Bugs fixed, all tests are passing now
Monica-Yfb Jan 6, 2023
7d8b817
"working on test_routes"
Jewelhae Jan 6, 2023
9cc692b
Nested routes tests added. All tests passed
Monica-Yfb Jan 6, 2023
4a1a489
Procfile file for heroku added
Monica-Yfb Jan 6, 2023
9723403
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Jewelhae Jan 6, 2023
c17b2c2
Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api
Jewelhae Jan 6, 2023
d96a2a3
added one test and reviewed test routes file
Jewelhae Jan 6, 2023
0e5a05a
added Procfile
Jewelhae Jan 6, 2023
9b6be0f
pull code from git
Monica-Yfb Jan 6, 2023
dc226f2
Tests modified
Monica-Yfb Jan 6, 2023
010b2fc
more tests added
Monica-Yfb Jan 6, 2023
b2c5282
More tests added for sort feature
Monica-Yfb Jan 8, 2023
30cc0a6
more tests added
Monica-Yfb Jan 8, 2023
43b4938
add one test and reviewed all tests
Jewelhae Jan 8, 2023
5ed19c2
Add edge case handling to planet delete route
Monica-Yfb Jan 8, 2023
6e2dbc9
Additional tests added
Monica-Yfb Jan 8, 2023
2082ef4
modified test. All tests passed
Monica-Yfb Jan 8, 2023
8869e21
Project is completed
Monica-Yfb Jan 9, 2023
379aa48
Code fixed based on feedback
Monica-Yfb Jan 16, 2023
2a7ea7d
Fixed test file based on instructor's feedback
Monica-Yfb Jan 16, 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()'
28 changes: 28 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from dotenv import load_dotenv
import os

#postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development
db = SQLAlchemy()
migrate = Migrate(compare_type=True)
load_dotenv()

def create_app(test_config=None):
app = Flask(__name__)

app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically
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")

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
from .routes.moon_routes import moon_bp

app.register_blueprint(planets_bp)
app.register_blueprint(moon_bp)

return app
Empty file added app/models/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions app/models/moon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from app import db

class Moon(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String)
planet_id = db.Column(db.Integer, db.ForeignKey("planet.id"))
planet = db.relationship("Planet", back_populates = "moons")

def to_dict(self):
return{
"id" : self.id,
"name" : self.name,
"planet_id" : self.planet.id,
"planet" : self.planet.name
}

37 changes: 37 additions & 0 deletions app/models/planet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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)
gravity = db.Column(db.Float)
distance_from_earth = db.Column(db.Float)
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["description"] = self.description
planet_as_dict["gravity"] = self.gravity
planet_as_dict["distance_from_earth"] = self.distance_from_earth

moon_names = []
for moon in self.moons:
moon_names.append(moon.name)
planet_as_dict["moons"] = [moon.name for moon in self.moons]

return planet_as_dict

@classmethod
def from_dict(cls, planet_data):
new_planet = Planet(
name=planet_data["name"],
description=planet_data["description"],
gravity = planet_data['gravity'],
distance_from_earth = planet_data['distance_from_earth']
)

return new_planet
2 changes: 0 additions & 2 deletions app/routes.py

This file was deleted.

64 changes: 64 additions & 0 deletions app/routes/moon_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from flask import Blueprint, jsonify, abort, make_response, request
from app import db
from app.models.moon import Moon
from app.models.planet import Planet
from app.routes.planet_routes import validate_model

moon_bp = Blueprint("moons_bp", __name__, url_prefix="/moons")


@moon_bp.route("", methods=["POST"])
def create_new_moon():
request_body = request.get_json()
new_moon = Moon(name=request_body["name"], )

db.session.add(new_moon)
db.session.commit()

return make_response(jsonify(f"Moon {new_moon.name} successfully created."), 201)


@moon_bp.route("/<moon_id>", methods=["GET"])
def read_moon_by_id(moon_id):
moon_response = validate_model(Moon, moon_id)

return make_response(jsonify(moon_response.to_dict()), 200)


@moon_bp.route("", methods=["GET"])
def read_all_moons():
all_moon = Moon.query.all()

moons_response = []
for moon in all_moon:
moons_response.append({
"id": moon.id,
"name": moon.name,
}
)
return make_response(jsonify(moons_response), 200)


@moon_bp.route("/<planet_id>/moon", methods=["POST"])
def create_moon_to_planet_by_planet_id(planet_id):
new_planet = validate_model(Planet, planet_id)

request_body = request.get_json()
new_moon = Moon(
name=request_body["name"],
planet_id=new_planet.id,
planet=new_planet
)

db.session.add(new_moon)
db.session.commit()

return make_response(jsonify(new_moon.to_dict()), 201)


@moon_bp.route("/<planet_id>/moons", methods=["GET"])
def get_moons_by_planet_id(planet_id):
planet = validate_model(Planet, planet_id)

response = planet.to_dict()
return make_response(jsonify(response), 200)
143 changes: 143 additions & 0 deletions app/routes/planet_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from flask import Blueprint, jsonify, abort, make_response, request
from app import db
from app.models.planet import Planet
from app.models.moon import Moon

planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets")

# ---------------------------------------------- Route Functions ----------------------------------------------
@planets_bp.route("", methods = ["POST"])
def create_planet():
request_body = request.get_json()
validate_request_body(request_body)
#use from_dict function to simplfied code
new_planet = Planet.from_dict(request_body)

db.session.add(new_planet)
db.session.commit()

return make_response(jsonify(f"Planet: {new_planet.name} created successfully."), 201)

@planets_bp.route("", methods = ["GET"])
def read_planets():

planet_query = Planet.query # Get a query object for later use

# Query planets use name argument
name_query = request.args.get("name")
distance_query = request.args.get("distance_from_earth")
gravity_query = request.args.get("gravity")
# Sort argument passed by client
is_sort = request.args.get("sort")

if name_query:
planet_query = planet_query.filter_by(name = name_query)

if distance_query:
planet_query = planet_query.filter_by(distance_from_earth = distance_query)

if gravity_query:
planet_query = planet_query.filter_by(gravity = gravity_query)

if is_sort:
attribute = None
sort_method = is_sort

split_sort = is_sort.split(":")

if len(split_sort) == 2: # Case: ?sort=attribute:asc
attribute = split_sort[0]
sort_method = split_sort[1]
if len(split_sort) > 2:
abort(make_response("Too many parameters", 400))

# Sort records by client's request
if attribute == "distance_from_earth":
planet_query = sort_helper(planet_query, Planet.distance_from_earth, sort_method)
elif attribute == "gravity":
planet_query = sort_helper(planet_query, Planet.gravity, sort_method)
else: # If user don't specify any attribute, we would sort by name
planet_query = sort_helper(planet_query, Planet.name, sort_method)
else:
# Sort by id asc if no sort param porvided
planet_query = sort_helper(planet_query, Planet.id, "asc")

planets = planet_query.all()

planet_response = []
for planet in planets:
planet_response.append(planet.to_dict()) #use to_dict function to make code more readable

return make_response(jsonify(planet_response), 200)

@planets_bp.route("/<planet_id>", methods = ["GET"])
def read_one_planet_by_id(planet_id):
planet = validate_model(Planet, planet_id)
#use to_dict function to make code more readable
return (planet.to_dict(), 200)

@planets_bp.route("/<planet_id>", methods = ["PUT"])
def update_planet_by_id(planet_id):
planet = validate_model(Planet, planet_id)

request_body = request.get_json()
validate_request_body(request_body)

planet.name = request_body["name"]
planet.description = request_body["description"]
planet.gravity = request_body["gravity"]
planet.distance_from_earth = request_body["distance_from_earth"]

db.session.commit()

return make_response(jsonify(f"Planet: {planet_id} has been updated successfully."), 200)

@planets_bp.route("/<planet_id>", methods = ["DELETE"])
def delete_planet_by_id(planet_id):
planet = validate_model(Planet, planet_id)

# Delete moon when we delete the planet they associate to to avoid having dangling nodes
moons_query = Moon.query.all()
if moons_query:
for moon in moons_query:
if moon.planet_id == planet.id:
db.session.delete(moon)
db.session.commit()

db.session.delete(planet)
db.session.commit()

return make_response(jsonify(f"Planet: {planet_id} has been deleted successfully."), 200)


#---------------------------------------------- Helper Functions----------------------------------------------
def validate_model(cls, model_id):
try:
model_id = int(model_id)
except:
abort(make_response({"message":f"{cls.__name__} {model_id} is 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

def validate_request_body(request_body):

if "name" not in request_body or "description" not in request_body or "gravity" \
not in request_body or "distance_from_earth" not in request_body:
abort(make_response("Invalid Request", 400))

def sort_helper(planet_query, atr = None, sort_method = "asc"):
if sort_method == "asc" and atr:
planet_query = planet_query.order_by(atr.asc())
elif sort_method == "desc" and atr:
planet_query = planet_query.order_by(atr.desc())
elif sort_method == "desc":
planet_query = planet_query.order_by(Planet.name.desc())
else:
#Sort by name in ascending order by default
planet_query = planet_query.order_by(Planet.name.asc())

return planet_query
1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
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
Loading