Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
37a5253
Get all planets
wreckbeck Apr 23, 2022
6029082
poppy's version wave 1
PoppyMeng Apr 23, 2022
5445087
poppy's version wave 1
PoppyMeng Apr 23, 2022
fd60f2e
trial
PoppyMeng Apr 23, 2022
b3a981f
Get one planet and add errors if not found or not valid
wreckbeck Apr 25, 2022
421d3d1
slove migrate problem
PoppyMeng May 1, 2022
d5b0ed6
solve migrate problem
PoppyMeng May 1, 2022
b12b280
Danielles changes
wreckbeck May 1, 2022
a3f508d
Start over
wreckbeck May 1, 2022
89f97cc
xxxx
PoppyMeng May 1, 2022
a40971f
debugging
PoppyMeng May 1, 2022
3954ff3
get Poppy's changes
wreckbeck May 1, 2022
4707883
fix database errors
wreckbeck May 1, 2022
61d7100
poppy is debugging
PoppyMeng May 2, 2022
05712cd
Read one, update one, delete one Planet
wreckbeck May 3, 2022
3d22747
changes during class
PoppyMeng May 4, 2022
13a75e5
add helper file
PoppyMeng May 4, 2022
f3c0e71
wave 05
PoppyMeng May 4, 2022
59893ad
Update planet model
wreckbeck May 4, 2022
5be6929
accept Poppy's changes
wreckbeck May 4, 2022
f860662
add methods to planet model
wreckbeck May 4, 2022
ab981c1
fix error in planet method
wreckbeck May 4, 2022
8b06e08
add test routes and configure testing environment
wreckbeck May 5, 2022
0b12beb
refactor and dry out HTTP methods
wreckbeck May 5, 2022
8c05d35
Add planet test data pytest fixture, complete all GET tests
wreckbeck May 5, 2022
c2c8c52
Co-authored-by: wreckbeck <wreckbeck@users.noreply.github.com>
PoppyMeng May 6, 2022
739dfc6
Update POST planets test response body
wreckbeck May 6, 2022
c34fea3
Add moon model and update planet model with reference to moons
wreckbeck May 10, 2022
b01f8d8
deploy
PoppyMeng May 11, 2022
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()'
24 changes: 24 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
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_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_TEST_DATABASE_URI")

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

from app.models.planet import Planet

from .routes import solar_bp
app.register_blueprint(solar_bp)

return app

17 changes: 17 additions & 0 deletions app/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Validate if GET by Planet ID does not exist
from flask import Blueprint, jsonify, request, make_response, abort
from app.models.planet import Planet


def validate_planet(planet_id):
Copy link
Copy Markdown

@yangashley yangashley May 6, 2022

Choose a reason for hiding this comment

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

Nice work pulling this into a helper to keep your routes short and sweet. In the future, you might need to make 'helper.py' more specific. Think about Task List, you have Goal and Task, you may need helpers for each. If you refactor helper methods into a helper file then you could have goal_helper.py and task_helper.py

try:
planet_id = int(planet_id)
except:
abort(make_response({"message": f"planet {planet_id} invalid"}, 400))

planet = Planet.query.get(planet_id)

if not planet:
abort(make_response({"message": f"planet {planet_id} not found"}, 404))

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

class Moon(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
size = db.Column(db.String)
description = db.Column(db.String)
color = db.Column(db.String)
planet_id = db.Column(db.Integer, db.ForeignKey('planet.id'))
planet = db.relationship("Planet", back_populates="moons")
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)
description = db.Column(db.String)
color = db.Column(db.String)
moons = db.relationship("Moon", back_populates="planet")

def to_json(self):
return {"id": self.id,
"name": self.name,
"description": self.description,
"color": self.color
}

def update(self, req_body):

self.name = req_body["name"]
self.description = req_body["description"]
self.color = req_body["color"]

@classmethod
def create(cls, req_body):
new_planet = cls(
name=req_body['name'],
description=req_body['description'],
color=req_body['color']
)

return new_planet
Comment on lines +17 to +31
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 comment applies to your create() and update() methods:

How would you add in validation to make sure that all the required fields are sent in the request to your server?

For example, if someone sent a POST request but left off color, then line 27 would throw KeyError that it couldn't find color.

it would be nice to handle the error and return a message so that the client knows their request was invalid and they need to include color. Something to think about for Task List.

61 changes: 60 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,61 @@
from flask import Blueprint
from flask import Blueprint, jsonify, request, make_response, abort
from app import db
from app.models.planet import Planet
from .helper import validate_planet

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

# GET and POST planets
@solar_bp.route("", methods=["POST", "GET"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It's good practice to keep in mind SRP - single responsibility principle for methods here.

Each method should handle its own route so you can split the POST and the GET requests into 2 separate methods. Maybe one called create_planet() for POST and get_planets() for GET

def handle_planets():
# Create a planet
if request.method== "POST":
request_body=request.get_json()
new_planet=Planet.create(request_body)

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

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

# Get all planets, or filter by planet name
elif request.method== "GET":
name_query=request.args.get('name')
if name_query:
planets=Planet.query.filter_by(name=name_query)
else:
planets=Planet.query.all()
planets_response=[]
for planet in planets:
planets_response.append(planet.to_json())

return jsonify(planets_response), 200


# GET ONE Planet
@solar_bp.route("/<planet_id>", methods=["GET"])
def read_one_planet(planet_id):
planet = validate_planet(planet_id)
return jsonify(planet.to_json()), 200

# UPDATE Planet
@solar_bp.route("/<planet_id>", methods=["PUT"])
def update_planet(planet_id):
planet = validate_planet(planet_id)
request_body = request.get_json()

planet.update(request_body)

db.session.commit()

return make_response(f"Planet #{planet.id} successfully updated")

# DELETE Planet
@solar_bp.route("/<planet_id>", methods=["DELETE"])
def delete_planet(planet_id):
planet = validate_planet(planet_id)

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

return make_response(f"Planet #{planet.id} successfully deleted")
Empty file added app/tests/__init__.py
Empty file.
38 changes: 38 additions & 0 deletions app/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import pytest
from app import create_app
from app import db
from flask.signals import request_finished
from app.models.planet import Planet


@pytest.fixture
def app():
app = create_app({"TESTING": True})

@request_finished.connect_via(app)
def expire_session(sender, response, **extra):
db.session.remove()

with app.app_context():
db.create_all()
yield app

with app.app_context():
db.drop_all()


@pytest.fixture
def client(app):
return app.test_client()

@pytest.fixture
def two_saved_planets(app):
Comment on lines +16 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍

planet_pluto = Planet(name="Pluto",
description="nobody loves me :(",
color="blue")
planet_sun = Planet(name="Sun",
description="im hot as heck son",
color="fiery red")
db.session.add_all([planet_pluto, planet_sun])
db.session.commit()
68 changes: 68 additions & 0 deletions app/tests/test_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

def test_get_all_planets_with_no_records(client):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These tests look good to me! 👍

# Act
response = client.get("/planets")
response_body = response.get_json()

# Assert
assert response.status_code == 200
assert response_body == []

def test_get_all_planets(client, two_saved_planets):
# Act
response = client.get("/planets")
response_body = response.get_json()

# Assert
assert response.status_code == 200
assert response_body == [
{
"id": 1,
"name": "Pluto",
"description": "nobody loves me :(",
"color": "blue"
},
{
"id": 2,
"name": "Sun",
"description": "im hot as heck son",
"color": "fiery red"
}
]

def test_get_one_planet(client, two_saved_planets):
# Act
response = client.get("/planets/1")
response_body = response.get_json()

# Assert
assert response.status_code == 200
assert response_body == {
"id": 1,
"name": "Pluto",
"description": "nobody loves me :(",
"color": "blue"
}

def test_get_one_planet_with_no_records(client):
# Act
response = client.get("/planets/1")
response_body = response.get_json()

# Assert
assert response.status_code == 404
assert response_body == {"message": "planet 1 not found"}


def test_create_one_planet(client):
# Act
response = client.post("/planets", json={
"name": "Mars",
"description": "Is Bruno Mars counted as Mars?",
"color": "orange"
})
response_body = response.get_json()

# Assert
assert response.status_code == 201
assert response_body == "Planet Mars successfully created"
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