Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d0c1880
added planets.py
lisabethu88 Dec 14, 2022
4dcfc9b
Created Planet class in planets.py
lisabethu88 Dec 14, 2022
f7c2f4a
created planets blueprint
HienXuanPham Dec 14, 2022
7e71408
created to_dict method in Planet class
lisabethu88 Dec 15, 2022
c4ff040
Added get_planet_by_id function and 400/404 error handling to planets.py
lisabethu88 Dec 15, 2022
0d44f67
updated get_planet_by_id function, created new validate_planet functi…
lisabethu88 Dec 15, 2022
823a64a
set up Planet modeland database
HienXuanPham Dec 20, 2022
842c63a
Merge pull request #1 from lisabethu88/planets_models_setup
lisabethu88 Dec 20, 2022
f6d81a1
add planets.py changes
lisabethu88 Dec 20, 2022
a1ba03e
created a route that creates model records
lisabethu88 Dec 20, 2022
e7051b2
defined route that reads model records
lisabethu88 Dec 20, 2022
99d983e
created a GET endpoint for getting one planet
lisabethu88 Dec 21, 2022
715df1c
define update route
HienXuanPham Dec 21, 2022
cc0275a
defined route that deletes a planet
lisabethu88 Dec 21, 2022
6a8add5
Added functionality to allow the user to make a request to both filte…
HienXuanPham Dec 22, 2022
55d4435
renamed planets.py to planets_routes.py and put routes back into this…
lisabethu88 Dec 22, 2022
7eb943a
commit before making a pull request
HienXuanPham Jan 3, 2023
633fed3
commit planets_routes.py
HienXuanPham Jan 3, 2023
0f090cc
created test database and .env and refactor create_app
HienXuanPham Jan 3, 2023
9c42bf6
added branch test_Setup
lisabethu88 Jan 3, 2023
f067be1
create test fixture for unit tests for get,post,update and delete
HienXuanPham Jan 3, 2023
6bf4e81
refactored to_dict and added more tests
lisabethu88 Jan 4, 2023
442ab49
refactored tests
lisabethu88 Jan 4, 2023
d328a24
from_dict classmethod
HienXuanPham Jan 4, 2023
ec72cce
merge
HienXuanPham Jan 4, 2023
4c114f0
added to_dict and from_dict test to tests_models.py and create a clas…
lisabethu88 Jan 4, 2023
75eb5a4
Merge branch 'from_dict_refactor' of https://github.com/lisabethu88/s…
HienXuanPham Jan 4, 2023
0642a6a
refactored validate_planet to validate_model
lisabethu88 Jan 5, 2023
fda0886
Merge branch 'from_dict_refactor' of https://github.com/lisabethu88/s…
HienXuanPham Jan 5, 2023
3f46339
Created Moon model
lisabethu88 Jan 5, 2023
cd5dd84
Merge branch 'moon_model' of https://github.com/lisabethu88/solar-sys…
HienXuanPham Jan 5, 2023
801b7ab
added nested routes
lisabethu88 Jan 5, 2023
a9f0bc9
fixed issue with route that creates a new moon with a planet
lisabethu88 Jan 6, 2023
4e7921c
Merge branch 'nested_routes' of https://github.com/lisabethu88/solar-…
HienXuanPham Jan 6, 2023
af46358
added read one moon
HienXuanPham Jan 6, 2023
0dba82d
added gunicorn to requirements.txt
lisabethu88 Jan 6, 2023
9a49851
added Procfile
lisabethu88 Jan 6, 2023
8641101
added tests for nested routes
lisabethu88 Jan 9, 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()'
26 changes: 26 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
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__)

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
if not test_config:
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_DATABASE_URI')
else:
app.config["Testing"] = True
app.config['SQLALCHEMY_TEST_DATABASE_URI'] = os.environ.get('SQLALCHEMY_TEST_DATABASE_URI')

from app.models.planet import Planet
from app.models.moon import Moon

db.init_app(app)
migrate.init_app(app, db)

from .planets_routes import planets_bp
app.register_blueprint(planets_bp)

from .moons_routes import moons_bp
app.register_blueprint(moons_bp)

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

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

Choose a reason for hiding this comment

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

Does this attribute hold a single planet, or many planets? If it is only ever connected to 1 planet, I would suggest using a singular variable name to better describe the contents.


def to_dict(self):
moon_dict = {}
moon_dict["id"] = self.id
moon_dict["size"] = self.size
moon_dict["description"] = self.description
moon_dict["name"] = self.name
return moon_dict

@classmethod
def from_dict(cls, moon_data):
new_moon = Moon(
name = moon_data["name"],
description = moon_data["description"],
size = moon_data["size"]
)
return new_moon
31 changes: 31 additions & 0 deletions app/models/planet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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)
description = db.Column(db.String)
diameter_in_km = db.Column(db.Integer)
moons = db.relationship("Moon", back_populates = "planets")


def to_dict(self):
planet_dict = {}
planet_dict["id"] = self.id
planet_dict["name"] = self.name
planet_dict["description"] = self.description
planet_dict["diameter_in_km"] = self.diameter_in_km
moon_names = []
for moon in self.moons:
moon_names.append(moon.name)
planet_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_dict["moons"] = [moon.name for moon in self.moons]

return planet_dict


@classmethod
def from_dict(cls, planet_data):
new_planet = Planet(
name = planet_data["name"],
description = planet_data["description"],
diameter_in_km = planet_data["diameter_in_km"]
)
return new_planet
32 changes: 32 additions & 0 deletions app/moons_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from app import db

Choose a reason for hiding this comment

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

It looks like the route files are loose in the app folder. To keep a project organized as it grows, I recommend creating a routes folder inside of app to place these in.

from app.models.planet import Planet
from app.models.moon import Moon
from app.planets_routes import validate_model
from app.models.moon import Moon
from flask import Blueprint, jsonify, abort, make_response, request

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

@moons_bp.route("", methods=["POST"])
def create_moon():
moon_data = request.get_json()
new_moon = Moon.from_dict(moon_data)

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

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

@moons_bp.route("", methods=["GET"])
def get_all_moons():
moons = Moon.query.all()

moons_response = []
for moon in moons:
moons_response.append(moon.to_dict())
Comment on lines +24 to +26

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.

moons_response = [moon.to_dict() for moon in moons]

return jsonify(moons_response)

@moons_bp.route("/<moon_id>", methods=["GET"])
def get_one_moon(moon_id):
moon = validate_model(Moon, moon_id)
return moon.to_dict()
138 changes: 138 additions & 0 deletions app/planets_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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
from sqlalchemy import desc, asc


# ---------helper functions--------

Choose a reason for hiding this comment

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

Specific teams might have their own style guides, but it is a common practice to place helper functions at the end of a file rather than the beginning. An often-used analogy is to lay out code like a newspaper article. Headlines and the most important information comes first - Functions that use other functions in the file go at the top. That gets followed by the details and extra information in an article, or in our code, all the helper functions that support the higher up functions.

def sort_planet_query(planet_query, sort_query, order_query):
if not sort_query:
sort_query = "name"

if not order_query:
order_query = "asc"

if order_query == "desc":
planet_query = planet_query.order_by(desc(sort_query))
else:
planet_query = planet_query.order_by(asc(sort_query))

return planet_query



Comment on lines +22 to +24

Choose a reason for hiding this comment

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

Tiny best practices feedback - we typically only want a single blank line to separate functions in a file. If we have blocks of functions that do different things, then we might consider using 2 blank lines. A solid example of this are lines 37 and 38 where we have 2 blank lines separating the block of helper functions from the route functions that follow.

def validate_model(cls, model_id):

Choose a reason for hiding this comment

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

Since this function is shared and not specific to the planet routes, I would suggest moving it out of the planet_routes.py file.

try:
model_id = int(model_id)
except:
abort(make_response(jsonify({"message":f"{cls.__name__} {model_id} invalid"}), 400))

model = cls.query.get(model_id)

if not model:
abort(make_response(jsonify({"message":f"{cls.__name__} #{model_id} not found"}), 404))

Choose a reason for hiding this comment

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

Around best practices, there are many lines across the files that break the PEP 8 guide of 79 characters max per line. I would keep an eye out for this in the future, especially on lines where there are nested function calls.


return model


# ------------route implementations-----------
planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets")

@planets_bp.route("", methods=["POST"])
def create_planet():
request_body = request.get_json()

# if "name" not in request_body or "description" not in request_body or 'diameter_in_km' not in request_body:
# return make_response("Invalid Request", 400)
Comment on lines +46 to +47

Choose a reason for hiding this comment

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

Gentle reminder that we want to remove commented code as part of our review and clean up steps before opening PRs.

try:
new_planet = Planet.from_dict(request_body)
except KeyError as err:
abort(make_response({"message":f"Missing {err.args[0]}."}, 400))

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 get_planets_optional_query():
planet_query = Planet.query
name_query = request.args.get("name")

if name_query:
planet_query = planet_query.filter(Planet.name.ilike(f"%{name_query}%"))

sort_query = request.args.get("sort")
order_query = request.args.get("order")

if sort_query or order_query:
planet_query = sort_planet_query(planet_query, sort_query, order_query)

planets = planet_query.all()
planets_response = []

for planet in planets:
planets_response.append(planet.to_dict())

return jsonify(planets_response)


@planets_bp.route("/<planet_id>", methods=["GET"])
def get_planet_by_id(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.name = request_body["name"]
planet.description = request_body["description"]
planet.diameter_in_km = request_body["diameter_in_km"]

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"))

@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)

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

request_body = request.get_json()
new_moon = Moon.from_dict(request_body)
new_moon.planet_id = planet_id

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


return make_response(jsonify({"message": f"Moon {new_moon.name} created with planet {planet.name}"}), 201)


# --------------for testing-----------------
# planets = [
# Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756),
# Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879),
# Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792)
#
2 changes: 0 additions & 2 deletions app/routes.py

This file was deleted.

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