Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e5d719c
Created Planets
kellytate Dec 14, 2022
fe92aad
Added /planets endpoint
kellytate Dec 14, 2022
6e1b787
added get planet by id, and add validation
smysh Dec 15, 2022
4759c30
Added a to_dict() method to convert data to dictionary.
smysh Dec 15, 2022
15c8865
Made planet module and added attribute
kellytate Dec 15, 2022
209d092
Moved planet.py, imported in routes.py, added additional planets.
smysh Dec 15, 2022
349bdca
changes to create the planet model. make sure to separately CREATE DA…
smysh Dec 20, 2022
1d2d1dc
added POST and GET routes to planets blueprint to create_planet_data(…
smysh Dec 21, 2022
798befb
Added endpoints to create and get all planets.
kellytate Dec 21, 2022
12087c9
Merged changes 12/21 in routes .py
smysh Dec 21, 2022
842e96d
Added Read, Update, and Delete endpoints
kellytate Dec 21, 2022
eb94c73
add query params to sort planets by name and display results in ascen…
smysh Dec 22, 2022
e06912c
refactor code for query params by name and sort asc or desc
smysh Dec 22, 2022
8729275
Added seed file
kellytate Dec 22, 2022
8a4bdd8
added reading SQL configuration from .env file. jsonified make_respon…
smysh Jan 4, 2023
0604d42
configure tests in conftest.py, added tests to read and create planet…
smysh Jan 4, 2023
c7de599
fix pytest warning
smysh Jan 4, 2023
bb771ec
Added tests for update and delete
kellytate Jan 4, 2023
4cb75a0
Added tests for edge cases for update and delete
kellytate Jan 4, 2023
672c773
Refactors and adds tests for to_dict() helper
kellytate Jan 4, 2023
f75313f
Adds tests for from_dict
kellytate Jan 4, 2023
abde75a
Refactors validate_planet to validate_model
kellytate Jan 4, 2023
706d123
Corrects typo in create_planet
kellytate Jan 4, 2023
5dce2e3
adds moon.py and moon_bp blueprint. Also adds routes to create and re…
smysh Jan 5, 2023
b9e8761
adds moon model migration change.
smysh Jan 5, 2023
653abf4
remove references to foreign key. tests pass and routes are working a…
smysh Jan 5, 2023
e99dba2
Updates planet and moon models for FK relationship
kellytate Jan 5, 2023
b622107
Migrations to add/update Moon and Planet models
kellytate Jan 5, 2023
6856cda
Removes old migration files
kellytate Jan 5, 2023
b0a271d
adds additional details to moon model.
smysh Jan 5, 2023
a6eb19c
Moved nested routes to planet_routes
kellytate Jan 5, 2023
4cf54b5
added Procfile. installed gunicorn and added it to requirements.
smysh Jan 6, 2023
0e2278c
added web: gunicorn to Procfile.
smysh Jan 6, 2023
c4eb47a
Minor correction to test name
kellytate Jan 8, 2023
dd97502
Adds Procfile
kellytate Jan 8, 2023
591ce8f
Removes commented-out code in moon_routes
kellytate Jan 9, 2023
9e83bbe
Merge branch 'main' of https://github.com/kellytate/solar-system-api
smysh Jan 9, 2023
23a31a7
Updates moon creation and deletion routes
kellytate Jan 10, 2023
789cd28
Merge branch 'main' of https://github.com/kellytate/solar-system-api
kellytate Jan 10, 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()'
29 changes: 29 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
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__)

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

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JSON_SORT_KEYS'] = False
app.url_map.strict_slashes = False

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

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

from .planet_routes import planets_bp
app.register_blueprint(planets_bp)

from .moon_routes import moon_bp
app.register_blueprint(moon_bp)

return app
Empty file added app/models/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions app/models/moon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from app import db
class Moon(db.Model):
id = db.Column(db.Integer,primary_key=True, autoincrement=True)
name = db.Column(db.String())
size = db.Column(db.Integer())
description = db.Column(db.String())
distance_from_planet = db.Column(db.Integer())
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
moon_as_dict["distance_from_planet"] = self.distance_from_planet
moon_as_dict["planet_id"] = self.planet_id

return moon_as_dict

@classmethod
def from_dict(cls, moon_data):
new_moon = Moon(
name=moon_data["name"],
description=moon_data["description"],
size=moon_data["size"],
distance_from_planet=moon_data["distance_from_planet"],
planet_id=moon_data["planet_id"]
)
return new_moon
33 changes: 33 additions & 0 deletions app/models/planet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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())
orbit_days = db.Column(db.Integer())
num_moons = db.Column(db.Integer())
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["orbit_days"] = self.orbit_days
planet_as_dict["num_moons"] = self.num_moons
moons_list = []
for moon in self.moons:
moons_list.append(moon.to_dict())
planet_as_dict["moons"] = moons_list

return planet_as_dict

@classmethod
def from_dict(cls, planet_data):
new_planet = Planet(
name=planet_data["name"],
description=planet_data["description"],
orbit_days=planet_data["orbit_days"],
num_moons=planet_data["num_moons"],

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

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

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

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

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

@moon_bp.route("", methods=["GET"])
def read_one_moon_by_name():

moons_query = Moon.query
name_query = request.args.get("name")
if name_query:
moons_query = moons_query.filter(Moon.name.ilike(f"%{name_query}%"))
moons = moons_query.all()
moons_response = []
for moon in moons:
moons_response.append(moon.to_dict())
return jsonify(moons_response)

@moon_bp.route("/<moon_id>", methods=["GET"])
def read_one_moon_by_id(moon_id):

moon = validate_model(Moon, moon_id)
return moon.to_dict()

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

moons = Moon.query.all()

moons_response = []
for moon in moons:
moons_response.append(moon.to_dict())
return jsonify(moons_response)


@moon_bp.route("/<moon_id>", methods=["DELETE"])
def delete_moon_by_id(moon_id):
moon = validate_model(Moon, moon_id)
planet = validate_model(Planet, moon.planet_id)

db.session.delete(moon)
planet.num_moons = len(planet.moons)
db.session.commit()

return make_response(jsonify(f"Moon {moon.name} successfully deleted"))


106 changes: 106 additions & 0 deletions app/planet_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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

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

def validate_model(cls, model_id):
try:
model_id = int(model_id)
except:
abort(make_response({"message":f"{cls.__name__} {model_id} invalid"}, 400))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nice use of introspection here :)


model = cls.query.get(model_id)

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

return model

@planets_bp.route("", methods=["POST"])
def create_planet():
request_body = request.get_json()
if "name" not in request_body:
return make_response("Invalid Request", 400)

new_planet = Planet.from_dict(request_body)
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 read_all_planets():
planets_response = []
planet_query = Planet.query

name_query = request.args.get("name")
if name_query:
planet_query = planet_query.filter(Planet.name.ilike(f"%{name_query}%"))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nice use of SQLAlchemy's case-insensitive searching


sort_query = request.args.get("sort")
if sort_query == "desc":
planet_query = planet_query.order_by(Planet.name.desc())
else:
planet_query = planet_query.order_by(Planet.name)


planets = planet_query.all()

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

@planets_bp.route("/<planet_id>", methods=["GET"])
def read_one_planet(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.orbit_days = request_body["orbit_days"]
planet.num_moons = request_body["num_moons"]

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=["POST"])
def create_one_moon_with_planet_id(planet_id):

planet = validate_model(Planet, planet_id)

request_body = request.get_json()
new_moon = Moon.from_dict(request_body)
new_moon.planet = planet
db.session.add(new_moon)
planet.num_moons = len(planet.moons)
db.session.commit()

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

@planets_bp.route("/<planet_id>/moons", methods=["GET"])
def read_all_moons_from_a_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)
13 changes: 13 additions & 0 deletions app/planet_seed.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
ALTER SEQUENCE planet_id_seq RESTART WITH 1;
ALTER SEQUENCE moon_id_seq RESTART WITH 1;
INSERT INTO planet VALUES (DEFAULT,'Mercury', 'Mercury is the closest planet to the Sun.', 88, 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This file is great to see here!

INSERT INTO planet VALUES (DEFAULT,'Venus', 'Venus is the hottest planet in the solar system.', 225 ,0);
INSERT INTO planet VALUES (DEFAULT,'Earth', 'Our home planet.', 365,1);
INSERT INTO planet VALUES (DEFAULT,'Mars', 'Also known as Red planet.', 687, 2);
INSERT INTO planet VALUES (DEFAULT,'Jupiter','Largest planet in the solar system.', 4333, 80);
INSERT INTO planet VALUES (DEFAULT,'Saturn', 'Only planet to have rings made of ice and rock.', 10759, 83);
INSERT INTO planet VALUES (DEFAULT,'Uranus', 'Only planet with a 97 degree tilted axis.', 30687, 27);
INSERT INTO planet VALUES (DEFAULT,'Neptune', 'Blue ice giant',60190, 14);
INSERT INTO moon VALUES (DEFAULT,'The Moon', 2159, 'The moon of Earth.', 238855, 3);
INSERT INTO moon VALUES (DEFAULT,'Deimos', 428, 'The smaller of the two moons of Mars.', 14580, 4);
INSERT INTO moon VALUES (DEFAULT,'The Phobos', 2618, 'The larger moon of Mars.', 3700, 4);
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