-
Notifications
You must be signed in to change notification settings - Fork 11
Ocelots - Lisa Utsett, Xuan Hien Pham #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d0c1880
4dcfc9b
f7c2f4a
7e71408
c4ff040
0d44f67
823a64a
842c63a
f6d81a1
a1ba03e
e7051b2
99d983e
715df1c
cc0275a
6a8add5
55d4435
7eb943a
633fed3
0f090cc
9c42bf6
f067be1
6bf4e81
442ab49
d328a24
ec72cce
4c114f0
75eb5a4
0642a6a
fda0886
3f46339
cd5dd84
801b7ab
a9f0bc9
4e7921c
af46358
0dba82d
9a49851
8641101
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| web: gunicorn 'app:create_app()' |
| 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 |
| 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") | ||
|
|
||
| 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 | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| from app import db | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the route files are loose in the |
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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() | ||
| 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-------- | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
| # | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Generic single-database configuration. |
| 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 |
There was a problem hiding this comment.
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.