From e50ac41bf8291ba539148f993344a00bc35ecfe5 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 14 Dec 2022 11:08:11 -0800 Subject: [PATCH 01/62] Monica's update: set Planet class, Blueprint, and __init__ file --- app/__init__.py | 3 +++ app/routes.py | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 70b4cabfe..2e25643c2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,4 +4,7 @@ def create_app(test_config=None): app = Flask(__name__) + from .routes import planets_bp + app.register_blueprint(planets_bp) + return app diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..628105a97 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,16 @@ -from flask import Blueprint +from flask import Blueprint, jsonify +class Planets: + def __init__(self, id, name, description, gravity): + self.id = id + self.name = name + self.description = description + self.type = type + +planets = [ + Planets(1, "Mercury", "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), + Planets(1, "Earth", "The third planet from the Sun", "9.807 m/s^2"), + Planets(1, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") +] + +planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") \ No newline at end of file From 8080e4b90ea743da35b61e420d983d02eaa3eb49 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 14 Dec 2022 11:10:20 -0800 Subject: [PATCH 02/62] Updated Planets class attribute --- app/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 628105a97..56edd049b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -5,7 +5,7 @@ def __init__(self, id, name, description, gravity): self.id = id self.name = name self.description = description - self.type = type + self.gravity = gravity planets = [ Planets(1, "Mercury", "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), From fe2a6b1ac2a5512a0b009bcf81214612596cd2af Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 14 Dec 2022 11:13:54 -0800 Subject: [PATCH 03/62] "routes updates" --- app/routes.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..4a227d4a1 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,17 @@ -from flask import Blueprint +from flask import Blueprint, jsonify +class Planet: + def __init__(self, id, name, gravity,description): + self.id = id + self.name = name + self.gravity = gravity + self.description = description + +planets = [ + Planet(1,"Mars","","Mars is the fourth planet from the Sun. A dusty, cold, desert world with a very thin atmosphere.") +] +planets_bp = Blueprint("planets", __name__) + +@planets_bp.route("/planets", methods = ["GET"]) +def planet_json(): + response_body = [] \ No newline at end of file From 8ce866914d9ea5c9944924a20d8037d79d0a4f4f Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 14 Dec 2022 11:35:46 -0800 Subject: [PATCH 04/62] created planets route and tested in postman --- app/routes.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/routes.py b/app/routes.py index 0169202cc..078c5f41a 100644 --- a/app/routes.py +++ b/app/routes.py @@ -10,13 +10,20 @@ def __init__(self, id, name, description, gravity): planets = [ Planets(1, "Mercury", "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), - Planets(1, "Earth", "The third planet from the Sun", "9.807 m/s^2"), - Planets(1, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") + Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), + Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") ] planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") -@planets_bp.route("/planets", methods = ["GET"]) -def planet_json(): - response_body = [] - +@planets_bp.route("", methods = ["GET"]) +def planets_json(): + planets_response = [] + for planet in planets: + planets_response.append({ + "id": planet.id, + "name": planet.name, + "description": planet.description, + "gravity":planet.gravity + }) + return jsonify(planets_response) From d34fd18a5091474f91d8634b27455f0bd7d8a4fa Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 14 Dec 2022 20:44:00 -0800 Subject: [PATCH 05/62] wave 2 ready for review --- app/routes.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 078c5f41a..e6406cc6d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint, jsonify +from flask import Blueprint, jsonify, abort, make_response class Planets: @@ -27,3 +27,27 @@ def planets_json(): "gravity":planet.gravity }) return jsonify(planets_response) + +#validate planet id and response +def validate_planet(planet_id): + try: + book_id = int(book_id) + except: + abort(make_response({"message":f"planet {planet_id} invalid"}, 400)) + + for planet in planets: + if planet.id == planet_id: + return planet + + abort(make_response({"message":f"book {book_id} not found"}, 404)) + +#Get planet info by valid id +@planets_bp.route("/", methods = ["GET"]) +def planets_get_by_id(planet_id): + planet = validate_planet(planet_id) + return { + "id": planet.id, + "title": planet.title, + "description": planet.description + } + From 9d66b1266483ff5c58cc5f00e03348e8c692fa98 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 15 Dec 2022 11:27:58 -0800 Subject: [PATCH 06/62] Wave2 Completed, add routes by_name and by_id --- app/__init__.py | 1 + app/routes.py | 60 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 2e25643c2..3a9a24e63 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,6 +3,7 @@ def create_app(test_config=None): app = Flask(__name__) + app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically from .routes import planets_bp app.register_blueprint(planets_bp) diff --git a/app/routes.py b/app/routes.py index 078c5f41a..c8ea70bd1 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,22 +1,33 @@ -from flask import Blueprint, jsonify +from flask import Blueprint, jsonify, abort, make_response -class Planets: - def __init__(self, id, name, description, gravity): +class Planets: + def __init__(self, id, name, description, gravity): self.id = id - self.name = name - self.description = description - self.gravity = gravity - + self.name = name + self.description = description + self.gravity = gravity + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "gravity": self.gravity + } + + planets = [ - Planets(1, "Mercury", "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), + Planets(1, "Mercury", + "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") ] planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") -@planets_bp.route("", methods = ["GET"]) + +@planets_bp.route("", methods=["GET"]) def planets_json(): planets_response = [] for planet in planets: @@ -24,6 +35,33 @@ def planets_json(): "id": planet.id, "name": planet.name, "description": planet.description, - "gravity":planet.gravity + "gravity": planet.gravity }) - return jsonify(planets_response) + return jsonify(planets_response), 200 + + +def planet_validation(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response({"Message": "Planet id must be an integer."}, 401)) + + for planet in planets: + if planet.id == planet_id: + return planet + + abort(make_response({"Message": f"Planet {planet_id} not found."}, 404)) + + +@planets_bp.route("/", methods=["GET"]) +def get_planet_by_id(planet_id): + planet = planet_validation(planet_id) + return jsonify(planet.to_dict()), 200 + +@planets_bp.route("/name/", methods=["GET"]) +def get_planet_by_name(planet_name): + for planet in planets: + if planet.name.lower() == planet_name.lower(): + return jsonify(planet.to_dict()), 200 + + abort(make_response({"Message": f"Planet {planet_name} not found."}, 404)) From b9116c215f1cd9e27eac6f58cc9d8bfdb057b7f9 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 15 Dec 2022 11:35:51 -0800 Subject: [PATCH 07/62] Remove additional imports --- app/routes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/routes.py b/app/routes.py index 99fd79ebd..946c910cc 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,4 @@ from flask import Blueprint, jsonify, abort, make_response -from flask import Blueprint, jsonify, abort, make_response - class Planets: def __init__(self, id, name, description, gravity): From cfc1857931f901d7ae475f0e3f77cc32a0716a63 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Thu, 15 Dec 2022 14:15:03 -0800 Subject: [PATCH 08/62] add register --- app/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/__init__.py b/app/__init__.py index 2e25643c2..818cef5fd 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,6 +3,7 @@ def create_app(test_config=None): app = Flask(__name__) + app.config['JSON_SORT_KEYS'] = False from .routes import planets_bp app.register_blueprint(planets_bp) From 5bfeaa18591146d657dfe40376b45e8a74aacb87 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Tue, 20 Dec 2022 10:55:27 -0800 Subject: [PATCH 09/62] Wave 3 Completed --- app/__init__.py | 18 +++++++++++--- app/routes.py | 66 ------------------------------------------------- 2 files changed, 15 insertions(+), 69 deletions(-) delete mode 100644 app/routes.py diff --git a/app/__init__.py b/app/__init__.py index 3a9a24e63..4a59ded35 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,11 +1,23 @@ from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate +db = SQLAlchemy() # Create a database +migrate = Migrate() def create_app(test_config=None): app = Flask(__name__) - app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically + app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically + # Database Config + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + # Connection String + app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development' - from .routes import planets_bp - app.register_blueprint(planets_bp) + db.init_app(app) # Tell database this is the app it will work with + migrate.init_app(app, db) # Tell migrate this is the app to work with and the way to get to the database + + from .planet_routes import planets_bp + from app.models.planet import Planet # Import our model, before we do anything with our API + app.register_blueprint(planets_bp) return app diff --git a/app/routes.py b/app/routes.py deleted file mode 100644 index 946c910cc..000000000 --- a/app/routes.py +++ /dev/null @@ -1,66 +0,0 @@ -from flask import Blueprint, jsonify, abort, make_response - -class Planets: - def __init__(self, id, name, description, gravity): - self.id = id - self.name = name - self.description = description - self.gravity = gravity - - def to_dict(self): - return { - "id": self.id, - "name": self.name, - "description": self.description, - "gravity": self.gravity - } - - -planets = [ - Planets(1, "Mercury", - "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), - Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), - Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") -] - -planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") - - -@planets_bp.route("", methods=["GET"]) -def planets_json(): - planets_response = [] - for planet in planets: - planets_response.append({ - "id": planet.id, - "name": planet.name, - "description": planet.description, - "gravity": planet.gravity - }) - return jsonify(planets_response), 200 - - -def planet_validation(planet_id): - try: - planet_id = int(planet_id) - except: - abort(make_response({"Message": "Planet id must be an integer."}, 401)) - - for planet in planets: - if planet.id == planet_id: - return planet - - abort(make_response({"Message": f"Planet {planet_id} not found."}, 404)) - - -@planets_bp.route("/", methods=["GET"]) -def get_planet_by_id(planet_id): - planet = planet_validation(planet_id) - return jsonify(planet.to_dict()), 200 - -@planets_bp.route("/name/", methods=["GET"]) -def get_planet_by_name(planet_name): - for planet in planets: - if planet.name.lower() == planet_name.lower(): - return jsonify(planet.to_dict()), 200 - - abort(make_response({"Message": f"Planet {planet_name} not found."}, 404)) From 1c60c4b875c7bef6799349261624a7c7363af6e9 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Tue, 20 Dec 2022 11:58:29 -0800 Subject: [PATCH 10/62] wave 3 --- app/Models/__init__.py | 0 app/Models/planet.py | 10 ++ app/__init__.py | 15 ++- app/routes.py | 114 +++++++++--------- migrations/README | 1 + migrations/alembic.ini | 45 +++++++ migrations/env.py | 96 +++++++++++++++ migrations/script.py.mako | 24 ++++ .../934749f17def_new_planet_class_created.py | 34 ++++++ 9 files changed, 284 insertions(+), 55 deletions(-) create mode 100644 app/Models/__init__.py create mode 100644 app/Models/planet.py create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/934749f17def_new_planet_class_created.py diff --git a/app/Models/__init__.py b/app/Models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/Models/planet.py b/app/Models/planet.py new file mode 100644 index 000000000..7169373d7 --- /dev/null +++ b/app/Models/planet.py @@ -0,0 +1,10 @@ +from app import db + + +class Planets(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.String) + \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index eb993f8a2..ca7478e28 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,9 +1,22 @@ from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate + +#postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development +db = SQLAlchemy() +migrate = Migrate() 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 + app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development' + + + db.init_app(app) + migrate.init_app(app, db) + from app.Models.planet import Planets from .routes import planets_bp diff --git a/app/routes.py b/app/routes.py index 946c910cc..0913be7bf 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,66 +1,72 @@ -from flask import Blueprint, jsonify, abort, make_response - -class Planets: - def __init__(self, id, name, description, gravity): - self.id = id - self.name = name - self.description = description - self.gravity = gravity - - def to_dict(self): - return { - "id": self.id, - "name": self.name, - "description": self.description, - "gravity": self.gravity - } - - -planets = [ - Planets(1, "Mercury", - "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), - Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), - Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") -] +from app import db +from app.Models.planet import Planets +from flask import Blueprint, jsonify, abort, make_response,request planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") +#wave 3 create and read endpoints for planets +@planets_bp.route("", methods=["GET", "POST"]) +def planets_create_and_read(): + if request.method == "GET": + planets = Planets.query.all() + planets_response = [] + for planet in planets: + planets_response.append({ + "id": planet.id, + "name": planet.name, + "description": planet.description, + "gravity": planet.gravity + }) + return jsonify(planets_response), 200 + elif request.method == "POST": + planet_data = request.get_json() + new_planet = Planets( + name = planet_data["name"], + description = planet_data["description"], + gravity = planet_data["description"] + ) -@planets_bp.route("", methods=["GET"]) -def planets_json(): - planets_response = [] - for planet in planets: - planets_response.append({ - "id": planet.id, - "name": planet.name, - "description": planet.description, - "gravity": planet.gravity - }) - return jsonify(planets_response), 200 + db.session.add(new_planet) + db.session.commit() + return make_response(f"Planet {new_planet.name} successfully created.", 201) -def planet_validation(planet_id): - try: - planet_id = int(planet_id) - except: - abort(make_response({"Message": "Planet id must be an integer."}, 401)) - for planet in planets: - if planet.id == planet_id: - return planet +#--------------------------- hard coded data below ----------------------------------- - abort(make_response({"Message": f"Planet {planet_id} not found."}, 404)) +# planets = [ +# Planets(1, "Mercury", +# "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), +# Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), +# Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") +# ] -@planets_bp.route("/", methods=["GET"]) -def get_planet_by_id(planet_id): - planet = planet_validation(planet_id) - return jsonify(planet.to_dict()), 200 -@planets_bp.route("/name/", methods=["GET"]) -def get_planet_by_name(planet_name): - for planet in planets: - if planet.name.lower() == planet_name.lower(): - return jsonify(planet.to_dict()), 200 - abort(make_response({"Message": f"Planet {planet_name} not found."}, 404)) + +# def planet_validation(planet_id): +# try: +# planet_id = int(planet_id) +# except: +# abort(make_response({"Message": "Planet id must be an integer."}, 401)) + +# for planet in planets: +# if planet.id == planet_id: +# return planet + +# abort(make_response({"Message": f"Planet {planet_id} not found."}, 404)) + + +# @planets_bp.route("/", methods=["GET"]) +# def get_planet_by_id(planet_id): +# planet = planet_validation(planet_id) +# return jsonify(planet.to_dict()), 200 + +# @planets_bp.route("/name/", methods=["GET"]) +# def get_planet_by_name(planet_name): +# for planet in planets: +# if planet.name.lower() == planet_name.lower(): +# return jsonify(planet.to_dict()), 200 + +# abort(make_response({"Message": f"Planet {planet_name} not found."}, 404)) diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 000000000..f8ed4801f --- /dev/null +++ b/migrations/alembic.ini @@ -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 diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..8b3fb3353 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/934749f17def_new_planet_class_created.py b/migrations/versions/934749f17def_new_planet_class_created.py new file mode 100644 index 000000000..c2a0e1f54 --- /dev/null +++ b/migrations/versions/934749f17def_new_planet_class_created.py @@ -0,0 +1,34 @@ +"""new planet class created + +Revision ID: 934749f17def +Revises: +Create Date: 2022-12-20 11:08:45.015241 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '934749f17def' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('planets', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('gravity', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('planets') + # ### end Alembic commands ### From 1de39f465313bc5cef59a585ec68840190b9dd9a Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Tue, 20 Dec 2022 14:25:27 -0800 Subject: [PATCH 11/62] Wave3 update --- app/models/__init__.py | 0 app/models/planet.py | 6 +++ app/planet_routes.py | 101 ++++++++++++++++++++++++++++++++++++++ migrations/README | 1 + migrations/alembic.ini | 45 +++++++++++++++++ migrations/env.py | 96 ++++++++++++++++++++++++++++++++++++ migrations/script.py.mako | 24 +++++++++ 7 files changed, 273 insertions(+) create mode 100644 app/models/__init__.py create mode 100644 app/models/planet.py create mode 100644 app/planet_routes.py create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/models/planet.py b/app/models/planet.py new file mode 100644 index 000000000..640c5e509 --- /dev/null +++ b/app/models/planet.py @@ -0,0 +1,6 @@ +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) \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py new file mode 100644 index 000000000..b01b090e6 --- /dev/null +++ b/app/planet_routes.py @@ -0,0 +1,101 @@ +from flask import Blueprint, jsonify, abort, make_response, request +from app import db +from app.models.planet import Planet + +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: + return make_response("Invalid request.", 400) + + new_planet = Planet( + name = request_body["name"], + description = request_body["description"] + ) + + db.session.add(new_planet) + db.session.commit() + + return make_response(f"Planet: {new_planet.name} created successfully.", 201) + +@planets_bp.route("", methods = ["GET"]) +def read_all_planets(): + planets = Planet.query.all() + planet_response = [] + for planet in planets: + planet_response.append( + {"id": planet.id, + "name": planet.name, + "description": planet.description} + ) + return jsonify(planet_response), 200 + +# ----------------------- Hard coded routes for practice and notes ------------------------------ +# class Planets: +# def __init__(self, id, name, description, gravity): +# self.id = id +# self.name = name +# self.description = description +# self.gravity = gravity + +# def to_dict(self): +# return { +# "id": self.id, +# "name": self.name, +# "description": self.description, +# "gravity": self.gravity +# } + + +# planets = [ +# Planets(1, "Mercury", +# "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), +# Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), +# Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") +# ] + +# planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") + + +# @planets_bp.route("", methods=["GET"]) +# def planets_json(): +# planets_response = [] +# for planet in planets: +# planets_response.append({ +# "id": planet.id, +# "name": planet.name, +# "description": planet.description, +# "gravity": planet.gravity +# }) +# return jsonify(planets_response), 200 + + +# def planet_validation(planet_id): +# try: +# planet_id = int(planet_id) +# except: +# abort(make_response({"Message": "Planet id must be an integer."}, 401)) + +# for planet in planets: +# if planet.id == planet_id: +# return planet + +# abort(make_response({"Message": f"Planet {planet_id} not found."}, 404)) + + +# @planets_bp.route("/", methods=["GET"]) +# def get_planet_by_id(planet_id): +# planet = planet_validation(planet_id) +# return jsonify(planet.to_dict()), 200 + +# @planets_bp.route("/name/", methods=["GET"]) +# def get_planet_by_name(planet_name): +# for planet in planets: +# if planet.name.lower() == planet_name.lower(): +# return jsonify(planet.to_dict()), 200 + +# abort(make_response({"Message": f"Planet {planet_name} not found."}, 404)) + +# ----------------------- Hard coded routes for practice and notes ------------------------------ diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 000000000..f8ed4801f --- /dev/null +++ b/migrations/alembic.ini @@ -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 diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..8b3fb3353 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} From abc1a16ef96687080e30d1662a46c0937c103b7a Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Tue, 20 Dec 2022 17:24:35 -0800 Subject: [PATCH 12/62] Completed wave 4 --- app/planet_routes.py | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/app/planet_routes.py b/app/planet_routes.py index b01b090e6..e44f76030 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -4,6 +4,21 @@ planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") +#---------------------------------------------- Helper Functions----------------------- ----------------------- +def validate_planet_id(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response("Planet id is invalid"), 400) + + planet = Planet.query.get(planet_id) + + if not planet: + abort(make_response(f"Planet: {planet_id} is not found."), 404) + + return planet + +# ---------------------------------------------- Route Functions ---------------------------------------------- @planets_bp.route("", methods = ["POST"]) def create_planet(): request_body = request.get_json() @@ -32,6 +47,41 @@ def read_all_planets(): ) return jsonify(planet_response), 200 +@planets_bp.route("/", methods = ["GET"]) +def read_one_planet_by_id(planet_id): + planet = validate_planet_id(planet_id) + + return ({ + "id" : planet.id, + "name" : planet.name, + "description" : planet.description + }, 200) + +@planets_bp.route("/", methods = ["PUT"]) +def update_planet_by_id(planet_id): + planet = validate_planet_id(planet_id) + + request_body = request.get_json() + + if "name" in request_body: + planet.name = request_body["name"] + if "description" in request_body: + planet.name = request_body["description"] + + db.session.commit() + + return (f"Planet: {planet_id} has been updated successfully.", 200) + +@planets_bp.route("/", methods = ["DELETE"]) +def delete_planet_by_id(planet_id): + planet = validate_planet_id(planet_id) + + db.session.delete(planet) + db.session.commit() + + return (f"Planet: {planet_id} has been deleted successfully.", 200) + + # ----------------------- Hard coded routes for practice and notes ------------------------------ # class Planets: # def __init__(self, id, name, description, gravity): From e45b359cd2b3b65bf4bb8b335ec0a167d66ba29e Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Tue, 20 Dec 2022 23:24:03 -0800 Subject: [PATCH 13/62] wave 4 completed --- app/__init__.py | 1 + app/routes.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index ca7478e28..dceeb34e6 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -10,6 +10,7 @@ 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 app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development' diff --git a/app/routes.py b/app/routes.py index 0913be7bf..c363ce65e 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,9 +1,26 @@ from app import db from app.Models.planet import Planets -from flask import Blueprint, jsonify, abort, make_response,request +from flask import Blueprint, jsonify, abort, make_response,request, abort planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") +#helper functions +def validate_planet(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response( + {"message":f"Planet {planet_id} invalid"},400 + )) + planet = Planets.query.get(planet_id) + + if not planet: + abort(make_response( + {"message":f"Planet {planet_id} not found"}, 404 + )) + + return planet + #wave 3 create and read endpoints for planets @planets_bp.route("", methods=["GET", "POST"]) def planets_create_and_read(): @@ -32,6 +49,43 @@ def planets_create_and_read(): return make_response(f"Planet {new_planet.name} successfully created.", 201) +#wave 4 read, update and delete +@planets_bp.route("/", methods = ["GET"]) +#read planet by id endpoint +def read_planet_by_id(planet_id): + planet = validate_planet(planet_id) + return jsonify({ + "id": planet.id, + "name": planet.name, + "description": planet.description, + "gravity": planet.gravity + }) + +@planets_bp.route("/", methods = ["PUT"]) +#update planet by id endpoint +def update_planet(planet_id): + planet = validate_planet(planet_id) + planet_data = request.get_json() + + planet.name = planet_data["name"] + planet.description = planet_data["description"] + planet.gravity = planet_data["gravity"] + + db.session.commit() + + return make_response(f"Planet #{planet.id} successfully updated") + + +@planets_bp.route("/", methods = ["DELETE"]) +#delete planet by id endpoint +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") + #--------------------------- hard coded data below ----------------------------------- From d20aa2195de9fb4a9fc86db8cab41a6f1961cd90 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 21 Dec 2022 10:39:58 -0800 Subject: [PATCH 14/62] wave 4 completed and added distance attribute --- app/Models/planet.py | 1 + migrations/versions/4b0ace852662_.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 migrations/versions/4b0ace852662_.py diff --git a/app/Models/planet.py b/app/Models/planet.py index 7169373d7..863a6b1ed 100644 --- a/app/Models/planet.py +++ b/app/Models/planet.py @@ -7,4 +7,5 @@ class Planets(db.Model): name = db.Column(db.String) description = db.Column(db.String) gravity = db.Column(db.String) + distance = db.Column(db.String) \ No newline at end of file diff --git a/migrations/versions/4b0ace852662_.py b/migrations/versions/4b0ace852662_.py new file mode 100644 index 000000000..15e02b60e --- /dev/null +++ b/migrations/versions/4b0ace852662_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 4b0ace852662 +Revises: 934749f17def +Create Date: 2022-12-21 10:24:56.300990 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4b0ace852662' +down_revision = '934749f17def' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('planets', sa.Column('distance', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('planets', 'distance') + # ### end Alembic commands ### From 6f01bc3bfdbb376881b2e2e5e36773198500640e Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 21 Dec 2022 11:02:06 -0800 Subject: [PATCH 15/62] add gravity attr --- app/__init__.py | 2 +- app/models/planet.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 4a59ded35..b5d1efe45 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -17,7 +17,7 @@ def create_app(test_config=None): migrate.init_app(app, db) # Tell migrate this is the app to work with and the way to get to the database from .planet_routes import planets_bp - from app.models.planet import Planet # Import our model, before we do anything with our API + from app.Models.planet import Planet # Import our model, before we do anything with our API app.register_blueprint(planets_bp) return app diff --git a/app/models/planet.py b/app/models/planet.py index 640c5e509..4d9c7ff00 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -3,4 +3,5 @@ class Planet(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String) - description = db.Column(db.String) \ No newline at end of file + description = db.Column(db.String) + gravity = db.Column(db.String) \ No newline at end of file From b8d489a5e91941c84ab8ceea8f7cdce09aec2250 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 21 Dec 2022 12:01:14 -0800 Subject: [PATCH 16/62] "merge dev_jd to main." --- app/models/planet.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 4d9c7ff00..863a6b1ed 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -1,7 +1,11 @@ -from app import db +from app import db -class Planet(db.Model): - id = db.Column(db.Integer, primary_key=True, autoincrement=True) + +class Planets(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.String) \ No newline at end of file + gravity = db.Column(db.String) + distance = db.Column(db.String) + \ No newline at end of file From 1960d7ffdc9edcc5e109a4446ef436339493a473 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 14:32:10 -0800 Subject: [PATCH 17/62] updated --- app/__init__.py | 2 +- app/models/planet.py | 6 +-- app/planet_routes.py | 2 +- app/routes.py | 126 ------------------------------------------- 4 files changed, 5 insertions(+), 131 deletions(-) delete mode 100644 app/routes.py diff --git a/app/__init__.py b/app/__init__.py index 515b4b17f..8efc58394 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -18,7 +18,7 @@ def create_app(test_config=None): db.init_app(app) migrate.init_app(app, db) - from app.Models.planet import Planets + from app.models.planet import Planet from .planet_routes import planets_bp app.register_blueprint(planets_bp) diff --git a/app/models/planet.py b/app/models/planet.py index 863a6b1ed..47b610054 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -1,11 +1,11 @@ from app import db -class Planets(db.Model): +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.String) - distance = db.Column(db.String) + # gravity = db.Column(db.String) + # distance = db.Column(db.String) \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py index e44f76030..85794966c 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -4,7 +4,7 @@ planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") -#---------------------------------------------- Helper Functions----------------------- ----------------------- +#---------------------------------------------- Helper Functions---------------------------------------------- def validate_planet_id(planet_id): try: planet_id = int(planet_id) diff --git a/app/routes.py b/app/routes.py deleted file mode 100644 index c363ce65e..000000000 --- a/app/routes.py +++ /dev/null @@ -1,126 +0,0 @@ -from app import db -from app.Models.planet import Planets -from flask import Blueprint, jsonify, abort, make_response,request, abort - -planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") - -#helper functions -def validate_planet(planet_id): - try: - planet_id = int(planet_id) - except: - abort(make_response( - {"message":f"Planet {planet_id} invalid"},400 - )) - planet = Planets.query.get(planet_id) - - if not planet: - abort(make_response( - {"message":f"Planet {planet_id} not found"}, 404 - )) - - return planet - -#wave 3 create and read endpoints for planets -@planets_bp.route("", methods=["GET", "POST"]) -def planets_create_and_read(): - if request.method == "GET": - planets = Planets.query.all() - planets_response = [] - for planet in planets: - planets_response.append({ - "id": planet.id, - "name": planet.name, - "description": planet.description, - "gravity": planet.gravity - }) - return jsonify(planets_response), 200 - elif request.method == "POST": - planet_data = request.get_json() - new_planet = Planets( - name = planet_data["name"], - description = planet_data["description"], - gravity = planet_data["description"] - ) - - db.session.add(new_planet) - db.session.commit() - - return make_response(f"Planet {new_planet.name} successfully created.", 201) - - -#wave 4 read, update and delete -@planets_bp.route("/", methods = ["GET"]) -#read planet by id endpoint -def read_planet_by_id(planet_id): - planet = validate_planet(planet_id) - return jsonify({ - "id": planet.id, - "name": planet.name, - "description": planet.description, - "gravity": planet.gravity - }) - -@planets_bp.route("/", methods = ["PUT"]) -#update planet by id endpoint -def update_planet(planet_id): - planet = validate_planet(planet_id) - planet_data = request.get_json() - - planet.name = planet_data["name"] - planet.description = planet_data["description"] - planet.gravity = planet_data["gravity"] - - db.session.commit() - - return make_response(f"Planet #{planet.id} successfully updated") - - -@planets_bp.route("/", methods = ["DELETE"]) -#delete planet by id endpoint -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") - -#--------------------------- hard coded data below ----------------------------------- - - -# planets = [ -# Planets(1, "Mercury", -# "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), -# Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), -# Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") -# ] - - - - -# def planet_validation(planet_id): -# try: -# planet_id = int(planet_id) -# except: -# abort(make_response({"Message": "Planet id must be an integer."}, 401)) - -# for planet in planets: -# if planet.id == planet_id: -# return planet - -# abort(make_response({"Message": f"Planet {planet_id} not found."}, 404)) - - -# @planets_bp.route("/", methods=["GET"]) -# def get_planet_by_id(planet_id): -# planet = planet_validation(planet_id) -# return jsonify(planet.to_dict()), 200 - -# @planets_bp.route("/name/", methods=["GET"]) -# def get_planet_by_name(planet_name): -# for planet in planets: -# if planet.name.lower() == planet_name.lower(): -# return jsonify(planet.to_dict()), 200 - -# abort(make_response({"Message": f"Planet {planet_name} not found."}, 404)) From 706bd4bbe5a9b698818e310e137bdc2fe7613f21 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 21 Dec 2022 14:45:09 -0800 Subject: [PATCH 18/62] migrations version file upload --- migrations/versions/4b0ace852662_.py | 28 +++++++++++++++ .../934749f17def_new_planet_class_created.py | 34 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 migrations/versions/4b0ace852662_.py create mode 100644 migrations/versions/934749f17def_new_planet_class_created.py diff --git a/migrations/versions/4b0ace852662_.py b/migrations/versions/4b0ace852662_.py new file mode 100644 index 000000000..15e02b60e --- /dev/null +++ b/migrations/versions/4b0ace852662_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 4b0ace852662 +Revises: 934749f17def +Create Date: 2022-12-21 10:24:56.300990 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4b0ace852662' +down_revision = '934749f17def' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('planets', sa.Column('distance', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('planets', 'distance') + # ### end Alembic commands ### diff --git a/migrations/versions/934749f17def_new_planet_class_created.py b/migrations/versions/934749f17def_new_planet_class_created.py new file mode 100644 index 000000000..c2a0e1f54 --- /dev/null +++ b/migrations/versions/934749f17def_new_planet_class_created.py @@ -0,0 +1,34 @@ +"""new planet class created + +Revision ID: 934749f17def +Revises: +Create Date: 2022-12-20 11:08:45.015241 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '934749f17def' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('planets', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('gravity', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('planets') + # ### end Alembic commands ### From ec369090d6905ce6ecb7cdc8744b6be5002c55c0 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 15:11:34 -0800 Subject: [PATCH 19/62] Completed wave 4, error free --- app/models/planet.py | 4 +-- migrations/versions/69017a0f8abc_.py | 44 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/69017a0f8abc_.py diff --git a/app/models/planet.py b/app/models/planet.py index 47b610054..0d80dcf01 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -6,6 +6,6 @@ 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.String) - # distance = db.Column(db.String) + gravity = db.Column(db.String) + distance = db.Column(db.String) \ No newline at end of file diff --git a/migrations/versions/69017a0f8abc_.py b/migrations/versions/69017a0f8abc_.py new file mode 100644 index 000000000..9a34addae --- /dev/null +++ b/migrations/versions/69017a0f8abc_.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: 69017a0f8abc +Revises: 4b0ace852662 +Create Date: 2022-12-21 14:53:17.119811 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '69017a0f8abc' +down_revision = '4b0ace852662' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('planet', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('gravity', sa.String(), nullable=True), + sa.Column('distance', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.drop_table('planets') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('planets', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('description', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('gravity', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('distance', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint('id', name='planets_pkey') + ) + op.drop_table('planet') + # ### end Alembic commands ### From bad112310f1cc4250c2e40f89f26996d47ecdb04 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 21 Dec 2022 15:58:14 -0800 Subject: [PATCH 20/62] change class planets to planet in planet.py --- app/Models/planet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/planet.py b/app/Models/planet.py index 863a6b1ed..0d80dcf01 100644 --- a/app/Models/planet.py +++ b/app/Models/planet.py @@ -1,7 +1,7 @@ from app import db -class Planets(db.Model): +class Planet(db.Model): id = db.Column(db.Integer,primary_key = True, autoincrement = True) name = db.Column(db.String) From b9753252e16ce67c79ca728432057e0fcb48f213 Mon Sep 17 00:00:00 2001 From: Jewelhae <113408512+Jewelhae@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:05:45 -0800 Subject: [PATCH 21/62] Delete app/Models directory --- app/Models/__init__.py | 0 app/Models/planet.py | 11 ----------- 2 files changed, 11 deletions(-) delete mode 100644 app/Models/__init__.py delete mode 100644 app/Models/planet.py diff --git a/app/Models/__init__.py b/app/Models/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/Models/planet.py b/app/Models/planet.py deleted file mode 100644 index 0d80dcf01..000000000 --- a/app/Models/planet.py +++ /dev/null @@ -1,11 +0,0 @@ -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.String) - distance = db.Column(db.String) - \ No newline at end of file From edac9e702087baf564dc00429dfdb9d93691a650 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 16:38:41 -0800 Subject: [PATCH 22/62] updated code --- app/models/planet.py | 4 ++-- app/planet_routes.py | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 0d80dcf01..8b7a78b54 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -6,6 +6,6 @@ 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.String) - distance = db.Column(db.String) + gravity = db.Column(db.Float) + distance = db.Column(db.Float) \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py index 85794966c..38bca678e 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -27,7 +27,9 @@ def create_planet(): new_planet = Planet( name = request_body["name"], - description = request_body["description"] + description = request_body["description"], + gravity = request_body["gravity"], + distance = request_body["distance"] ) db.session.add(new_planet) @@ -43,7 +45,9 @@ def read_all_planets(): planet_response.append( {"id": planet.id, "name": planet.name, - "description": planet.description} + "description": planet.description, + "gravity": planet.gravity, + "distance": planet.distance} ) return jsonify(planet_response), 200 @@ -54,7 +58,9 @@ def read_one_planet_by_id(planet_id): return ({ "id" : planet.id, "name" : planet.name, - "description" : planet.description + "description" : planet.description, + "gravity": planet.gravity, + "distance": planet.distance }, 200) @planets_bp.route("/", methods = ["PUT"]) @@ -63,10 +69,10 @@ def update_planet_by_id(planet_id): request_body = request.get_json() - if "name" in request_body: - planet.name = request_body["name"] - if "description" in request_body: - planet.name = request_body["description"] + planet.name = request_body["name"] + planet.description = request_body["description"] + planet.gravity = request_body["gravity"] + planet.distance = request_body["distance"] db.session.commit() From 81de35a313005940d156b56acdd7c4dd7fa6facf Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 16:41:51 -0800 Subject: [PATCH 23/62] Error Handling added --- app/planet_routes.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 38bca678e..2f4d1f1d5 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -18,12 +18,18 @@ def validate_planet_id(planet_id): return planet +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" not in request_body: + + abort(make_response("Invalid Request", 400)) + # ---------------------------------------------- Route Functions ---------------------------------------------- @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: - return make_response("Invalid request.", 400) + abort(make_response("Invalid request.", 400)) new_planet = Planet( name = request_body["name"], @@ -68,6 +74,7 @@ def update_planet_by_id(planet_id): planet = validate_planet_id(planet_id) request_body = request.get_json() + validate_request_body(request_body) planet.name = request_body["name"] planet.description = request_body["description"] From 30f21f3ead7bff24e6f0f79945350e0ef16b85cb Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 17:18:26 -0800 Subject: [PATCH 24/62] Merge branch 'main' of https://github.com/Yf-Monica-Bao/solar-system-api --- app/planet_routes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 2f4d1f1d5..789478c46 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -28,8 +28,7 @@ def validate_request_body(request_body): @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: - abort(make_response("Invalid request.", 400)) + validate_request_body(request_body) new_planet = Planet( name = request_body["name"], From ff6a5216fa02b39dc11f95715f6f9ad868044378 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 17:33:00 -0800 Subject: [PATCH 25/62] Error handling fixed --- app/planet_routes.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 789478c46..162e752d8 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -9,13 +9,12 @@ def validate_planet_id(planet_id): try: planet_id = int(planet_id) except: - abort(make_response("Planet id is invalid"), 400) - + abort(make_response({"message":f"Planet_id {planet_id} is invalid"}, 400)) + planet = Planet.query.get(planet_id) if not planet: - abort(make_response(f"Planet: {planet_id} is not found."), 404) - + abort(make_response({"message":f"Planet_id {planet_id} not found"}, 404)) return planet def validate_request_body(request_body): From 22793cb629c3bf1f63e8d1ac2439db40ba354bd5 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 18:48:42 -0800 Subject: [PATCH 26/62] Added compare_type to Migrate() to detect type changes --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 8efc58394..41cd64dc0 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -5,7 +5,7 @@ #postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development db = SQLAlchemy() -migrate = Migrate() +migrate = Migrate(compare_type=True) def create_app(test_config=None): app = Flask(__name__) From 2fd38ae838556e1de2511b6490b6009fb97ce2de Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 21 Dec 2022 20:38:56 -0800 Subject: [PATCH 27/62] migrations file updated --- migrations/versions/3f86dac7d25d_.py | 34 ++++++++++++++++++++++++++++ migrations/versions/f8a4e2800d01_.py | 34 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 migrations/versions/3f86dac7d25d_.py create mode 100644 migrations/versions/f8a4e2800d01_.py diff --git a/migrations/versions/3f86dac7d25d_.py b/migrations/versions/3f86dac7d25d_.py new file mode 100644 index 000000000..66ba555d2 --- /dev/null +++ b/migrations/versions/3f86dac7d25d_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 3f86dac7d25d +Revises: f8a4e2800d01 +Create Date: 2022-12-21 18:48:03.587168 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3f86dac7d25d' +down_revision = 'f8a4e2800d01' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('planet', 'gravity', + existing_type=sa.INTEGER(), + type_=sa.Float(), + existing_nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('planet', 'gravity', + existing_type=sa.Float(), + type_=sa.INTEGER(), + existing_nullable=True) + # ### end Alembic commands ### diff --git a/migrations/versions/f8a4e2800d01_.py b/migrations/versions/f8a4e2800d01_.py new file mode 100644 index 000000000..6b9f52160 --- /dev/null +++ b/migrations/versions/f8a4e2800d01_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: f8a4e2800d01 +Revises: 69017a0f8abc +Create Date: 2022-12-21 18:46:20.450773 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'f8a4e2800d01' +down_revision = '69017a0f8abc' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('planet', 'gravity', + existing_type=postgresql.DOUBLE_PRECISION(precision=53), + type_=sa.Integer(), + existing_nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('planet', 'gravity', + existing_type=sa.Integer(), + type_=postgresql.DOUBLE_PRECISION(precision=53), + existing_nullable=True) + # ### end Alembic commands ### From 4f43976870fc7835254165279fc7b8f95cc3a42b Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 22 Dec 2022 00:23:35 -0800 Subject: [PATCH 28/62] Wave5 in progress --- app/planet_routes.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 162e752d8..302632c5a 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -1,6 +1,7 @@ from flask import Blueprint, jsonify, abort, make_response, request from app import db from app.models.planet import Planet +from sqlalchemy import desc, asc planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") @@ -43,7 +44,22 @@ def create_planet(): @planets_bp.route("", methods = ["GET"]) def read_all_planets(): - planets = Planet.query.all() + + # Query planets use name argument + name_query = request.args.get("name") + if name_query: + planets = Planet.query.filter_by(name = name_query) + + # Sort argument passed by client + is_sort = request.args.get("sort") + if is_sort == "asc": + planets = Planet.query.order_by(asc(Planet.name)).all() + elif is_sort == "desc": + planets = Planet.query.order_by(desc(Planet.name)).all() + + if not name_query and not is_sort: + planets = Planet.query.all() + planet_response = [] for planet in planets: planet_response.append( From 23e7900c37aa1ff1fe53d96a50b62e12254546c2 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 22 Dec 2022 18:12:03 -0800 Subject: [PATCH 29/62] Wave5 Completed --- app/planet_routes.py | 51 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 302632c5a..cba45294e 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -1,7 +1,6 @@ from flask import Blueprint, jsonify, abort, make_response, request from app import db from app.models.planet import Planet -from sqlalchemy import desc, asc planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") @@ -24,6 +23,18 @@ def validate_request_body(request_body): abort(make_response("Invalid Request", 400)) +# def sort_helper(planet_query, atr, sort_method): +# 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: +# planet_query = planet_query.order_by(Planet.name.asc()) #Sort by name in ascending order by default + +# return planet_query + # ---------------------------------------------- Route Functions ---------------------------------------------- @planets_bp.route("", methods = ["POST"]) def create_planet(): @@ -43,22 +54,40 @@ def create_planet(): return make_response(f"Planet: {new_planet.name} created successfully.", 201) @planets_bp.route("", methods = ["GET"]) -def read_all_planets(): +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") - if name_query: - planets = Planet.query.filter_by(name = name_query) - + distance_query = request.args.get("distance") + gravity_query = request.args.get("gravity") # Sort argument passed by client is_sort = request.args.get("sort") - if is_sort == "asc": - planets = Planet.query.order_by(asc(Planet.name)).all() - elif is_sort == "desc": - planets = Planet.query.order_by(desc(Planet.name)).all() - if not name_query and not is_sort: - planets = Planet.query.all() + # Attribute that users want to sort by + # If it's none, then we sort by name in ascending order by default + # atr_to_be_sort = None + + if name_query: + planet_query = planet_query.filter_by(name = name_query) + # atr_to_be_sort = Planet.name + + if distance_query: + planet_query = planet_query.filter_by(distance = distance_query) + # atr_to_be_sort = Planet.distance + + if gravity_query: + planet_query = planet_query.filter_by(gravity = gravity_query) + # atr_to_be_sort = Planet.gravity + + if is_sort: + if is_sort == "asc": + planet_query = planet_query.order_by(Planet.name.asc()) + else: + planet_query = planet_query.order_by(Planet.name.desc()) + + planets = planet_query.all() planet_response = [] for planet in planets: From a877c4dd89a2b1458399c7e71baaca6b68262ecc Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 22 Dec 2022 18:57:48 -0800 Subject: [PATCH 30/62] Add feature: Sort records based on clients' requested attribute --- app/planet_routes.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index cba45294e..ecc19cd68 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -82,10 +82,29 @@ def read_planets(): # atr_to_be_sort = Planet.gravity if is_sort: - if is_sort == "asc": - planet_query = planet_query.order_by(Planet.name.asc()) - else: + attribute = is_sort.split(":")[0] + sort_method = is_sort.split(":")[1] + + # Sort records by client's request + if attribute == "name": + if sort_method == "asc": + planet_query = planet_query.order_by(Planet.name.asc()) + else: + planet_query = planet_query.order_by(Planet.name.desc()) + elif attribute == "distance": + if sort_method == "asc": + planet_query = planet_query.order_by(Planet.distance.asc()) + else: + planet_query = planet_query.order_by(Planet.distance.desc()) + elif attribute == "gravity": + if sort_method == "asc": + planet_query = planet_query.order_by(Planet.gravity.asc()) + else: + planet_query = planet_query.order_by(Planet.gravity.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()) planets = planet_query.all() From 33471430be6556acf36dac984dcc4ff39408fd2a Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 22 Dec 2022 19:09:52 -0800 Subject: [PATCH 31/62] Error Handling added --- app/planet_routes.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index ecc19cd68..b7ee4cc0f 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -82,8 +82,16 @@ def read_planets(): # atr_to_be_sort = Planet.gravity if is_sort: - attribute = is_sort.split(":")[0] - sort_method = is_sort.split(":")[1] + attribute = None + sort_method = is_sort + + split_sort = is_sort.split(":") + + if len(split_sort) == 2: + 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 == "name": From 2fb4764a35a51108db44dd0fdc10dedbbac9a92b Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 22 Dec 2022 19:35:38 -0800 Subject: [PATCH 32/62] Added helper function to clean up the code --- app/planet_routes.py | 55 +++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index b7ee4cc0f..849b20b4c 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -23,17 +23,18 @@ def validate_request_body(request_body): abort(make_response("Invalid Request", 400)) -# def sort_helper(planet_query, atr, sort_method): -# 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: -# planet_query = planet_query.order_by(Planet.name.asc()) #Sort by name in ascending order by default - -# return planet_query +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 # ---------------------------------------------- Route Functions ---------------------------------------------- @planets_bp.route("", methods = ["POST"]) @@ -65,21 +66,14 @@ def read_planets(): # Sort argument passed by client is_sort = request.args.get("sort") - # Attribute that users want to sort by - # If it's none, then we sort by name in ascending order by default - # atr_to_be_sort = None - if name_query: planet_query = planet_query.filter_by(name = name_query) - # atr_to_be_sort = Planet.name if distance_query: planet_query = planet_query.filter_by(distance = distance_query) - # atr_to_be_sort = Planet.distance if gravity_query: planet_query = planet_query.filter_by(gravity = gravity_query) - # atr_to_be_sort = Planet.gravity if is_sort: attribute = None @@ -87,32 +81,21 @@ def read_planets(): split_sort = is_sort.split(":") - if len(split_sort) == 2: + if len(split_sort) == 2: # Case: ?sort=attribute:asc attribute = split_sort[0] sort_method = split_sort[1] - if len(split_sort) > 2: + if len(split_sort) > 2: abort(make_response("Too many parameters", 400)) # Sort records by client's request if attribute == "name": - if sort_method == "asc": - planet_query = planet_query.order_by(Planet.name.asc()) - else: - planet_query = planet_query.order_by(Planet.name.desc()) + planet_query = sort_helper(planet_query, Planet.name, sort_method) elif attribute == "distance": - if sort_method == "asc": - planet_query = planet_query.order_by(Planet.distance.asc()) - else: - planet_query = planet_query.order_by(Planet.distance.desc()) + planet_query = sort_helper(planet_query, Planet.distance, sort_method) elif attribute == "gravity": - if sort_method == "asc": - planet_query = planet_query.order_by(Planet.gravity.asc()) - else: - planet_query = planet_query.order_by(Planet.gravity.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()) + 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) planets = planet_query.all() From 1a82e92fc3e2f8fb3756dd59c8a81825184a78fc Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Sun, 1 Jan 2023 20:30:07 -0800 Subject: [PATCH 33/62] testing config added --- app/__init__.py | 20 ++++++++---- app/planet_routes.py | 77 +++----------------------------------------- 2 files changed, 18 insertions(+), 79 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 41cd64dc0..96ec1d425 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,19 +1,27 @@ 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 - app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development' - + # app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically + # app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + + if test_config: + app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") + else: + app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_DATABASE_URI") db.init_app(app) migrate.init_app(app, db) diff --git a/app/planet_routes.py b/app/planet_routes.py index 849b20b4c..666e9fb67 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -52,7 +52,7 @@ def create_planet(): db.session.add(new_planet) db.session.commit() - return make_response(f"Planet: {new_planet.name} created successfully.", 201) + return make_response(jsonify(f"Planet: {new_planet.name} created successfully."), 201) @planets_bp.route("", methods = ["GET"]) def read_planets(): @@ -108,7 +108,7 @@ def read_planets(): "gravity": planet.gravity, "distance": planet.distance} ) - return jsonify(planet_response), 200 + return make_response(jsonify(planet_response), 200) @planets_bp.route("/", methods = ["GET"]) def read_one_planet_by_id(planet_id): @@ -136,7 +136,7 @@ def update_planet_by_id(planet_id): db.session.commit() - return (f"Planet: {planet_id} has been updated successfully.", 200) + return make_response(jsonify(f"Planet: {planet_id} has been updated successfully."), 200) @planets_bp.route("/", methods = ["DELETE"]) def delete_planet_by_id(planet_id): @@ -145,73 +145,4 @@ def delete_planet_by_id(planet_id): db.session.delete(planet) db.session.commit() - return (f"Planet: {planet_id} has been deleted successfully.", 200) - - -# ----------------------- Hard coded routes for practice and notes ------------------------------ -# class Planets: -# def __init__(self, id, name, description, gravity): -# self.id = id -# self.name = name -# self.description = description -# self.gravity = gravity - -# def to_dict(self): -# return { -# "id": self.id, -# "name": self.name, -# "description": self.description, -# "gravity": self.gravity -# } - - -# planets = [ -# Planets(1, "Mercury", -# "The smallest planet in our solar system and nearest to the Sun.", "3.7 m/s^2"), -# Planets(2, "Earth", "The third planet from the Sun", "9.807 m/s^2"), -# Planets(3, "Jupiter", "The largest planet in the solar system", "24.79 m/s^2") -# ] - -# planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") - - -# @planets_bp.route("", methods=["GET"]) -# def planets_json(): -# planets_response = [] -# for planet in planets: -# planets_response.append({ -# "id": planet.id, -# "name": planet.name, -# "description": planet.description, -# "gravity": planet.gravity -# }) -# return jsonify(planets_response), 200 - - -# def planet_validation(planet_id): -# try: -# planet_id = int(planet_id) -# except: -# abort(make_response({"Message": "Planet id must be an integer."}, 401)) - -# for planet in planets: -# if planet.id == planet_id: -# return planet - -# abort(make_response({"Message": f"Planet {planet_id} not found."}, 404)) - - -# @planets_bp.route("/", methods=["GET"]) -# def get_planet_by_id(planet_id): -# planet = planet_validation(planet_id) -# return jsonify(planet.to_dict()), 200 - -# @planets_bp.route("/name/", methods=["GET"]) -# def get_planet_by_name(planet_name): -# for planet in planets: -# if planet.name.lower() == planet_name.lower(): -# return jsonify(planet.to_dict()), 200 - -# abort(make_response({"Message": f"Planet {planet_name} not found."}, 404)) - -# ----------------------- Hard coded routes for practice and notes ------------------------------ + return make_response(jsonify(f"Planet: {planet_id} has been deleted successfully."), 200) From caebe3327fe9f8554c7c025eedd92b4be8fe9119 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Sun, 1 Jan 2023 21:43:36 -0800 Subject: [PATCH 34/62] Tests added --- app/tests/__init__.py | 0 app/tests/conftest.py | 43 ++++++++++++++++++++ app/tests/test_routes.py | 86 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 app/tests/__init__.py create mode 100644 app/tests/conftest.py create mode 100644 app/tests/test_routes.py diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/conftest.py b/app/tests/conftest.py new file mode 100644 index 000000000..4f0cd6bea --- /dev/null +++ b/app/tests/conftest.py @@ -0,0 +1,43 @@ +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({"TEST": 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 saved_two_planets(app): + mars = Planet(name="Mars", + description="This is planet: Mars", + gravity=3.721, + distance=60.81) + + jupiter = Planet(name="Jupiter", + description="This is planet: Jupiter", + gravity=24.79, + distance=467.64) + + db.session.add_all([mars, jupiter]) + + db.session.commit() diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py new file mode 100644 index 000000000..d39153928 --- /dev/null +++ b/app/tests/test_routes.py @@ -0,0 +1,86 @@ +def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): + response = client.get("/planets/1") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["name"] == "Mars" + + +def test_get_planet_by_not_exist_id_return_404(client): + response = client.get("/planets/1") + response_body = response.get_json() + + assert response.status_code == 404 + +def test_get_planet_by_invalid_planet_id_return_400_bad_request_error(client, saved_two_planets): + response = client.get("/planets/hello") + response_body = response.get_json() + + assert response.status_code == 400 + assert response_body == {"message": "Planet_id hello is invalid"} + + +def test_get_all_planets_with_no_records_return_empty_array(client): + response = client.get("/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == [] + + +def test_get_all_planets_with_two_records_return_array_with_size_2(client, saved_two_planets): + response = client.get("/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert len(response_body) == 2 + assert response_body[0] == {"id": 1, + "name": "Mars", + "description": "This is planet: Mars", + "gravity": 3.721, + "distance": 60.81} + assert response_body[1] == {"id": 2, + "name": "Jupiter", + "description": "This is planet: Jupiter", + "gravity": 24.79, + "distance": 467.64} + + +def test_create_one_planet_return_201_successfully_created(client): + resposne = client.post("/planets", + json={"name": "Venus", + "description": "This is planet: Venus", + "gravity": 9.87, + "distance": 67.685}) + response_body = resposne.get_json() + + assert resposne.status_code == 201 + assert response_body == "Planet: Venus created successfully." + + +def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): + resposne = client.put("/planets/1", + json={"name": "New Planet", + "description": "This a New Planet", + "gravity": 20.0, + "distance": 55.99}) + response_body = resposne.get_json() + + assert resposne.status_code == 200 + assert response_body == "Planet: 1 has been updated successfully." + + +def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, saved_two_planets): + response = client.delete("planets/1") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == "Planet: 1 has been deleted successfully." + +def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, saved_two_planets): + response = client.delete("planets/10") + response_body = response.get_json() + + assert response.status_code == 404 + assert response_body == {"message": "Planet_id 10 not found"} + From 946e8249516950451c0ac11a5f10670a0d30594c Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Tue, 3 Jan 2023 12:10:06 -0800 Subject: [PATCH 35/62] Testing files modified --- app/__init__.py | 1 + app/models/planet.py | 3 +- app/planet_routes.py | 18 ++++----- app/tests/__init__.py | 0 app/tests/conftest.py | 43 -------------------- app/tests/test_routes.py | 86 ---------------------------------------- 6 files changed, 11 insertions(+), 140 deletions(-) delete mode 100644 app/tests/__init__.py delete mode 100644 app/tests/conftest.py delete mode 100644 app/tests/test_routes.py diff --git a/app/__init__.py b/app/__init__.py index 96ec1d425..71658c9b1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -18,6 +18,7 @@ def create_app(test_config=None): app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") + app.config["Testing"] = True else: app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False diff --git a/app/models/planet.py b/app/models/planet.py index 8b7a78b54..ca3daef35 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -7,5 +7,4 @@ class Planet(db.Model): name = db.Column(db.String) description = db.Column(db.String) gravity = db.Column(db.Float) - distance = db.Column(db.Float) - \ No newline at end of file + distance_from_earth = db.Column(db.Float) \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py index 666e9fb67..26158b091 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -19,7 +19,7 @@ def validate_planet_id(planet_id): 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" not in request_body: + not in request_body or "distance_from_earth" not in request_body: abort(make_response("Invalid Request", 400)) @@ -46,7 +46,7 @@ def create_planet(): name = request_body["name"], description = request_body["description"], gravity = request_body["gravity"], - distance = request_body["distance"] + distance_from_earth = request_body["distance_from_earth"] ) db.session.add(new_planet) @@ -61,7 +61,7 @@ def read_planets(): # Query planets use name argument name_query = request.args.get("name") - distance_query = request.args.get("distance") + 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") @@ -70,7 +70,7 @@ def read_planets(): planet_query = planet_query.filter_by(name = name_query) if distance_query: - planet_query = planet_query.filter_by(distance = 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) @@ -90,8 +90,8 @@ def read_planets(): # Sort records by client's request if attribute == "name": planet_query = sort_helper(planet_query, Planet.name, sort_method) - elif attribute == "distance": - planet_query = sort_helper(planet_query, Planet.distance, sort_method) + elif 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 @@ -106,7 +106,7 @@ def read_planets(): "name": planet.name, "description": planet.description, "gravity": planet.gravity, - "distance": planet.distance} + "distance_from_earth": planet.distance_from_earth} ) return make_response(jsonify(planet_response), 200) @@ -119,7 +119,7 @@ def read_one_planet_by_id(planet_id): "name" : planet.name, "description" : planet.description, "gravity": planet.gravity, - "distance": planet.distance + "distance_from_earth": planet.distance_from_earth }, 200) @planets_bp.route("/", methods = ["PUT"]) @@ -132,7 +132,7 @@ def update_planet_by_id(planet_id): planet.name = request_body["name"] planet.description = request_body["description"] planet.gravity = request_body["gravity"] - planet.distance = request_body["distance"] + planet.distance_from_earth = request_body["distance_from_earth"] db.session.commit() diff --git a/app/tests/__init__.py b/app/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/tests/conftest.py b/app/tests/conftest.py deleted file mode 100644 index 4f0cd6bea..000000000 --- a/app/tests/conftest.py +++ /dev/null @@ -1,43 +0,0 @@ -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({"TEST": 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 saved_two_planets(app): - mars = Planet(name="Mars", - description="This is planet: Mars", - gravity=3.721, - distance=60.81) - - jupiter = Planet(name="Jupiter", - description="This is planet: Jupiter", - gravity=24.79, - distance=467.64) - - db.session.add_all([mars, jupiter]) - - db.session.commit() diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py deleted file mode 100644 index d39153928..000000000 --- a/app/tests/test_routes.py +++ /dev/null @@ -1,86 +0,0 @@ -def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): - response = client.get("/planets/1") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body["name"] == "Mars" - - -def test_get_planet_by_not_exist_id_return_404(client): - response = client.get("/planets/1") - response_body = response.get_json() - - assert response.status_code == 404 - -def test_get_planet_by_invalid_planet_id_return_400_bad_request_error(client, saved_two_planets): - response = client.get("/planets/hello") - response_body = response.get_json() - - assert response.status_code == 400 - assert response_body == {"message": "Planet_id hello is invalid"} - - -def test_get_all_planets_with_no_records_return_empty_array(client): - response = client.get("/planets") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body == [] - - -def test_get_all_planets_with_two_records_return_array_with_size_2(client, saved_two_planets): - response = client.get("/planets") - response_body = response.get_json() - - assert response.status_code == 200 - assert len(response_body) == 2 - assert response_body[0] == {"id": 1, - "name": "Mars", - "description": "This is planet: Mars", - "gravity": 3.721, - "distance": 60.81} - assert response_body[1] == {"id": 2, - "name": "Jupiter", - "description": "This is planet: Jupiter", - "gravity": 24.79, - "distance": 467.64} - - -def test_create_one_planet_return_201_successfully_created(client): - resposne = client.post("/planets", - json={"name": "Venus", - "description": "This is planet: Venus", - "gravity": 9.87, - "distance": 67.685}) - response_body = resposne.get_json() - - assert resposne.status_code == 201 - assert response_body == "Planet: Venus created successfully." - - -def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): - resposne = client.put("/planets/1", - json={"name": "New Planet", - "description": "This a New Planet", - "gravity": 20.0, - "distance": 55.99}) - response_body = resposne.get_json() - - assert resposne.status_code == 200 - assert response_body == "Planet: 1 has been updated successfully." - - -def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, saved_two_planets): - response = client.delete("planets/1") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body == "Planet: 1 has been deleted successfully." - -def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, saved_two_planets): - response = client.delete("planets/10") - response_body = response.get_json() - - assert response.status_code == 404 - assert response_body == {"message": "Planet_id 10 not found"} - From 5252ab24a6136a5c4f2b4296eee9fbc4fdce1de5 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Tue, 3 Jan 2023 20:01:58 -0800 Subject: [PATCH 36/62] Tests --- .../ad5e5f65a1aa_change_column_name.py | 30 +++++++ tests/__init__.py | 0 tests/conftest.py | 44 ++++++++++ tests/test_routes.py | 86 +++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 migrations/versions/ad5e5f65a1aa_change_column_name.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_routes.py diff --git a/migrations/versions/ad5e5f65a1aa_change_column_name.py b/migrations/versions/ad5e5f65a1aa_change_column_name.py new file mode 100644 index 000000000..2b37b87cb --- /dev/null +++ b/migrations/versions/ad5e5f65a1aa_change_column_name.py @@ -0,0 +1,30 @@ +"""change column name + +Revision ID: ad5e5f65a1aa +Revises: 3f86dac7d25d +Create Date: 2023-01-02 21:00:55.031373 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'ad5e5f65a1aa' +down_revision = '3f86dac7d25d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('planet', sa.Column('distance_from_earth', sa.Float(), nullable=True)) + op.drop_column('planet', 'distance') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('planet', sa.Column('distance', postgresql.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True)) + op.drop_column('planet', 'distance_from_earth') + # ### end Alembic commands ### diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..27272439c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,44 @@ +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({"TEST": 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 saved_two_planets(app): + mars = Planet(name="Mars", + description="This is planet: Mars", + gravity=3.721, + distance_from_earth=60.81) + + jupiter = Planet(name="Jupiter", + description="This is planet: Jupiter", + gravity=24.79, + distance_from_earth=467.64) + + db.session.add_all([mars, jupiter]) + db.session.commit() + db.session.refresh(mars, ["id"]) + db.session.refresh(jupiter, ["id"]) \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py new file mode 100644 index 000000000..1aed08222 --- /dev/null +++ b/tests/test_routes.py @@ -0,0 +1,86 @@ +def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): + response = client.get("/planets/1") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["name"] == "Mars" + + +def test_get_planet_by_not_exist_id_return_404(client): + response = client.get("/planets/1") + response_body = response.get_json() + + assert response.status_code == 404 + +def test_get_planet_by_invalid_planet_id_return_400_bad_request_error(client, saved_two_planets): + response = client.get("/planets/hello") + response_body = response.get_json() + + assert response.status_code == 400 + assert response_body == {"message": "Planet_id hello is invalid"} + + +def test_get_all_planets_with_no_records_return_empty_array(client): + response = client.get("/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == [] + + +def test_get_all_planets_with_two_records_return_array_with_size_2(client, saved_two_planets): + response = client.get("/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert len(response_body) == 2 + assert response_body[0] == {"id": 1, + "name": "Mars", + "description": "This is planet: Mars", + "gravity": 3.721, + "distance_from_earth": 60.81} + assert response_body[1] == {"id": 2, + "name": "Jupiter", + "description": "This is planet: Jupiter", + "gravity": 24.79, + "distance_from_earth": 467.64} + + +def test_create_one_planet_return_201_successfully_created(client): + resposne = client.post("/planets", + json={"name": "Venus", + "description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) + response_body = resposne.get_json() + + assert resposne.status_code == 201 + assert response_body == "Planet: Venus created successfully." + + +def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): + resposne = client.put("/planets/1", + json={"name": "New Planet", + "description": "This a New Planet", + "gravity": 20.0, + "distance_from_earth": 55.99}) + response_body = resposne.get_json() + + assert resposne.status_code == 200 + assert response_body == "Planet: 1 has been updated successfully." + + +def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, saved_two_planets): + response = client.delete("planets/1") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == "Planet: 1 has been deleted successfully." + +def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, saved_two_planets): + response = client.delete("planets/10") + response_body = response.get_json() + + assert response.status_code == 404 + assert response_body == {"message": "Planet_id 10 not found"} + From 0dc2698a804b6eedd340e9b70cb2ab3b78938d06 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Tue, 3 Jan 2023 21:00:03 -0800 Subject: [PATCH 37/62] refactor to_dict function and add models tests. --- app/models/planet.py | 12 ++- app/planet_routes.py | 19 +---- migrations/versions/3f86dac7d25d_.py | 34 --------- migrations/versions/4b0ace852662_.py | 28 ------- migrations/versions/69017a0f8abc_.py | 44 ----------- .../ad5e5f65a1aa_change_column_name.py | 30 -------- ...anet_class_created.py => db3fb05c8c12_.py} | 15 ++-- migrations/versions/f8a4e2800d01_.py | 34 --------- tests/test_models.py | 75 +++++++++++++++++++ tests/test_routes.py | 34 +++++---- 10 files changed, 118 insertions(+), 207 deletions(-) delete mode 100644 migrations/versions/3f86dac7d25d_.py delete mode 100644 migrations/versions/4b0ace852662_.py delete mode 100644 migrations/versions/69017a0f8abc_.py delete mode 100644 migrations/versions/ad5e5f65a1aa_change_column_name.py rename migrations/versions/{934749f17def_new_planet_class_created.py => db3fb05c8c12_.py} (68%) delete mode 100644 migrations/versions/f8a4e2800d01_.py create mode 100644 tests/test_models.py diff --git a/app/models/planet.py b/app/models/planet.py index ca3daef35..b5b584fb4 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -7,4 +7,14 @@ class Planet(db.Model): name = db.Column(db.String) description = db.Column(db.String) gravity = db.Column(db.Float) - distance_from_earth = db.Column(db.Float) \ No newline at end of file + distance_from_earth = db.Column(db.Float) + + 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 + + return planet_as_dict \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py index 26158b091..c77196222 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -101,26 +101,15 @@ def read_planets(): planet_response = [] for planet in planets: - planet_response.append( - {"id": planet.id, - "name": planet.name, - "description": planet.description, - "gravity": planet.gravity, - "distance_from_earth": planet.distance_from_earth} - ) + 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("/", methods = ["GET"]) def read_one_planet_by_id(planet_id): planet = validate_planet_id(planet_id) - - return ({ - "id" : planet.id, - "name" : planet.name, - "description" : planet.description, - "gravity": planet.gravity, - "distance_from_earth": planet.distance_from_earth - }, 200) + #use to_dict function to make code more readable + return (planet.to_dict(), 200) @planets_bp.route("/", methods = ["PUT"]) def update_planet_by_id(planet_id): diff --git a/migrations/versions/3f86dac7d25d_.py b/migrations/versions/3f86dac7d25d_.py deleted file mode 100644 index 66ba555d2..000000000 --- a/migrations/versions/3f86dac7d25d_.py +++ /dev/null @@ -1,34 +0,0 @@ -"""empty message - -Revision ID: 3f86dac7d25d -Revises: f8a4e2800d01 -Create Date: 2022-12-21 18:48:03.587168 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '3f86dac7d25d' -down_revision = 'f8a4e2800d01' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('planet', 'gravity', - existing_type=sa.INTEGER(), - type_=sa.Float(), - existing_nullable=True) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('planet', 'gravity', - existing_type=sa.Float(), - type_=sa.INTEGER(), - existing_nullable=True) - # ### end Alembic commands ### diff --git a/migrations/versions/4b0ace852662_.py b/migrations/versions/4b0ace852662_.py deleted file mode 100644 index 15e02b60e..000000000 --- a/migrations/versions/4b0ace852662_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 4b0ace852662 -Revises: 934749f17def -Create Date: 2022-12-21 10:24:56.300990 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '4b0ace852662' -down_revision = '934749f17def' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('planets', sa.Column('distance', sa.String(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('planets', 'distance') - # ### end Alembic commands ### diff --git a/migrations/versions/69017a0f8abc_.py b/migrations/versions/69017a0f8abc_.py deleted file mode 100644 index 9a34addae..000000000 --- a/migrations/versions/69017a0f8abc_.py +++ /dev/null @@ -1,44 +0,0 @@ -"""empty message - -Revision ID: 69017a0f8abc -Revises: 4b0ace852662 -Create Date: 2022-12-21 14:53:17.119811 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '69017a0f8abc' -down_revision = '4b0ace852662' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('planet', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('description', sa.String(), nullable=True), - sa.Column('gravity', sa.String(), nullable=True), - sa.Column('distance', sa.String(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.drop_table('planets') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('planets', - sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), - sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), - sa.Column('description', sa.VARCHAR(), autoincrement=False, nullable=True), - sa.Column('gravity', sa.VARCHAR(), autoincrement=False, nullable=True), - sa.Column('distance', sa.VARCHAR(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint('id', name='planets_pkey') - ) - op.drop_table('planet') - # ### end Alembic commands ### diff --git a/migrations/versions/ad5e5f65a1aa_change_column_name.py b/migrations/versions/ad5e5f65a1aa_change_column_name.py deleted file mode 100644 index 2b37b87cb..000000000 --- a/migrations/versions/ad5e5f65a1aa_change_column_name.py +++ /dev/null @@ -1,30 +0,0 @@ -"""change column name - -Revision ID: ad5e5f65a1aa -Revises: 3f86dac7d25d -Create Date: 2023-01-02 21:00:55.031373 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = 'ad5e5f65a1aa' -down_revision = '3f86dac7d25d' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('planet', sa.Column('distance_from_earth', sa.Float(), nullable=True)) - op.drop_column('planet', 'distance') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('planet', sa.Column('distance', postgresql.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True)) - op.drop_column('planet', 'distance_from_earth') - # ### end Alembic commands ### diff --git a/migrations/versions/934749f17def_new_planet_class_created.py b/migrations/versions/db3fb05c8c12_.py similarity index 68% rename from migrations/versions/934749f17def_new_planet_class_created.py rename to migrations/versions/db3fb05c8c12_.py index c2a0e1f54..b7247a203 100644 --- a/migrations/versions/934749f17def_new_planet_class_created.py +++ b/migrations/versions/db3fb05c8c12_.py @@ -1,8 +1,8 @@ -"""new planet class created +"""empty message -Revision ID: 934749f17def +Revision ID: db3fb05c8c12 Revises: -Create Date: 2022-12-20 11:08:45.015241 +Create Date: 2023-01-03 20:17:26.357614 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '934749f17def' +revision = 'db3fb05c8c12' down_revision = None branch_labels = None depends_on = None @@ -18,11 +18,12 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('planets', + op.create_table('planet', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('name', sa.String(), nullable=True), sa.Column('description', sa.String(), nullable=True), - sa.Column('gravity', sa.String(), nullable=True), + sa.Column('gravity', sa.Float(), nullable=True), + sa.Column('distance_from_earth', sa.Float(), nullable=True), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### @@ -30,5 +31,5 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('planets') + op.drop_table('planet') # ### end Alembic commands ### diff --git a/migrations/versions/f8a4e2800d01_.py b/migrations/versions/f8a4e2800d01_.py deleted file mode 100644 index 6b9f52160..000000000 --- a/migrations/versions/f8a4e2800d01_.py +++ /dev/null @@ -1,34 +0,0 @@ -"""empty message - -Revision ID: f8a4e2800d01 -Revises: 69017a0f8abc -Create Date: 2022-12-21 18:46:20.450773 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = 'f8a4e2800d01' -down_revision = '69017a0f8abc' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('planet', 'gravity', - existing_type=postgresql.DOUBLE_PRECISION(precision=53), - type_=sa.Integer(), - existing_nullable=True) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('planet', 'gravity', - existing_type=sa.Integer(), - type_=postgresql.DOUBLE_PRECISION(precision=53), - existing_nullable=True) - # ### end Alembic commands ### diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 000000000..185748cb7 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,75 @@ +from app.models.planet import Planet + +def test_to_dict_no_missing_data(): + # Arrange + test_data = Planet(id = 1, + name="Mars", + description="This is planet: Mars", + gravity=3.721, + distance_from_earth=60.81) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 5 + assert result["id"] == 1 + assert result["name"] == "Mars" + assert result["description"] == "This is planet: Mars" + assert result["gravity"] == 3.721 + assert result["distance_from_earth"] == 60.81 + +def test_to_dict_missing_id(): + # Arrange + test_data = Planet(name="Mars", + description="This is planet: Mars", + gravity=3.721, + distance_from_earth=60.81) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 5 + assert result["id"] is None + assert result["name"] == "Mars" + assert result["description"] == "This is planet: Mars" + assert result["gravity"] == 3.721 + assert result["distance_from_earth"] == 60.81 + +def test_to_dict_missing_title(): + # Arrange + test_data = Planet(id=1, + description="This is planet: Mars", + gravity=3.721, + distance_from_earth=60.81) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 5 + assert result["id"] == 1 + assert result["name"] is None + assert result["description"] == "This is planet: Mars" + assert result["gravity"] == 3.721 + assert result["distance_from_earth"] == 60.81 + +def test_to_dict_missing_description(): + # Arrange + test_data = Planet(id = 1, + name="Mars", + description="This is planet: Mars", + gravity=3.721, + distance_from_earth=60.81) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 5 + assert result["id"] == 1 + assert result["name"] == "Mars" + assert result["description"] is None + assert result["gravity"] == 3.721 + assert result["distance_from_earth"] == 60.81 \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index 1aed08222..cefa0561d 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -34,27 +34,26 @@ def test_get_all_planets_with_two_records_return_array_with_size_2(client, saved assert response.status_code == 200 assert len(response_body) == 2 - assert response_body[0] == {"id": 1, - "name": "Mars", - "description": "This is planet: Mars", - "gravity": 3.721, - "distance_from_earth": 60.81} - assert response_body[1] == {"id": 2, - "name": "Jupiter", - "description": "This is planet: Jupiter", - "gravity": 24.79, - "distance_from_earth": 467.64} - + assert response_body[0]["id"] == 1 + assert response_body[0]["name"]== "Mars" + assert response_body[0]["description"] =="This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"]== "Jupiter" + assert response_body[1]["description"] == "This is planet: Jupiter" + assert response_body[1]["gravity"] == 24.79 + assert response_body[1]["distance_from_earth"] == 467.64 def test_create_one_planet_return_201_successfully_created(client): - resposne = client.post("/planets", + response = client.post("/planets", json={"name": "Venus", "description": "This is planet: Venus", "gravity": 9.87, "distance_from_earth": 67.685}) - response_body = resposne.get_json() + response_body = response.get_json() - assert resposne.status_code == 201 + assert response.status_code == 201 assert response_body == "Planet: Venus created successfully." @@ -68,6 +67,11 @@ def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, sa assert resposne.status_code == 200 assert response_body == "Planet: 1 has been updated successfully." + assert response_body[0]["id"] == 1 + assert response_body[0]["name"]== "New Planet" + assert response_body[0]["description"] =="This a New Planet" + assert response_body[0]["gravity"] == 20.0 + assert response_body[0]["distance_from_earth"] == 55.99 def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, saved_two_planets): @@ -84,3 +88,5 @@ def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, save assert response.status_code == 404 assert response_body == {"message": "Planet_id 10 not found"} + + From 4b052e2812307d23ed30a11df8924e8f2042dbfe Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Tue, 3 Jan 2023 22:00:48 -0800 Subject: [PATCH 38/62] add edge cases for create routes, and refactor from_dict function --- app/models/planet.py | 10 ++++++- app/planet_routes.py | 9 ++---- tests/test_models.py | 67 +++++++++++++++++++++++++++++++++++++++++++- tests/test_routes.py | 42 +++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 9 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index b5b584fb4..3159fd491 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -17,4 +17,12 @@ def to_dict(self): planet_as_dict["gravity"] = self.gravity planet_as_dict["distance_from_earth"] = self.distance_from_earth - return planet_as_dict \ No newline at end of file + return planet_as_dict + + @classmethod + def from_dict(cls, planet_data): + new_planet = Planet(title=planet_data["name"], + description=planet_data["description"], + gravity = planet_data['gravity'], + distance_from_earth = planet_data['distance_from_earth']) + return new_planet \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py index c77196222..85f5501c5 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -41,13 +41,8 @@ def sort_helper(planet_query, atr = None, sort_method = "asc"): def create_planet(): request_body = request.get_json() validate_request_body(request_body) - - new_planet = Planet( - name = request_body["name"], - description = request_body["description"], - gravity = request_body["gravity"], - distance_from_earth = request_body["distance_from_earth"] - ) + #use from_dict function to simplfied code + new_planet = Planet.from_dict(request_body) db.session.add(new_planet) db.session.commit() diff --git a/tests/test_models.py b/tests/test_models.py index 185748cb7..48a2d6af6 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,4 +1,5 @@ from app.models.planet import Planet +import pytest def test_to_dict_no_missing_data(): # Arrange @@ -72,4 +73,68 @@ def test_to_dict_missing_description(): assert result["name"] == "Mars" assert result["description"] is None assert result["gravity"] == 3.721 - assert result["distance_from_earth"] == 60.81 \ No newline at end of file + assert result["distance_from_earth"] == 60.81 + + +def test_from_dict_returns_book(): + # Arrange + planet_data = { + "name": "Mars", + "description": "This is planet: Mars", + "gravity" : 3.721, + "distance_from_earth" : 60.81 + + } + + # Act + new_planet = Planet.from_dict(planet_data) + + # Assert + assert new_planet.name == "Mars" + assert new_planet.description == "This is planet: Mars" + assert new_planet.gravity == 3.721 + assert new_planet.distance_from_earth == 60.81 + +def test_from_dict_with_no_name(): + # Arrange + planet_data = { + "description": "This is planet: Mars!", + "gravity" : 3.721, + "distance_from_earth" : 60.81 + } + + # Act & Assert + with pytest.raises(KeyError, match = 'title'): + new_planet = Planet.from_dict(planet_data) + +def test_from_dict_with_no_description(): + # Arrange + planet_data = { + "name": "Mars", + "gravity" : 3.721, + "distance_from_earth" : 60.81 + } + + # Act & Assert + with pytest.raises(KeyError, match = 'description'): + new_planet = Planet.from_dict(planet_data) + +def test_from_dict_with_extra_keys(): + # Arrange + planet_data = { + "extra": "some stuff", + "name": "Mars", + "description": "This is planet: Mars", + "gravity" : 3.721, + "distance_from_earth" : 60.81, + "another": "last value" + } + + # Act + new_planet = Planet.from_dict(planet_data) + + # Assert + assert new_planet.name == "Mars" + assert new_planet.description == "This is planet: Mars" + assert new_planet.gravity == 3.721 + assert new_planet.distance_from_earth == 60.81 \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index cefa0561d..f3f36afd6 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,3 +1,5 @@ +import pytest + def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): response = client.get("/planets/1") response_body = response.get_json() @@ -56,6 +58,46 @@ def test_create_one_planet_return_201_successfully_created(client): assert response.status_code == 201 assert response_body == "Planet: Venus created successfully." +#####edge cases for create one planet +def test_create_one_planet_no_name(client): + test_data = {"description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685} + + with pytest.raises(KeyError, match='name'): + response = client.post("/planets", json=test_data) + +def test_create_one_planet_no_description(client): + test_data = {"name": "Mars", + "gravity": 9.87, + "distance_from_earth": 67.685} + + with pytest.raises(KeyError, match='description'): + response = client.post("/planets", json=test_data) + +def test_create_one_planet_no_gravity(client): + test_data = {"name": "Mars", + "description": "This is planet: Venus", + "distance_from_earth": 67.685} + + with pytest.raises(KeyError, match='gravity'): + response = client.post("/planets", json=test_data) + +def test_create_one_book_with_extra_keys(client, two_saved_books): + test_data = { + "extra": "some stuff", + "name": "Venus", + "description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685, + "another": "last value" + } + + response = client.post("/planets", json=test_data) + response_body = response.get_json() + + assert response.status_code == 201 + assert response_body == "Planet Mars successfully created" def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): resposne = client.put("/planets/1", From 60bd0488bddaa147731d02b02acfe29d28d9c1ac Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Wed, 4 Jan 2023 00:22:29 -0800 Subject: [PATCH 39/62] bug free tests --- app/models/planet.py | 2 +- app/planet_routes.py | 2 +- tests/test_models.py | 7 +++---- tests/test_routes.py | 44 +++++++++++++++++++++----------------------- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 3159fd491..de895886c 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -21,7 +21,7 @@ def to_dict(self): @classmethod def from_dict(cls, planet_data): - new_planet = Planet(title=planet_data["name"], + new_planet = Planet(name=planet_data["name"], description=planet_data["description"], gravity = planet_data['gravity'], distance_from_earth = planet_data['distance_from_earth']) diff --git a/app/planet_routes.py b/app/planet_routes.py index 85f5501c5..26d28695b 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -18,9 +18,9 @@ def validate_planet_id(planet_id): return planet 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"): diff --git a/tests/test_models.py b/tests/test_models.py index 48a2d6af6..83715bf51 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -38,7 +38,7 @@ def test_to_dict_missing_id(): assert result["gravity"] == 3.721 assert result["distance_from_earth"] == 60.81 -def test_to_dict_missing_title(): +def test_to_dict_missing_name(): # Arrange test_data = Planet(id=1, description="This is planet: Mars", @@ -60,7 +60,6 @@ def test_to_dict_missing_description(): # Arrange test_data = Planet(id = 1, name="Mars", - description="This is planet: Mars", gravity=3.721, distance_from_earth=60.81) @@ -79,11 +78,11 @@ def test_to_dict_missing_description(): def test_from_dict_returns_book(): # Arrange planet_data = { + "id":1, "name": "Mars", "description": "This is planet: Mars", "gravity" : 3.721, "distance_from_earth" : 60.81 - } # Act @@ -104,7 +103,7 @@ def test_from_dict_with_no_name(): } # Act & Assert - with pytest.raises(KeyError, match = 'title'): + with pytest.raises(KeyError, match = 'name'): new_planet = Planet.from_dict(planet_data) def test_from_dict_with_no_description(): diff --git a/tests/test_routes.py b/tests/test_routes.py index f3f36afd6..4af0927e5 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -60,30 +60,33 @@ def test_create_one_planet_return_201_successfully_created(client): #####edge cases for create one planet def test_create_one_planet_no_name(client): - test_data = {"description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685} + + response = client.post("/planets", + json={"description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) + + assert response.status_code == 400 - with pytest.raises(KeyError, match='name'): - response = client.post("/planets", json=test_data) def test_create_one_planet_no_description(client): - test_data = {"name": "Mars", + response = client.post("/planets", + json={"name": "Mars", "gravity": 9.87, - "distance_from_earth": 67.685} + "distance_from_earth": 67.685}) - with pytest.raises(KeyError, match='description'): - response = client.post("/planets", json=test_data) + assert response.status_code == 400 -def test_create_one_planet_no_gravity(client): - test_data = {"name": "Mars", - "description": "This is planet: Venus", - "distance_from_earth": 67.685} +# def test_create_one_planet_no_gravity(client): +# test_data = {"name": "Mars", +# "description": "This is planet: Venus", +# "distance_from_earth": 67.685 +# } - with pytest.raises(KeyError, match='gravity'): - response = client.post("/planets", json=test_data) +# with pytest.raises(KeyError, match='gravity'): +# response = client.post("/planets", json=test_data) -def test_create_one_book_with_extra_keys(client, two_saved_books): +def test_create_one_planet_with_extra_keys(client, saved_two_planets): test_data = { "extra": "some stuff", "name": "Venus", @@ -97,7 +100,7 @@ def test_create_one_book_with_extra_keys(client, two_saved_books): response_body = response.get_json() assert response.status_code == 201 - assert response_body == "Planet Mars successfully created" + assert response_body == "Planet: Venus created successfully." def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): resposne = client.put("/planets/1", @@ -108,12 +111,7 @@ def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, sa response_body = resposne.get_json() assert resposne.status_code == 200 - assert response_body == "Planet: 1 has been updated successfully." - assert response_body[0]["id"] == 1 - assert response_body[0]["name"]== "New Planet" - assert response_body[0]["description"] =="This a New Planet" - assert response_body[0]["gravity"] == 20.0 - assert response_body[0]["distance_from_earth"] == 55.99 + assert response_body == "Planet: 1 has been updated successfully." def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, saved_two_planets): From 1ae8399a87af657ee78b7266bb5150014e10b723 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 4 Jan 2023 10:51:21 -0800 Subject: [PATCH 40/62] Refactored validate_model --- app/planet_routes.py | 20 +++++++++---------- tests/test_routes.py | 47 +++++++++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 26d28695b..a3b7481b4 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -5,17 +5,17 @@ planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") #---------------------------------------------- Helper Functions---------------------------------------------- -def validate_planet_id(planet_id): +def validate_model(cls, model_id): try: - planet_id = int(planet_id) + model_id = int(model_id) except: - abort(make_response({"message":f"Planet_id {planet_id} is invalid"}, 400)) + abort(make_response({"message":f"{cls.__name__} {model_id} is invalid"}, 400)) - planet = Planet.query.get(planet_id) + model = cls.query.get(model_id) - if not planet: - abort(make_response({"message":f"Planet_id {planet_id} not found"}, 404)) - return planet + if not model: + abort(make_response({"message":f"{cls.__name__} {model_id} not found"}, 404)) + return model def validate_request_body(request_body): @@ -102,13 +102,13 @@ def read_planets(): @planets_bp.route("/", methods = ["GET"]) def read_one_planet_by_id(planet_id): - planet = validate_planet_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("/", methods = ["PUT"]) def update_planet_by_id(planet_id): - planet = validate_planet_id(planet_id) + planet = validate_model(Planet, planet_id) request_body = request.get_json() validate_request_body(request_body) @@ -124,7 +124,7 @@ def update_planet_by_id(planet_id): @planets_bp.route("/", methods = ["DELETE"]) def delete_planet_by_id(planet_id): - planet = validate_planet_id(planet_id) + planet = validate_model(Planet, planet_id) db.session.delete(planet) db.session.commit() diff --git a/tests/test_routes.py b/tests/test_routes.py index 4af0927e5..c4351e316 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,4 +1,7 @@ import pytest +from werkzeug.exceptions import HTTPException +from app.planet_routes import validate_model +from app.models.planet import Planet def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): response = client.get("/planets/1") @@ -19,7 +22,7 @@ def test_get_planet_by_invalid_planet_id_return_400_bad_request_error(client, sa response_body = response.get_json() assert response.status_code == 400 - assert response_body == {"message": "Planet_id hello is invalid"} + assert response_body == {"message": "Planet hello is invalid"} def test_get_all_planets_with_no_records_return_empty_array(client): @@ -77,14 +80,13 @@ def test_create_one_planet_no_description(client): assert response.status_code == 400 -# def test_create_one_planet_no_gravity(client): -# test_data = {"name": "Mars", -# "description": "This is planet: Venus", -# "distance_from_earth": 67.685 -# } - -# with pytest.raises(KeyError, match='gravity'): -# response = client.post("/planets", json=test_data) +def test_create_one_planet_no_gravity(client): + test_data = {"name": "Mars", + "description": "This is planet: Venus", + "distance_from_earth": 67.685 + } + response = client.post("/planets", json = test_data) + assert response.status_code == 400 def test_create_one_planet_with_extra_keys(client, saved_two_planets): test_data = { @@ -126,7 +128,28 @@ def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, save response_body = response.get_json() assert response.status_code == 404 - assert response_body == {"message": "Planet_id 10 not found"} - - + assert response_body == {"message": "Planet 10 not found"} + +def test_validate_model(saved_two_planets): + result_planet = validate_model(Planet, 1) + + assert result_planet.id == 1 + assert result_planet.name == "Mars" + assert result_planet.description == "This is planet: Mars" + assert result_planet.gravity == 3.721 + assert result_planet.distance_from_earth == 60.81 + +def test_validate_model_missing_record(saved_two_planets): + # Act & Assert + # Calling `validate_model` without being invoked by a route will + # cause an `HTTPException` when an `abort` statement is reached + with pytest.raises(HTTPException): + result_planet = validate_model(Planet, "3") + +def test_validate_model_invalid_id(saved_two_planets): + # Act & Assert + # Calling `validate_model` without being invoked by a route will + # cause an `HTTPException` when an `abort` statement is reached + with pytest.raises(HTTPException): + result_planet = validate_model(Planet, "invalid_id") From 53d515c5206fbf2a4f823daa6106338cf04f05ee Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 4 Jan 2023 13:47:05 -0800 Subject: [PATCH 41/62] More tests added --- tests/test_routes.py | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/test_routes.py b/tests/test_routes.py index c4351e316..105f775bf 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -62,7 +62,7 @@ def test_create_one_planet_return_201_successfully_created(client): assert response_body == "Planet: Venus created successfully." #####edge cases for create one planet -def test_create_one_planet_no_name(client): +def test_create_one_planet_no_name_return_400(client): response = client.post("/planets", json={"description": "This is planet: Venus", @@ -72,7 +72,7 @@ def test_create_one_planet_no_name(client): assert response.status_code == 400 -def test_create_one_planet_no_description(client): +def test_create_one_planet_no_description_return_400(client): response = client.post("/planets", json={"name": "Mars", "gravity": 9.87, @@ -80,7 +80,7 @@ def test_create_one_planet_no_description(client): assert response.status_code == 400 -def test_create_one_planet_no_gravity(client): +def test_create_one_planet_no_gravity_return_400(client): test_data = {"name": "Mars", "description": "This is planet: Venus", "distance_from_earth": 67.685 @@ -88,7 +88,7 @@ def test_create_one_planet_no_gravity(client): response = client.post("/planets", json = test_data) assert response.status_code == 400 -def test_create_one_planet_with_extra_keys(client, saved_two_planets): +def test_create_one_planet_with_extra_keys_return_201(client, saved_two_planets): test_data = { "extra": "some stuff", "name": "Venus", @@ -115,6 +115,27 @@ def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, sa assert resposne.status_code == 200 assert response_body == "Planet: 1 has been updated successfully." +def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_planets): + resposne = client.put("/planets/9", + json={"name": "New Planet", + "description": "This a New Planet", + "gravity": 20.0, + "distance_from_earth": 55.99}) + response_body = resposne.get_json() + + assert resposne.status_code == 404 + assert response_body == {"message":"Planet 9 not found"} + +def test_put_invalid_planet__id_return_400_invalid_error(client, saved_two_planets): + resposne = client.put("/planets/invalid_id", + json={"name": "New Planet", + "description": "This a New Planet", + "gravity": 20.0, + "distance_from_earth": 55.99}) + response_body = resposne.get_json() + + assert resposne.status_code == 400 + assert response_body == {"message":"Planet invalid_id is invalid"} def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, saved_two_planets): response = client.delete("planets/1") @@ -130,6 +151,13 @@ def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, save assert response.status_code == 404 assert response_body == {"message": "Planet 10 not found"} +def test_delete_planet_with_invalid_id_return_400_invalid_error(client, saved_two_planets): + response = client.delete("planets/invalid_id") + response_body = response.get_json() + + assert response.status_code == 400 + assert response_body == {"message": "Planet invalid_id is invalid"} + def test_validate_model(saved_two_planets): result_planet = validate_model(Planet, 1) @@ -140,16 +168,12 @@ def test_validate_model(saved_two_planets): assert result_planet.distance_from_earth == 60.81 def test_validate_model_missing_record(saved_two_planets): - # Act & Assert # Calling `validate_model` without being invoked by a route will # cause an `HTTPException` when an `abort` statement is reached with pytest.raises(HTTPException): result_planet = validate_model(Planet, "3") def test_validate_model_invalid_id(saved_two_planets): - # Act & Assert - # Calling `validate_model` without being invoked by a route will - # cause an `HTTPException` when an `abort` statement is reached with pytest.raises(HTTPException): result_planet = validate_model(Planet, "invalid_id") From 0adc24febfca065ca447cb6c9d6676059e5552fa Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Wed, 4 Jan 2023 21:30:01 -0800 Subject: [PATCH 42/62] Wave 8 done. --- app/__init__.py | 5 +- app/models/moon.py | 7 ++ app/models/planet.py | 1 + app/moon_routes.py | 71 +++++++++++++++++++ .../{db3fb05c8c12_.py => ad9638fcbcc4_.py} | 14 +++- tests/test_routes.py | 7 ++ 6 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 app/models/moon.py create mode 100644 app/moon_routes.py rename migrations/versions/{db3fb05c8c12_.py => ad9638fcbcc4_.py} (65%) diff --git a/app/__init__.py b/app/__init__.py index 71658c9b1..77e712a51 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,8 +11,6 @@ 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['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically @@ -28,8 +26,11 @@ def create_app(test_config=None): migrate.init_app(app, db) from app.models.planet import Planet + from app.models.moon import Moon from .planet_routes import planets_bp + from .moon_routes import moon_bp app.register_blueprint(planets_bp) + app.register_blueprint(moon_bp) return app diff --git a/app/models/moon.py b/app/models/moon.py new file mode 100644 index 000000000..cdde9532b --- /dev/null +++ b/app/models/moon.py @@ -0,0 +1,7 @@ +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") \ No newline at end of file diff --git a/app/models/planet.py b/app/models/planet.py index de895886c..fa2b94696 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -8,6 +8,7 @@ class Planet(db.Model): 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 = {} diff --git a/app/moon_routes.py b/app/moon_routes.py new file mode 100644 index 000000000..aaeec2fe4 --- /dev/null +++ b/app/moon_routes.py @@ -0,0 +1,71 @@ +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.planet_routes import validate_model + +moon_bp = Blueprint("moon_bp", __name__, url_prefix="/moon") + +@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("", methods=["GET"]) +def read_moon_by_id(moon_id): + moon_response = validate_model(Moon, moon_id) + + result_dict = { + "id" : moon_response.id, + "name" : moon_response.name, + } + + return make_response(result_dict, 200) + +@moon_bp.route("/all", 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("//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(f"Moon {new_moon.name} successfully created to planet {planet_id}"), 201) + +@moon_bp.route("//moons", methods=["GET"]) +def get_book_by_author_id(planet_id): + planet = validate_model(Planet, planet_id) + moon_response = [] + + for moon in planet.moons: + moon_response.append( + { + "id" : moon.id, + "name" : moon.name, + } + ) + return make_response(jsonify(moon_response),200) + diff --git a/migrations/versions/db3fb05c8c12_.py b/migrations/versions/ad9638fcbcc4_.py similarity index 65% rename from migrations/versions/db3fb05c8c12_.py rename to migrations/versions/ad9638fcbcc4_.py index b7247a203..4c7140054 100644 --- a/migrations/versions/db3fb05c8c12_.py +++ b/migrations/versions/ad9638fcbcc4_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: db3fb05c8c12 +Revision ID: ad9638fcbcc4 Revises: -Create Date: 2023-01-03 20:17:26.357614 +Create Date: 2023-01-04 20:52:20.304125 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'db3fb05c8c12' +revision = 'ad9638fcbcc4' down_revision = None branch_labels = None depends_on = None @@ -26,10 +26,18 @@ def upgrade(): sa.Column('distance_from_earth', sa.Float(), nullable=True), sa.PrimaryKeyConstraint('id') ) + op.create_table('moon', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('planet_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['planet_id'], ['planet.id'], ), + sa.PrimaryKeyConstraint('id') + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('moon') op.drop_table('planet') # ### end Alembic commands ### diff --git a/tests/test_routes.py b/tests/test_routes.py index 105f775bf..eca9b6c5e 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -116,6 +116,13 @@ def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, sa assert response_body == "Planet: 1 has been updated successfully." def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_planets): + # with pytest.raises(HTTPException): + # resposne = client.put("/planets/9", + # json={"name": "New Planet", + # "description": "This a New Planet", + # "gravity": 20.0, + # "distance_from_earth": 55.99}) + resposne = client.put("/planets/9", json={"name": "New Planet", "description": "This a New Planet", From 78080267b80a322a1bf36727b3dd548a173f687e Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 5 Jan 2023 10:35:45 -0800 Subject: [PATCH 43/62] modified to_dict --- app/models/moon.py | 11 ++++++++++- app/models/planet.py | 7 ++++++- app/moon_routes.py | 23 +++++++++-------------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/models/moon.py b/app/models/moon.py index cdde9532b..28ab7fbf4 100644 --- a/app/models/moon.py +++ b/app/models/moon.py @@ -4,4 +4,13 @@ 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") \ No newline at end of file + planet = db.relationship("Planet", back_populates = "moons") + + def to_dict(self): + return{ + "id" : self.id, + "name" : self.name, + "planet_id" : self.new_planet.id, + "planet" : self.new_planet + } + \ No newline at end of file diff --git a/app/models/planet.py b/app/models/planet.py index fa2b94696..ce0f8cd1c 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -16,7 +16,12 @@ def to_dict(self): 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 + 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_names return planet_as_dict diff --git a/app/moon_routes.py b/app/moon_routes.py index aaeec2fe4..8af8fb51a 100644 --- a/app/moon_routes.py +++ b/app/moon_routes.py @@ -46,9 +46,11 @@ 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 ) + new_moon = Moon( + name = request_body["name"], + planet_id = new_planet.id, + planet = new_planet + ) db.session.add(new_moon) db.session.commit() @@ -56,16 +58,9 @@ def create_moon_to_planet_by_planet_id(planet_id): return make_response(jsonify(f"Moon {new_moon.name} successfully created to planet {planet_id}"), 201) @moon_bp.route("//moons", methods=["GET"]) -def get_book_by_author_id(planet_id): +def get_moons_by_planet_id(planet_id): planet = validate_model(Planet, planet_id) - moon_response = [] - - for moon in planet.moons: - moon_response.append( - { - "id" : moon.id, - "name" : moon.name, - } - ) - return make_response(jsonify(moon_response),200) + + response = planet.to_dict() + return make_response(jsonify(response),200) From 86a3bdf09345bf97d58db303b156a6ff0f37bf92 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Thu, 5 Jan 2023 18:32:24 -0800 Subject: [PATCH 44/62] added 10 tests to test moon route --- app/moon_routes.py | 10 +-- migrations/versions/ad9638fcbcc4_.py | 43 ------------- tests/conftest.py | 28 ++++++++- tests/test_models.py | 11 ++-- tests/test_routes.py | 93 ++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 54 deletions(-) delete mode 100644 migrations/versions/ad9638fcbcc4_.py diff --git a/app/moon_routes.py b/app/moon_routes.py index 8af8fb51a..bb8d23b42 100644 --- a/app/moon_routes.py +++ b/app/moon_routes.py @@ -4,7 +4,9 @@ from app.models.planet import Planet from app.planet_routes import validate_model -moon_bp = Blueprint("moon_bp", __name__, url_prefix="/moon") +moon_bp = Blueprint("moons_bp", __name__, url_prefix="/moons") + + @moon_bp.route("", methods=["POST"]) def create_new_moon(): @@ -14,10 +16,10 @@ def create_new_moon(): db.session.add(new_moon) db.session.commit() - return make_response(jsonify(f"Moon {new_moon.name} successfully created"), 201) + return make_response(jsonify(f"Moon {new_moon.name} successfully created."), 201) -@moon_bp.route("", methods=["GET"]) +@moon_bp.route("/", methods=["GET"]) def read_moon_by_id(moon_id): moon_response = validate_model(Moon, moon_id) @@ -26,7 +28,7 @@ def read_moon_by_id(moon_id): "name" : moon_response.name, } - return make_response(result_dict, 200) + return make_response(jsonify(result_dict), 200) @moon_bp.route("/all", methods=["GET"]) def read_all_moons(): diff --git a/migrations/versions/ad9638fcbcc4_.py b/migrations/versions/ad9638fcbcc4_.py deleted file mode 100644 index 4c7140054..000000000 --- a/migrations/versions/ad9638fcbcc4_.py +++ /dev/null @@ -1,43 +0,0 @@ -"""empty message - -Revision ID: ad9638fcbcc4 -Revises: -Create Date: 2023-01-04 20:52:20.304125 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'ad9638fcbcc4' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('planet', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('description', sa.String(), nullable=True), - sa.Column('gravity', sa.Float(), nullable=True), - sa.Column('distance_from_earth', sa.Float(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('moon', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('planet_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['planet_id'], ['planet.id'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('moon') - op.drop_table('planet') - # ### end Alembic commands ### diff --git a/tests/conftest.py b/tests/conftest.py index 27272439c..f07260daa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ from app import db from flask.signals import request_finished from app.models.planet import Planet +from app.models.moon import Moon @pytest.fixture @@ -31,14 +32,35 @@ def saved_two_planets(app): mars = Planet(name="Mars", description="This is planet: Mars", gravity=3.721, - distance_from_earth=60.81) + distance_from_earth=60.81, + moons = [] + ) jupiter = Planet(name="Jupiter", description="This is planet: Jupiter", gravity=24.79, - distance_from_earth=467.64) + distance_from_earth=467.64, + moons = [], + ) db.session.add_all([mars, jupiter]) db.session.commit() db.session.refresh(mars, ["id"]) - db.session.refresh(jupiter, ["id"]) \ No newline at end of file + db.session.refresh(jupiter, ["id"]) + + +@pytest.fixture +def saved_two_moons(app): + moon1 = Moon(name="Moon1", + planet_id=1, + planet="Mars") + + moon2 = Moon(name="Moon2", + planet_id=1, + planet="Mars") + + + db.session.add_all([moon1, moon2]) + db.session.commit() + db.session.refresh(moon1, ["id"]) + db.session.refresh(moon2, ["id"]) \ No newline at end of file diff --git a/tests/test_models.py b/tests/test_models.py index 83715bf51..d97cc47d0 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -11,14 +11,16 @@ def test_to_dict_no_missing_data(): # Act result = test_data.to_dict() + print(result) # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] == 1 assert result["name"] == "Mars" assert result["description"] == "This is planet: Mars" assert result["gravity"] == 3.721 assert result["distance_from_earth"] == 60.81 + assert result["moons"] == [] def test_to_dict_missing_id(): # Arrange @@ -31,12 +33,13 @@ def test_to_dict_missing_id(): result = test_data.to_dict() # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] is None assert result["name"] == "Mars" assert result["description"] == "This is planet: Mars" assert result["gravity"] == 3.721 assert result["distance_from_earth"] == 60.81 + assert result["moons"] == [] def test_to_dict_missing_name(): # Arrange @@ -49,7 +52,7 @@ def test_to_dict_missing_name(): result = test_data.to_dict() # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] == 1 assert result["name"] is None assert result["description"] == "This is planet: Mars" @@ -67,7 +70,7 @@ def test_to_dict_missing_description(): result = test_data.to_dict() # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] == 1 assert result["name"] == "Mars" assert result["description"] is None diff --git a/tests/test_routes.py b/tests/test_routes.py index eca9b6c5e..b48ee89c2 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -2,6 +2,7 @@ from werkzeug.exceptions import HTTPException from app.planet_routes import validate_model from app.models.planet import Planet +from app.models.moon import Moon def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): response = client.get("/planets/1") @@ -173,6 +174,7 @@ def test_validate_model(saved_two_planets): assert result_planet.description == "This is planet: Mars" assert result_planet.gravity == 3.721 assert result_planet.distance_from_earth == 60.81 + assert result_planet.moons == [] def test_validate_model_missing_record(saved_two_planets): # Calling `validate_model` without being invoked by a route will @@ -184,3 +186,94 @@ def test_validate_model_invalid_id(saved_two_planets): with pytest.raises(HTTPException): result_planet = validate_model(Planet, "invalid_id") +#--------------------------------------- +#--------------------------------------- +#---------Moon route tests-------------- +#--------------------------------------- +#--------------------------------------- + +def test_get_moon_by_id_return_200_successful_code(client, saved_two_moons): + response = client.get("/moons/1") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["name"] == "Moon1" + + +def test_get_moon_by_not_exist_id_return_404(client): + response = client.get("/moons/1") + response_body = response.get_json() + + assert response.status_code == 404 + +def test_get_moon_by_invalid_moon_id_return_400_bad_request_error(client, saved_two_moons): + response = client.get("/moons/hello") + response_body = response.get_json() + + assert response.status_code == 400 + assert response_body == {"message": "Moon hello is invalid"} + + +def test_get_all_moons_with_no_records_return_empty_array(client): + response = client.get("/moons/all") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == [] + + +def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_two_moons): + response = client.get("/moons") + response_body = response.get_json() + + assert response.status_code == 200 + assert len(response_body) == 2 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"]== "Moon1" + assert response_body[0]["planet_id"] ==1 + assert response_body[0]["planet"] == "Mars" + assert response_body[1]["id"] == 2 + assert response_body[1]["name"]== "Moon2" + assert response_body[1]["planet_id"] == 1 + assert response_body[1]["planet"] =="Mars" + + +def test_create_one_moon_return_201_successfully_created(client): + response = client.post("/moons", + json={"name": "Moon3", + "planet": "Jupiter", + "planet_id": 2}) + response_body = response.get_json() + + assert response.status_code == 201 + assert response_body == "Moon Moon3 successfully created." + +def test_create_one_moon_under_planet_return_201_successfully_created(client,saved_two_planets): + response = client.post("/2/moon", + json= {"name": "Moon3", + "planet": "Jupiter", + "planet_id": 2}) + response_body = response.get_json() + + assert response.status_code == 201 + assert response_body == "Moon Moon3 successfully created to planet 2" + + +def test_validate_moon_model(saved_two_moons): + result_moon = validate_model(Moon, 1) + + assert result_moon.id == 1 + assert result_moon.name == "moon1" + assert result_moon.planet_id == 1 + assert result_moon.planet == "Mars" + +def test_validate_model_missing_moon_record(saved_two_moons): + # Calling `validate_model` without being invoked by a route will + # cause an `HTTPException` when an `abort` statement is reached + with pytest.raises(HTTPException): + result_planet = validate_model(Moon, "3") + +def test_validate_model_invalid_moon_id(saved_two_moons): + with pytest.raises(HTTPException): + result_planet = validate_model(Moon, "invalid_id") + From 9d831911d2396093234b47c496cc6ae994dc8ffb Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 5 Jan 2023 18:55:51 -0800 Subject: [PATCH 45/62] old test file --- tests/test_models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index 83715bf51..d908b86b1 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -13,7 +13,7 @@ def test_to_dict_no_missing_data(): result = test_data.to_dict() # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] == 1 assert result["name"] == "Mars" assert result["description"] == "This is planet: Mars" @@ -31,7 +31,7 @@ def test_to_dict_missing_id(): result = test_data.to_dict() # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] is None assert result["name"] == "Mars" assert result["description"] == "This is planet: Mars" @@ -49,7 +49,7 @@ def test_to_dict_missing_name(): result = test_data.to_dict() # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] == 1 assert result["name"] is None assert result["description"] == "This is planet: Mars" @@ -67,7 +67,7 @@ def test_to_dict_missing_description(): result = test_data.to_dict() # Assert - assert len(result) == 5 + assert len(result) == 6 assert result["id"] == 1 assert result["name"] == "Mars" assert result["description"] is None From 44a34269076b2196efada8ad0c19111e0f48fa5f Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 5 Jan 2023 19:11:11 -0800 Subject: [PATCH 46/62] Bugs fixed, all tests are passing now --- tests/conftest.py | 8 ++------ tests/test_routes.py | 12 +++--------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f07260daa..6cf47d25f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,13 +51,9 @@ def saved_two_planets(app): @pytest.fixture def saved_two_moons(app): - moon1 = Moon(name="Moon1", - planet_id=1, - planet="Mars") + moon1 = Moon(name="Moon1") - moon2 = Moon(name="Moon2", - planet_id=1, - planet="Mars") + moon2 = Moon(name="Moon2") db.session.add_all([moon1, moon2]) diff --git a/tests/test_routes.py b/tests/test_routes.py index b48ee89c2..0bf8af045 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -223,19 +223,15 @@ def test_get_all_moons_with_no_records_return_empty_array(client): def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_two_moons): - response = client.get("/moons") + response = client.get("/moons/all") response_body = response.get_json() assert response.status_code == 200 assert len(response_body) == 2 assert response_body[0]["id"] == 1 assert response_body[0]["name"]== "Moon1" - assert response_body[0]["planet_id"] ==1 - assert response_body[0]["planet"] == "Mars" assert response_body[1]["id"] == 2 assert response_body[1]["name"]== "Moon2" - assert response_body[1]["planet_id"] == 1 - assert response_body[1]["planet"] =="Mars" def test_create_one_moon_return_201_successfully_created(client): @@ -249,7 +245,7 @@ def test_create_one_moon_return_201_successfully_created(client): assert response_body == "Moon Moon3 successfully created." def test_create_one_moon_under_planet_return_201_successfully_created(client,saved_two_planets): - response = client.post("/2/moon", + response = client.post("moons/2/moon", json= {"name": "Moon3", "planet": "Jupiter", "planet_id": 2}) @@ -263,9 +259,7 @@ def test_validate_moon_model(saved_two_moons): result_moon = validate_model(Moon, 1) assert result_moon.id == 1 - assert result_moon.name == "moon1" - assert result_moon.planet_id == 1 - assert result_moon.planet == "Mars" + assert result_moon.name == "Moon1" def test_validate_model_missing_moon_record(saved_two_moons): # Calling `validate_model` without being invoked by a route will From 7d8b817c848f0a0692c9c1059b97dbe5ad78e50c Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Thu, 5 Jan 2023 19:12:44 -0800 Subject: [PATCH 47/62] "working on test_routes" --- tests/conftest.py | 8 ++------ tests/test_routes.py | 12 +++++------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f07260daa..6cf47d25f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,13 +51,9 @@ def saved_two_planets(app): @pytest.fixture def saved_two_moons(app): - moon1 = Moon(name="Moon1", - planet_id=1, - planet="Mars") + moon1 = Moon(name="Moon1") - moon2 = Moon(name="Moon2", - planet_id=1, - planet="Mars") + moon2 = Moon(name="Moon2") db.session.add_all([moon1, moon2]) diff --git a/tests/test_routes.py b/tests/test_routes.py index b48ee89c2..a717acb29 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -230,12 +230,12 @@ def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_t assert len(response_body) == 2 assert response_body[0]["id"] == 1 assert response_body[0]["name"]== "Moon1" - assert response_body[0]["planet_id"] ==1 - assert response_body[0]["planet"] == "Mars" + #assert response_body[0]["planet_id"] ==1 + #assert response_body[0]["planet"] == "Mars" assert response_body[1]["id"] == 2 assert response_body[1]["name"]== "Moon2" - assert response_body[1]["planet_id"] == 1 - assert response_body[1]["planet"] =="Mars" + #assert response_body[1]["planet_id"] == 1 + #assert response_body[1]["planet"] =="Mars" def test_create_one_moon_return_201_successfully_created(client): @@ -263,9 +263,7 @@ def test_validate_moon_model(saved_two_moons): result_moon = validate_model(Moon, 1) assert result_moon.id == 1 - assert result_moon.name == "moon1" - assert result_moon.planet_id == 1 - assert result_moon.planet == "Mars" + assert result_moon.name == "Moon1" def test_validate_model_missing_moon_record(saved_two_moons): # Calling `validate_model` without being invoked by a route will From 9cc692bdb7cc2898f9fc71e25f378b53b78fe65c Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 5 Jan 2023 20:00:52 -0800 Subject: [PATCH 48/62] Nested routes tests added. All tests passed --- app/models/moon.py | 4 +- app/moon_routes.py | 61 ++++++++--------- tests/test_models.py | 10 ++- tests/test_routes.py | 157 +++++++++++++++++++++++++++---------------- 4 files changed, 141 insertions(+), 91 deletions(-) diff --git a/app/models/moon.py b/app/models/moon.py index 28ab7fbf4..1b8771031 100644 --- a/app/models/moon.py +++ b/app/models/moon.py @@ -10,7 +10,7 @@ def to_dict(self): return{ "id" : self.id, "name" : self.name, - "planet_id" : self.new_planet.id, - "planet" : self.new_planet + "planet_id" : self.planet.id, + "planet" : self.planet.name } \ No newline at end of file diff --git a/app/moon_routes.py b/app/moon_routes.py index bb8d23b42..66145979c 100644 --- a/app/moon_routes.py +++ b/app/moon_routes.py @@ -1,17 +1,16 @@ -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 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.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"], ) + new_moon = Moon(name=request_body["name"], ) db.session.add(new_moon) db.session.commit() @@ -20,49 +19,51 @@ def create_new_moon(): @moon_bp.route("/", methods=["GET"]) -def read_moon_by_id(moon_id): +def read_moon_by_id(moon_id): moon_response = validate_model(Moon, moon_id) result_dict = { - "id" : moon_response.id, - "name" : moon_response.name, + "id": moon_response.id, + "name": moon_response.name, } - return make_response(jsonify(result_dict), 200) + return make_response(jsonify(result_dict), 200) + @moon_bp.route("/all", methods=["GET"]) -def read_all_moons(): +def read_all_moons(): all_moon = Moon.query.all() - moons_response = [] - for moon in all_moon: + moons_response = [] + for moon in all_moon: moons_response.append({ - "id" : moon.id, - "name" : moon.name, - } -) - return make_response(jsonify(moons_response), 200) + "id": moon.id, + "name": moon.name, + } + ) + return make_response(jsonify(moons_response), 200) + -@moon_bp.route("//moon", methods = ["POST"]) -def create_moon_to_planet_by_planet_id(planet_id): +@moon_bp.route("//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() + request_body = request.get_json() new_moon = Moon( - name = request_body["name"], - planet_id = new_planet.id, - planet = new_planet - ) + name=request_body["name"], + planet_id=new_planet.id, + planet=new_planet + ) db.session.add(new_moon) db.session.commit() - return make_response(jsonify(f"Moon {new_moon.name} successfully created to planet {planet_id}"), 201) + return make_response(jsonify(new_moon.to_dict()), 201) + @moon_bp.route("//moons", methods=["GET"]) -def get_moons_by_planet_id(planet_id): +def get_moons_by_planet_id(planet_id): planet = validate_model(Planet, planet_id) - - response = planet.to_dict() - return make_response(jsonify(response),200) + response = planet.to_dict() + return make_response(jsonify(response), 200) diff --git a/tests/test_models.py b/tests/test_models.py index d97cc47d0..3251760d9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,4 +1,6 @@ from app.models.planet import Planet +from app.models.moon import Moon +from app.planet_routes import validate_model import pytest def test_to_dict_no_missing_data(): @@ -139,4 +141,10 @@ def test_from_dict_with_extra_keys(): assert new_planet.name == "Mars" assert new_planet.description == "This is planet: Mars" assert new_planet.gravity == 3.721 - assert new_planet.distance_from_earth == 60.81 \ No newline at end of file + assert new_planet.distance_from_earth == 60.81 + +def test_validate_moon_model(saved_two_moons): + result_moon = validate_model(Moon, 1) + + assert result_moon.id == 1 + assert result_moon.name == "Moon1" \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index 0bf8af045..7643dfdf1 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,9 +1,10 @@ import pytest from werkzeug.exceptions import HTTPException from app.planet_routes import validate_model -from app.models.planet import Planet +from app.models.planet import Planet from app.models.moon import Moon + def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): response = client.get("/planets/1") response_body = response.get_json() @@ -18,7 +19,8 @@ def test_get_planet_by_not_exist_id_return_404(client): assert response.status_code == 404 -def test_get_planet_by_invalid_planet_id_return_400_bad_request_error(client, saved_two_planets): + +def test_get_planet_by_invalid_planet_id_return_400_bad_request_error(client, saved_two_planets): response = client.get("/planets/hello") response_body = response.get_json() @@ -40,54 +42,59 @@ def test_get_all_planets_with_two_records_return_array_with_size_2(client, saved assert response.status_code == 200 assert len(response_body) == 2 - assert response_body[0]["id"] == 1 - assert response_body[0]["name"]== "Mars" - assert response_body[0]["description"] =="This is planet: Mars" + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" assert response_body[0]["gravity"] == 3.721 assert response_body[0]["distance_from_earth"] == 60.81 - assert response_body[1]["id"] == 2 - assert response_body[1]["name"]== "Jupiter" + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Jupiter" assert response_body[1]["description"] == "This is planet: Jupiter" assert response_body[1]["gravity"] == 24.79 assert response_body[1]["distance_from_earth"] == 467.64 + def test_create_one_planet_return_201_successfully_created(client): response = client.post("/planets", - json={"name": "Venus", - "description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"name": "Venus", + "description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) response_body = response.get_json() assert response.status_code == 201 assert response_body == "Planet: Venus created successfully." -#####edge cases for create one planet +# edge cases for create one planet + + def test_create_one_planet_no_name_return_400(client): - + response = client.post("/planets", - json={"description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 def test_create_one_planet_no_description_return_400(client): response = client.post("/planets", - json={"name": "Mars", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"name": "Mars", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 + def test_create_one_planet_no_gravity_return_400(client): test_data = {"name": "Mars", - "description": "This is planet: Venus", - "distance_from_earth": 67.685 - } - response = client.post("/planets", json = test_data) - assert response.status_code == 400 + "description": "This is planet: Venus", + "distance_from_earth": 67.685 + } + response = client.post("/planets", json=test_data) + assert response.status_code == 400 + def test_create_one_planet_with_extra_keys_return_201(client, saved_two_planets): test_data = { @@ -105,16 +112,18 @@ def test_create_one_planet_with_extra_keys_return_201(client, saved_two_planets) assert response.status_code == 201 assert response_body == "Planet: Venus created successfully." + def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): resposne = client.put("/planets/1", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) response_body = resposne.get_json() assert resposne.status_code == 200 - assert response_body == "Planet: 1 has been updated successfully." + assert response_body == "Planet: 1 has been updated successfully." + def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_planets): # with pytest.raises(HTTPException): @@ -125,25 +134,27 @@ def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_pl # "distance_from_earth": 55.99}) resposne = client.put("/planets/9", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) response_body = resposne.get_json() assert resposne.status_code == 404 - assert response_body == {"message":"Planet 9 not found"} + assert response_body == {"message": "Planet 9 not found"} + def test_put_invalid_planet__id_return_400_invalid_error(client, saved_two_planets): resposne = client.put("/planets/invalid_id", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) response_body = resposne.get_json() assert resposne.status_code == 400 - assert response_body == {"message":"Planet invalid_id is invalid"} + assert response_body == {"message": "Planet invalid_id is invalid"} + def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, saved_two_planets): response = client.delete("planets/1") @@ -152,6 +163,7 @@ def test_delete_planet_with_id_1_return_200_planet_successfully_deleted(client, assert response.status_code == 200 assert response_body == "Planet: 1 has been deleted successfully." + def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, saved_two_planets): response = client.delete("planets/10") response_body = response.get_json() @@ -159,6 +171,7 @@ def test_delete_planet_with_non_exist_id_return_404_not_found_error(client, save assert response.status_code == 404 assert response_body == {"message": "Planet 10 not found"} + def test_delete_planet_with_invalid_id_return_400_invalid_error(client, saved_two_planets): response = client.delete("planets/invalid_id") response_body = response.get_json() @@ -166,31 +179,35 @@ def test_delete_planet_with_invalid_id_return_400_invalid_error(client, saved_tw assert response.status_code == 400 assert response_body == {"message": "Planet invalid_id is invalid"} + def test_validate_model(saved_two_planets): result_planet = validate_model(Planet, 1) assert result_planet.id == 1 assert result_planet.name == "Mars" assert result_planet.description == "This is planet: Mars" - assert result_planet.gravity == 3.721 + assert result_planet.gravity == 3.721 assert result_planet.distance_from_earth == 60.81 assert result_planet.moons == [] + def test_validate_model_missing_record(saved_two_planets): # Calling `validate_model` without being invoked by a route will - # cause an `HTTPException` when an `abort` statement is reached + # cause an `HTTPException` when an `abort` statement is reached with pytest.raises(HTTPException): result_planet = validate_model(Planet, "3") - + + def test_validate_model_invalid_id(saved_two_planets): with pytest.raises(HTTPException): result_planet = validate_model(Planet, "invalid_id") -#--------------------------------------- -#--------------------------------------- -#---------Moon route tests-------------- -#--------------------------------------- -#--------------------------------------- +# --------------------------------------- +# --------------------------------------- +# ---------Moon route tests-------------- +# --------------------------------------- +# --------------------------------------- + def test_get_moon_by_id_return_200_successful_code(client, saved_two_moons): response = client.get("/moons/1") @@ -206,7 +223,8 @@ def test_get_moon_by_not_exist_id_return_404(client): assert response.status_code == 404 -def test_get_moon_by_invalid_moon_id_return_400_bad_request_error(client, saved_two_moons): + +def test_get_moon_by_invalid_moon_id_return_400_bad_request_error(client, saved_two_moons): response = client.get("/moons/hello") response_body = response.get_json() @@ -228,46 +246,69 @@ def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_t assert response.status_code == 200 assert len(response_body) == 2 - assert response_body[0]["id"] == 1 - assert response_body[0]["name"]== "Moon1" + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Moon1" assert response_body[1]["id"] == 2 - assert response_body[1]["name"]== "Moon2" + assert response_body[1]["name"] == "Moon2" def test_create_one_moon_return_201_successfully_created(client): response = client.post("/moons", - json={"name": "Moon3", - "planet": "Jupiter", - "planet_id": 2}) + json={"name": "Moon3"}) response_body = response.get_json() assert response.status_code == 201 assert response_body == "Moon Moon3 successfully created." -def test_create_one_moon_under_planet_return_201_successfully_created(client,saved_two_planets): - response = client.post("moons/2/moon", - json= {"name": "Moon3", - "planet": "Jupiter", - "planet_id": 2}) +def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): + response = client.post("/moons/1/moon", + json={ + "name": "planet1_moon" + }) response_body = response.get_json() assert response.status_code == 201 - assert response_body == "Moon Moon3 successfully created to planet 2" + assert response_body["name"] == "planet1_moon" + assert response_body["planet_id"] == 1 + assert response_body["planet"] == "Mars" +def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_planets): + response = client.get("moons/1/moons") + response_body = response.get_json() -def test_validate_moon_model(saved_two_moons): - result_moon = validate_model(Moon, 1) + assert response.status_code == 200 + assert response_body["id"] == 1 + assert response_body["name"] == "Mars" + assert response_body["description"] == "This is planet: Mars" + assert response_body["description"] == "This is planet: Mars" + assert response_body["gravity"] == 3.721 + assert response_body["distance_from_earth"] == 60.81 + assert response_body["moons"] == [] + +def test_get_moons_by_planet_id_return_list_of_moons(client, saved_two_planets): + post_response = client.post("/moons/1/moon", + json={ + "name": "planet1_moon" + }) + response = client.get("moons/1/moons") + response_body = response.get_json() - assert result_moon.id == 1 - assert result_moon.name == "Moon1" + assert response.status_code == 200 + assert response_body["id"] == 1 + assert response_body["name"] == "Mars" + assert response_body["description"] == "This is planet: Mars" + assert response_body["description"] == "This is planet: Mars" + assert response_body["gravity"] == 3.721 + assert response_body["distance_from_earth"] == 60.81 + assert response_body["moons"] == ["planet1_moon"] def test_validate_model_missing_moon_record(saved_two_moons): # Calling `validate_model` without being invoked by a route will - # cause an `HTTPException` when an `abort` statement is reached + # cause an `HTTPException` when an `abort` statement is reached with pytest.raises(HTTPException): result_planet = validate_model(Moon, "3") - + + def test_validate_model_invalid_moon_id(saved_two_moons): with pytest.raises(HTTPException): result_planet = validate_model(Moon, "invalid_id") - From 4a1a4893ff22308f2d52afdadc35d4476a7193cb Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Thu, 5 Jan 2023 20:20:35 -0800 Subject: [PATCH 49/62] Procfile file for heroku added --- Procfile | 1 + requirements.txt | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..62e430aca --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn 'app:create_app()' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fba2b3e38..7c37f2a09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,25 @@ alembic==1.5.4 +attrs==22.2.0 autopep8==1.5.5 blinker==1.4 certifi==2020.12.5 chardet==4.0.0 click==7.1.2 +coverage==7.0.0 Flask==1.1.2 Flask-Migrate==2.6.0 Flask-SQLAlchemy==2.4.4 +gunicorn==20.1.0 idna==2.10 +iniconfig==1.1.1 itsdangerous==1.1.0 Jinja2==2.11.3 Mako==1.1.4 MarkupSafe==1.1.1 +packaging==22.0 +pluggy==1.0.0 psycopg2-binary==2.9.4 +py==1.11.0 pycodestyle==2.6.0 pytest==7.1.1 pytest-cov==2.12.1 @@ -23,5 +30,6 @@ requests==2.25.1 six==1.15.0 SQLAlchemy==1.3.23 toml==0.10.2 +tomli==2.0.1 urllib3==1.26.4 Werkzeug==1.0.1 From d96a2a3e82b0afc69f162697d1f6c34504fb996d Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Thu, 5 Jan 2023 23:43:50 -0800 Subject: [PATCH 50/62] added one test and reviewed test routes file --- migrations/versions/c9a0888f7e70_.py | 43 ++++++++++++++++++++++++++++ tests/test_routes.py | 21 ++++++++++++-- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/c9a0888f7e70_.py diff --git a/migrations/versions/c9a0888f7e70_.py b/migrations/versions/c9a0888f7e70_.py new file mode 100644 index 000000000..f93eb38dc --- /dev/null +++ b/migrations/versions/c9a0888f7e70_.py @@ -0,0 +1,43 @@ +"""empty message + +Revision ID: c9a0888f7e70 +Revises: +Create Date: 2023-01-05 11:26:58.114514 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c9a0888f7e70' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('planet', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('gravity', sa.Float(), nullable=True), + sa.Column('distance_from_earth', sa.Float(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('moon', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('planet_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['planet_id'], ['planet.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('moon') + op.drop_table('planet') + # ### end Alembic commands ### diff --git a/tests/test_routes.py b/tests/test_routes.py index 7643dfdf1..beb91b238 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -280,11 +280,29 @@ def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_pla assert response_body["id"] == 1 assert response_body["name"] == "Mars" assert response_body["description"] == "This is planet: Mars" - assert response_body["description"] == "This is planet: Mars" assert response_body["gravity"] == 3.721 assert response_body["distance_from_earth"] == 60.81 assert response_body["moons"] == [] +def test_get_moons_by_planet_id_return_list_of_two_moons(client,saved_two_planets,saved_two_moons): + post_response = client.post("/moons/1/moon", + json={"name": "Moon1" + }) + post_response = client.post("/moons/1/moon", + json={"name": "Moon2" + }) + response = client.get("moons/1/moons") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["id"] == 1 + assert response_body["name"] == "Mars" + assert response_body["description"] == "This is planet: Mars" + assert response_body["gravity"] == 3.721 + assert response_body["distance_from_earth"] == 60.81 + assert response_body["moons"] == ["Moon1", "Moon2"] + + def test_get_moons_by_planet_id_return_list_of_moons(client, saved_two_planets): post_response = client.post("/moons/1/moon", json={ @@ -297,7 +315,6 @@ def test_get_moons_by_planet_id_return_list_of_moons(client, saved_two_planets): assert response_body["id"] == 1 assert response_body["name"] == "Mars" assert response_body["description"] == "This is planet: Mars" - assert response_body["description"] == "This is planet: Mars" assert response_body["gravity"] == 3.721 assert response_body["distance_from_earth"] == 60.81 assert response_body["moons"] == ["planet1_moon"] From 0e5a05a88a77163a790bd352439a1d64f6d060e0 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Fri, 6 Jan 2023 00:44:29 -0800 Subject: [PATCH 51/62] added Procfile --- requirements.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/requirements.txt b/requirements.txt index fba2b3e38..eb1bd92e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,29 @@ alembic==1.5.4 +attrs==20.3.0 autopep8==1.5.5 blinker==1.4 certifi==2020.12.5 chardet==4.0.0 +charset-normalizer==2.1.1 click==7.1.2 +coverage==7.0.3 +exceptiongroup==1.0.4 Flask==1.1.2 Flask-Migrate==2.6.0 Flask-SQLAlchemy==2.4.4 +gunicorn==20.1.0 idna==2.10 +iniconfig==1.1.1 itsdangerous==1.1.0 Jinja2==2.11.3 Mako==1.1.4 MarkupSafe==1.1.1 +packaging==20.9 +pluggy==0.13.1 psycopg2-binary==2.9.4 +py==1.10.0 pycodestyle==2.6.0 +pyparsing==2.4.7 pytest==7.1.1 pytest-cov==2.12.1 python-dateutil==2.8.1 @@ -23,5 +33,6 @@ requests==2.25.1 six==1.15.0 SQLAlchemy==1.3.23 toml==0.10.2 +tomli==2.0.1 urllib3==1.26.4 Werkzeug==1.0.1 From dc226f2a0429849dc82c461e3b48ec692b27293f Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Fri, 6 Jan 2023 09:50:12 -0800 Subject: [PATCH 52/62] Tests modified --- app/planet_routes.py | 2 +- tests/test_routes.py | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index a3b7481b4..f71811bb0 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -96,7 +96,7 @@ def read_planets(): planet_response = [] for planet in planets: - planet_response.append(planet.to_dict()) #use to_dict function to make code more readable + planet_response.append(planet.to_dict()) #use to_dict function to make code more readable return make_response(jsonify(planet_response), 200) diff --git a/tests/test_routes.py b/tests/test_routes.py index beb91b238..be10f57f1 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -302,23 +302,6 @@ def test_get_moons_by_planet_id_return_list_of_two_moons(client,saved_two_planet assert response_body["distance_from_earth"] == 60.81 assert response_body["moons"] == ["Moon1", "Moon2"] - -def test_get_moons_by_planet_id_return_list_of_moons(client, saved_two_planets): - post_response = client.post("/moons/1/moon", - json={ - "name": "planet1_moon" - }) - response = client.get("moons/1/moons") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body["id"] == 1 - assert response_body["name"] == "Mars" - assert response_body["description"] == "This is planet: Mars" - assert response_body["gravity"] == 3.721 - assert response_body["distance_from_earth"] == 60.81 - assert response_body["moons"] == ["planet1_moon"] - def test_validate_model_missing_moon_record(saved_two_moons): # Calling `validate_model` without being invoked by a route will # cause an `HTTPException` when an `abort` statement is reached From 010b2fc2eaee8dfe7d1786a7eefdb33ce5a0ef81 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Fri, 6 Jan 2023 10:16:06 -0800 Subject: [PATCH 53/62] more tests added --- requirements.txt | 2 -- tests/test_routes.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 34bb6ab51..545a38efa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,10 +11,8 @@ Flask==1.1.2 Flask-Migrate==2.6.0 Flask-SQLAlchemy==2.4.4 gunicorn==20.1.0 -gunicorn==20.1.0 idna==2.10 iniconfig==1.1.1 -iniconfig==1.1.1 itsdangerous==1.1.0 Jinja2==2.11.3 Mako==1.1.4 diff --git a/tests/test_routes.py b/tests/test_routes.py index be10f57f1..8b64dc2d8 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -302,6 +302,20 @@ def test_get_moons_by_planet_id_return_list_of_two_moons(client,saved_two_planet assert response_body["distance_from_earth"] == 60.81 assert response_body["moons"] == ["Moon1", "Moon2"] +def test_create_moons_by_invalid_planet_id(client, saved_two_planets): + response = client.post("/moons/invalid/moon", + json={"name": "Moon1" + }) + assert response.status_code == 400 + assert response.get_json() == {"message": "Planet invalid is invalid"} + +def test_create_moons_by_a_non_existing_planet_id(client, saved_two_planets): + response = client.post("/moons/100/moon", + json={"name": "Moon1" + }) + assert response.status_code == 404 + assert response.get_json() == {"message": "Planet 100 not found"} + def test_validate_model_missing_moon_record(saved_two_moons): # Calling `validate_model` without being invoked by a route will # cause an `HTTPException` when an `abort` statement is reached From b2c52827e7ac5277392bae76ce7f976afee1f15a Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Sat, 7 Jan 2023 17:23:11 -0800 Subject: [PATCH 54/62] More tests added for sort feature --- app/moon_routes.py | 2 +- app/planet_routes.py | 5 +- tests/conftest.py | 29 +++++ tests/test_routes.py | 246 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 256 insertions(+), 26 deletions(-) diff --git a/app/moon_routes.py b/app/moon_routes.py index 66145979c..2f9e67d03 100644 --- a/app/moon_routes.py +++ b/app/moon_routes.py @@ -30,7 +30,7 @@ def read_moon_by_id(moon_id): return make_response(jsonify(result_dict), 200) -@moon_bp.route("/all", methods=["GET"]) +@moon_bp.route("", methods=["GET"]) def read_all_moons(): all_moon = Moon.query.all() diff --git a/app/planet_routes.py b/app/planet_routes.py index f71811bb0..46aef9990 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -91,6 +91,9 @@ def read_planets(): 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() @@ -129,4 +132,4 @@ def delete_planet_by_id(planet_id): db.session.delete(planet) db.session.commit() - return make_response(jsonify(f"Planet: {planet_id} has been deleted successfully."), 200) + return make_response(jsonify(f"Planet: {planet_id} has been deleted successfully."), 200) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 6cf47d25f..42947ebfc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,6 +48,35 @@ def saved_two_planets(app): db.session.refresh(mars, ["id"]) db.session.refresh(jupiter, ["id"]) +@pytest.fixture +def saved_three_planets_with_duplicate_planet_name(app): + mars = Planet(name="Mars", + description="This is planet: Mars", + gravity=3.721, + distance_from_earth=60.81, + moons = [] + ) + + mars2 = Planet(name="Mars", + description="This is planet: Mars2", + gravity=4.721, + distance_from_earth=60.81, + moons = [] + ) + + jupiter = Planet(name="Jupiter", + description="This is planet: Jupiter", + gravity=24.79, + distance_from_earth=467.64, + moons = [], + ) + + db.session.add_all([mars, mars2, jupiter]) + db.session.commit() + db.session.refresh(mars, ["id"]) + db.session.refresh(mars2, ["id"]) + db.session.refresh(jupiter, ["id"]) + @pytest.fixture def saved_two_moons(app): diff --git a/tests/test_routes.py b/tests/test_routes.py index 8b64dc2d8..34a447df6 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -4,6 +4,12 @@ from app.models.planet import Planet from app.models.moon import Moon +# --------------------------------------- +# --------------------------------------- +# ---------Planet route tests-------------- +# --------------------------------------- +# --------------------------------------- + def test_get_planet_by_id_return_200_successful_code(client, saved_two_planets): response = client.get("/planets/1") @@ -54,6 +60,199 @@ def test_get_all_planets_with_two_records_return_array_with_size_2(client, saved assert response_body[1]["distance_from_earth"] == 467.64 +def test_get_planets_no_param_sort_by_id_asc(saved_two_planets, client): + response = client.get("/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Jupiter" + assert response_body[1]["description"] == "This is planet: Jupiter" + assert response_body[1]["gravity"] == 24.79 + assert response_body[1]["distance_from_earth"] == 467.64 + + +def test_get_planets_sort_desc_param_sort_by_name_desc(saved_two_planets, client): + response = client.get("/planets?sort=desc") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Jupiter" + assert response_body[1]["description"] == "This is planet: Jupiter" + assert response_body[1]["gravity"] == 24.79 + assert response_body[1]["distance_from_earth"] == 467.64 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + + +def test_get_planets_sort_desc_param_sort_by_name_asc(saved_two_planets, client): + response = client.get("/planets?sort=asc") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 2 + assert response_body[0]["name"] == "Jupiter" + assert response_body[0]["description"] == "This is planet: Jupiter" + assert response_body[0]["gravity"] == 24.79 + assert response_body[0]["distance_from_earth"] == 467.64 + assert response_body[1]["id"] == 1 + assert response_body[1]["name"] == "Mars" + assert response_body[1]["description"] == "This is planet: Mars" + assert response_body[1]["gravity"] == 3.721 + assert response_body[1]["distance_from_earth"] == 60.81 + + +def test_get_planets_sort_by_gravity_desc(saved_two_planets, client): + data = {"sort": "gravity:desc"} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 2 + assert response_body[0]["name"] == "Jupiter" + assert response_body[0]["description"] == "This is planet: Jupiter" + assert response_body[0]["gravity"] == 24.79 + assert response_body[0]["distance_from_earth"] == 467.64 + assert response_body[1]["id"] == 1 + assert response_body[1]["name"] == "Mars" + assert response_body[1]["description"] == "This is planet: Mars" + assert response_body[1]["gravity"] == 3.721 + assert response_body[1]["distance_from_earth"] == 60.81 + + +def test_get_planets_sort_planets_by_name_desc_order(saved_two_planets, client): + data = {"sort": "name:desc"} + response = client.get("/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Jupiter" + assert response_body[1]["description"] == "This is planet: Jupiter" + assert response_body[1]["gravity"] == 24.79 + assert response_body[1]["distance_from_earth"] == 467.64 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + + +def test_get_planets_filter_by_planet_name_return_Mars_only(saved_two_planets, client): + data = {"name": "Mars"} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + + +def test_get_planets_filter_by_gravity_return_Mars_only(saved_two_planets, client): + data = {"gravity": 3.721} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + + +def test_get_planets_filter_by_distance_from_earth_return_Jupiter_only(saved_two_planets, client): + data = {"distance_from_earth": 467.64} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 2 + assert response_body[0]["name"] == "Jupiter" + assert response_body[0]["description"] == "This is planet: Jupiter" + assert response_body[0]["gravity"] == 24.79 + assert response_body[0]["distance_from_earth"] == 467.64 + + +def test_get_planets_filter_by_planet_Jupiter_return_Jupiter_only(saved_two_planets, client): + data = {"name": "Jupiter"} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 2 + assert response_body[0]["name"] == "Jupiter" + assert response_body[0]["description"] == "This is planet: Jupiter" + assert response_body[0]["gravity"] == 24.79 + assert response_body[0]["distance_from_earth"] == 467.64 + + +def test_get_planets_filter_by_planet_Mars_sort_by_id_asc(saved_three_planets_with_duplicate_planet_name, client): + data = {"name": "Mars"} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Mars" + assert response_body[1]["description"] == "This is planet: Mars2" + assert response_body[1]["gravity"] == 4.721 + assert response_body[1]["distance_from_earth"] == 60.81 + + +def test_get_planets_filter_by_planet_Mars_sort_by_gravity_asc(saved_three_planets_with_duplicate_planet_name, client): + data = {"name": "Mars", "sort": "gravity"} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Mars" + assert response_body[1]["description"] == "This is planet: Mars2" + assert response_body[1]["gravity"] == 4.721 + assert response_body[1]["distance_from_earth"] == 60.81 + + +def test_get_planets_filter_by_planet_Mars_sort_by_gravity_desc(saved_three_planets_with_duplicate_planet_name, client): + data = {"name": "Mars", "sort": "gravity:desc"} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[1]["id"] == 1 + assert response_body[1]["name"] == "Mars" + assert response_body[1]["description"] == "This is planet: Mars" + assert response_body[1]["gravity"] == 3.721 + assert response_body[1]["distance_from_earth"] == 60.81 + assert response_body[0]["id"] == 2 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars2" + assert response_body[0]["gravity"] == 4.721 + assert response_body[0]["distance_from_earth"] == 60.81 + + def test_create_one_planet_return_201_successfully_created(client): response = client.post("/planets", json={"name": "Venus", @@ -126,13 +325,6 @@ def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, sa def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_planets): - # with pytest.raises(HTTPException): - # resposne = client.put("/planets/9", - # json={"name": "New Planet", - # "description": "This a New Planet", - # "gravity": 20.0, - # "distance_from_earth": 55.99}) - resposne = client.put("/planets/9", json={"name": "New Planet", "description": "This a New Planet", @@ -233,7 +425,7 @@ def test_get_moon_by_invalid_moon_id_return_400_bad_request_error(client, saved_ def test_get_all_moons_with_no_records_return_empty_array(client): - response = client.get("/moons/all") + response = client.get("/moons") response_body = response.get_json() assert response.status_code == 200 @@ -241,7 +433,7 @@ def test_get_all_moons_with_no_records_return_empty_array(client): def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_two_moons): - response = client.get("/moons/all") + response = client.get("/moons") response_body = response.get_json() assert response.status_code == 200 @@ -254,17 +446,18 @@ def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_t def test_create_one_moon_return_201_successfully_created(client): response = client.post("/moons", - json={"name": "Moon3"}) + json={"name": "Moon3"}) response_body = response.get_json() assert response.status_code == 201 assert response_body == "Moon Moon3 successfully created." + def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): response = client.post("/moons/1/moon", - json={ - "name": "planet1_moon" - }) + json={ + "name": "planet1_moon" + }) response_body = response.get_json() assert response.status_code == 201 @@ -272,50 +465,55 @@ def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): assert response_body["planet_id"] == 1 assert response_body["planet"] == "Mars" + def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_planets): response = client.get("moons/1/moons") response_body = response.get_json() assert response.status_code == 200 - assert response_body["id"] == 1 + assert response_body["id"] == 1 assert response_body["name"] == "Mars" assert response_body["description"] == "This is planet: Mars" assert response_body["gravity"] == 3.721 assert response_body["distance_from_earth"] == 60.81 assert response_body["moons"] == [] -def test_get_moons_by_planet_id_return_list_of_two_moons(client,saved_two_planets,saved_two_moons): + +def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets, saved_two_moons): post_response = client.post("/moons/1/moon", - json={"name": "Moon1" - }) + json={"name": "Moon1" + }) post_response = client.post("/moons/1/moon", - json={"name": "Moon2" - }) + json={"name": "Moon2" + }) response = client.get("moons/1/moons") response_body = response.get_json() assert response.status_code == 200 - assert response_body["id"] == 1 + assert response_body["id"] == 1 assert response_body["name"] == "Mars" assert response_body["description"] == "This is planet: Mars" assert response_body["gravity"] == 3.721 assert response_body["distance_from_earth"] == 60.81 assert response_body["moons"] == ["Moon1", "Moon2"] + def test_create_moons_by_invalid_planet_id(client, saved_two_planets): response = client.post("/moons/invalid/moon", - json={"name": "Moon1" - }) + json={"name": "Moon1" + }) assert response.status_code == 400 assert response.get_json() == {"message": "Planet invalid is invalid"} + def test_create_moons_by_a_non_existing_planet_id(client, saved_two_planets): response = client.post("/moons/100/moon", - json={"name": "Moon1" - }) + json={"name": "Moon1" + }) assert response.status_code == 404 assert response.get_json() == {"message": "Planet 100 not found"} + def test_validate_model_missing_moon_record(saved_two_moons): # Calling `validate_model` without being invoked by a route will # cause an `HTTPException` when an `abort` statement is reached From 30cc0a62f7a82da28da6bafc252e5a417c1b7883 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Sat, 7 Jan 2023 17:42:58 -0800 Subject: [PATCH 55/62] more tests added --- tests/test_routes.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_routes.py b/tests/test_routes.py index 34a447df6..f0eae5789 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -146,6 +146,23 @@ def test_get_planets_sort_planets_by_name_desc_order(saved_two_planets, client): assert response_body[0]["gravity"] == 3.721 assert response_body[0]["distance_from_earth"] == 60.81 +def test_get_planets_sort_planets_by_distance_from_earth_desc(saved_two_planets, client): + data = {"sort": "distance_from_earth:desc"} + response = client.get("/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Jupiter" + assert response_body[1]["description"] == "This is planet: Jupiter" + assert response_body[1]["gravity"] == 24.79 + assert response_body[1]["distance_from_earth"] == 467.64 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + def test_get_planets_filter_by_planet_name_return_Mars_only(saved_two_planets, client): data = {"name": "Mars"} From 43b49385d008d96d6e433b0ef304a66d85268ab1 Mon Sep 17 00:00:00 2001 From: Jennifer Dai Date: Sat, 7 Jan 2023 19:32:43 -0800 Subject: [PATCH 56/62] add one test and reviewed all tests --- tests/test_routes.py | 79 +++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/tests/test_routes.py b/tests/test_routes.py index f0eae5789..484ed9391 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -82,16 +82,17 @@ def test_get_planets_sort_desc_param_sort_by_name_desc(saved_two_planets, client response_body = response.get_json() assert response.status_code == 200 - assert response_body[1]["id"] == 2 - assert response_body[1]["name"] == "Jupiter" - assert response_body[1]["description"] == "This is planet: Jupiter" - assert response_body[1]["gravity"] == 24.79 - assert response_body[1]["distance_from_earth"] == 467.64 assert response_body[0]["id"] == 1 assert response_body[0]["name"] == "Mars" assert response_body[0]["description"] == "This is planet: Mars" assert response_body[0]["gravity"] == 3.721 assert response_body[0]["distance_from_earth"] == 60.81 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Jupiter" + assert response_body[1]["description"] == "This is planet: Jupiter" + assert response_body[1]["gravity"] == 24.79 + assert response_body[1]["distance_from_earth"] == 467.64 + def test_get_planets_sort_desc_param_sort_by_name_asc(saved_two_planets, client): @@ -146,6 +147,23 @@ def test_get_planets_sort_planets_by_name_desc_order(saved_two_planets, client): assert response_body[0]["gravity"] == 3.721 assert response_body[0]["distance_from_earth"] == 60.81 +def test_get_planets_sort_by_distance_from_earth_asc(saved_two_planets, client): + data = {"sort": "distance_from_earth:asc"} + response = client.get("/planets", query_string=data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Jupiter" + assert response_body[1]["description"] == "This is planet: Jupiter" + assert response_body[1]["gravity"] == 24.79 + assert response_body[1]["distance_from_earth"] == 467.64 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Mars" + assert response_body[0]["description"] == "This is planet: Mars" + assert response_body[0]["gravity"] == 3.721 + assert response_body[0]["distance_from_earth"] == 60.81 + def test_get_planets_sort_planets_by_distance_from_earth_desc(saved_two_planets, client): data = {"sort": "distance_from_earth:desc"} response = client.get("/planets") @@ -272,10 +290,10 @@ def test_get_planets_filter_by_planet_Mars_sort_by_gravity_desc(saved_three_plan def test_create_one_planet_return_201_successfully_created(client): response = client.post("/planets", - json={"name": "Venus", - "description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"name": "Venus", + "description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) response_body = response.get_json() assert response.status_code == 201 @@ -287,27 +305,27 @@ def test_create_one_planet_return_201_successfully_created(client): def test_create_one_planet_no_name_return_400(client): response = client.post("/planets", - json={"description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 def test_create_one_planet_no_description_return_400(client): response = client.post("/planets", - json={"name": "Mars", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"name": "Mars", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 def test_create_one_planet_no_gravity_return_400(client): test_data = {"name": "Mars", - "description": "This is planet: Venus", - "distance_from_earth": 67.685 - } + "description": "This is planet: Venus", + "distance_from_earth": 67.685 + } response = client.post("/planets", json=test_data) assert response.status_code == 400 @@ -331,7 +349,7 @@ def test_create_one_planet_with_extra_keys_return_201(client, saved_two_planets) def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): resposne = client.put("/planets/1", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) @@ -343,7 +361,7 @@ def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, sa def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_planets): resposne = client.put("/planets/9", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) @@ -355,7 +373,7 @@ def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_pl def test_put_invalid_planet__id_return_400_invalid_error(client, saved_two_planets): resposne = client.put("/planets/invalid_id", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) @@ -463,7 +481,7 @@ def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_t def test_create_one_moon_return_201_successfully_created(client): response = client.post("/moons", - json={"name": "Moon3"}) + json={"name": "Moon3"}) response_body = response.get_json() assert response.status_code == 201 @@ -472,9 +490,8 @@ def test_create_one_moon_return_201_successfully_created(client): def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): response = client.post("/moons/1/moon", - json={ - "name": "planet1_moon" - }) + json={ + "name": "planet1_moon"}) response_body = response.get_json() assert response.status_code == 201 @@ -499,10 +516,10 @@ def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_pla def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets, saved_two_moons): post_response = client.post("/moons/1/moon", json={"name": "Moon1" - }) + }) post_response = client.post("/moons/1/moon", json={"name": "Moon2" - }) + }) response = client.get("moons/1/moons") response_body = response.get_json() @@ -517,16 +534,16 @@ def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_plane def test_create_moons_by_invalid_planet_id(client, saved_two_planets): response = client.post("/moons/invalid/moon", - json={"name": "Moon1" - }) + json={"name": "Moon1" + }) assert response.status_code == 400 assert response.get_json() == {"message": "Planet invalid is invalid"} def test_create_moons_by_a_non_existing_planet_id(client, saved_two_planets): response = client.post("/moons/100/moon", - json={"name": "Moon1" - }) + json={"name": "Moon1" + }) assert response.status_code == 404 assert response.get_json() == {"message": "Planet 100 not found"} From 5ed19c28f95b9ae47dad53a1c51a0415c634bb73 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Sun, 8 Jan 2023 12:09:03 -0800 Subject: [PATCH 57/62] Add edge case handling to planet delete route --- app/planet_routes.py | 9 +++++++++ tests/conftest.py | 3 ++- tests/test_routes.py | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 46aef9990..fddef4286 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -1,6 +1,7 @@ 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") @@ -129,6 +130,14 @@ def update_planet_by_id(planet_id): 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() diff --git a/tests/conftest.py b/tests/conftest.py index 42947ebfc..44a489fba 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -88,4 +88,5 @@ def saved_two_moons(app): db.session.add_all([moon1, moon2]) db.session.commit() db.session.refresh(moon1, ["id"]) - db.session.refresh(moon2, ["id"]) \ No newline at end of file + db.session.refresh(moon2, ["id"]) + diff --git a/tests/test_routes.py b/tests/test_routes.py index 484ed9391..1eb4bff5b 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -405,7 +405,7 @@ def test_delete_planet_with_invalid_id_return_400_invalid_error(client, saved_tw assert response.status_code == 400 assert response_body == {"message": "Planet invalid_id is invalid"} - + def test_validate_model(saved_two_planets): result_planet = validate_model(Planet, 1) From 6e2dbc971078fc2f043844f78152b52b658f2f6b Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Sun, 8 Jan 2023 12:19:37 -0800 Subject: [PATCH 58/62] Additional tests added --- tests/test_routes.py | 71 +++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/tests/test_routes.py b/tests/test_routes.py index 1eb4bff5b..d6db1bf63 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -94,7 +94,6 @@ def test_get_planets_sort_desc_param_sort_by_name_desc(saved_two_planets, client assert response_body[1]["distance_from_earth"] == 467.64 - def test_get_planets_sort_desc_param_sort_by_name_asc(saved_two_planets, client): response = client.get("/planets?sort=asc") response_body = response.get_json() @@ -147,6 +146,7 @@ def test_get_planets_sort_planets_by_name_desc_order(saved_two_planets, client): assert response_body[0]["gravity"] == 3.721 assert response_body[0]["distance_from_earth"] == 60.81 + def test_get_planets_sort_by_distance_from_earth_asc(saved_two_planets, client): data = {"sort": "distance_from_earth:asc"} response = client.get("/planets", query_string=data) @@ -164,6 +164,7 @@ def test_get_planets_sort_by_distance_from_earth_asc(saved_two_planets, client): assert response_body[0]["gravity"] == 3.721 assert response_body[0]["distance_from_earth"] == 60.81 + def test_get_planets_sort_planets_by_distance_from_earth_desc(saved_two_planets, client): data = {"sort": "distance_from_earth:desc"} response = client.get("/planets") @@ -290,10 +291,10 @@ def test_get_planets_filter_by_planet_Mars_sort_by_gravity_desc(saved_three_plan def test_create_one_planet_return_201_successfully_created(client): response = client.post("/planets", - json={"name": "Venus", - "description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"name": "Venus", + "description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) response_body = response.get_json() assert response.status_code == 201 @@ -305,27 +306,27 @@ def test_create_one_planet_return_201_successfully_created(client): def test_create_one_planet_no_name_return_400(client): response = client.post("/planets", - json={"description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 def test_create_one_planet_no_description_return_400(client): response = client.post("/planets", - json={"name": "Mars", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"name": "Mars", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 def test_create_one_planet_no_gravity_return_400(client): test_data = {"name": "Mars", - "description": "This is planet: Venus", - "distance_from_earth": 67.685 - } + "description": "This is planet: Venus", + "distance_from_earth": 67.685 + } response = client.post("/planets", json=test_data) assert response.status_code == 400 @@ -349,7 +350,7 @@ def test_create_one_planet_with_extra_keys_return_201(client, saved_two_planets) def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, saved_two_planets): resposne = client.put("/planets/1", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) @@ -361,7 +362,7 @@ def test_put_planet_with_id_1_return_200_planet_successfully_replaced(client, sa def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_planets): resposne = client.put("/planets/9", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) @@ -373,7 +374,7 @@ def test_put_non_existing_planet_return_404_not_found_error(client, saved_two_pl def test_put_invalid_planet__id_return_400_invalid_error(client, saved_two_planets): resposne = client.put("/planets/invalid_id", - json={"name": "New Planet", + json={"name": "New Planet", "description": "This a New Planet", "gravity": 20.0, "distance_from_earth": 55.99}) @@ -405,7 +406,23 @@ def test_delete_planet_with_invalid_id_return_400_invalid_error(client, saved_tw assert response.status_code == 400 assert response_body == {"message": "Planet invalid_id is invalid"} - + + +def test_delete_exist_planet_associate_moons_also_delete(client, saved_two_planets): + create_moon_to_planet = client.post("/moons/1/moon", + json={"name": "Moon1" + }) + delete_planet = client.delete("/planets/1") + delete_planet_response_body = delete_planet.get_json() + + get_moons = client.get("/moons") + get_moons_response_body = get_moons.get_json() + + assert delete_planet.status_code == 200 + assert delete_planet_response_body == "Planet: 1 has been deleted successfully." + assert get_moons.status_code == 200 + assert get_moons_response_body == [] + def test_validate_model(saved_two_planets): result_planet = validate_model(Planet, 1) @@ -481,7 +498,7 @@ def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_t def test_create_one_moon_return_201_successfully_created(client): response = client.post("/moons", - json={"name": "Moon3"}) + json={"name": "Moon3"}) response_body = response.get_json() assert response.status_code == 201 @@ -490,8 +507,8 @@ def test_create_one_moon_return_201_successfully_created(client): def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): response = client.post("/moons/1/moon", - json={ - "name": "planet1_moon"}) + json={ + "name": "planet1_moon"}) response_body = response.get_json() assert response.status_code == 201 @@ -516,10 +533,10 @@ def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_pla def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets, saved_two_moons): post_response = client.post("/moons/1/moon", json={"name": "Moon1" - }) + }) post_response = client.post("/moons/1/moon", json={"name": "Moon2" - }) + }) response = client.get("moons/1/moons") response_body = response.get_json() @@ -534,16 +551,16 @@ def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_plane def test_create_moons_by_invalid_planet_id(client, saved_two_planets): response = client.post("/moons/invalid/moon", - json={"name": "Moon1" - }) + json={"name": "Moon1" + }) assert response.status_code == 400 assert response.get_json() == {"message": "Planet invalid is invalid"} def test_create_moons_by_a_non_existing_planet_id(client, saved_two_planets): response = client.post("/moons/100/moon", - json={"name": "Moon1" - }) + json={"name": "Moon1" + }) assert response.status_code == 404 assert response.get_json() == {"message": "Planet 100 not found"} From 2082ef4a6bbbe179d4812adaf58b22ccffaf34ab Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Sun, 8 Jan 2023 12:42:04 -0800 Subject: [PATCH 59/62] modified test. All tests passed --- tests/test_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_routes.py b/tests/test_routes.py index d6db1bf63..c18c6fd64 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -530,7 +530,7 @@ def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_pla assert response_body["moons"] == [] -def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets, saved_two_moons): +def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets): post_response = client.post("/moons/1/moon", json={"name": "Moon1" }) From 8869e2119368bdb54417b709b13d8adb8f71124f Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Mon, 9 Jan 2023 10:42:16 -0800 Subject: [PATCH 60/62] Project is completed --- tests/test_routes.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/test_routes.py b/tests/test_routes.py index c18c6fd64..53a0652db 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -498,7 +498,7 @@ def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_t def test_create_one_moon_return_201_successfully_created(client): response = client.post("/moons", - json={"name": "Moon3"}) + json={"name": "Moon3"}) response_body = response.get_json() assert response.status_code == 201 @@ -508,7 +508,7 @@ def test_create_one_moon_return_201_successfully_created(client): def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): response = client.post("/moons/1/moon", json={ - "name": "planet1_moon"}) + "name": "planet1_moon"}) response_body = response.get_json() assert response.status_code == 201 @@ -533,10 +533,10 @@ def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_pla def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets): post_response = client.post("/moons/1/moon", json={"name": "Moon1" - }) + }) post_response = client.post("/moons/1/moon", json={"name": "Moon2" - }) + }) response = client.get("moons/1/moons") response_body = response.get_json() @@ -550,17 +550,13 @@ def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_plane def test_create_moons_by_invalid_planet_id(client, saved_two_planets): - response = client.post("/moons/invalid/moon", - json={"name": "Moon1" - }) + response = client.post("/moons/invalid/moon", json={"name": "Moon1"}) assert response.status_code == 400 assert response.get_json() == {"message": "Planet invalid is invalid"} def test_create_moons_by_a_non_existing_planet_id(client, saved_two_planets): - response = client.post("/moons/100/moon", - json={"name": "Moon1" - }) + response = client.post("/moons/100/moon",json={"name": "Moon1"}) assert response.status_code == 404 assert response.get_json() == {"message": "Planet 100 not found"} From 379aa4892da6e213eaed7eadedf8d584399d3586 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Mon, 16 Jan 2023 15:24:34 -0800 Subject: [PATCH 61/62] Code fixed based on feedback --- app/__init__.py | 11 +++-- app/models/planet.py | 13 +++--- app/{ => routes}/moon_routes.py | 9 +--- app/{ => routes}/planet_routes.py | 71 +++++++++++++++---------------- tests/test_models.py | 2 +- tests/test_routes.py | 16 +++---- 6 files changed, 59 insertions(+), 63 deletions(-) rename app/{ => routes}/moon_routes.py (89%) rename app/{ => routes}/planet_routes.py (96%) diff --git a/app/__init__.py b/app/__init__.py index 77e712a51..315dbed4f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -12,14 +12,13 @@ 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['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") app.config["Testing"] = True else: - app.config['JSON_SORT_KEYS'] = False # Don't sort keys alphabetically - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_DATABASE_URI") db.init_app(app) @@ -27,8 +26,8 @@ def create_app(test_config=None): from app.models.planet import Planet from app.models.moon import Moon - from .planet_routes import planets_bp - from .moon_routes import moon_bp + from .routes.planet_routes import planets_bp + from .routes.moon_routes import moon_bp app.register_blueprint(planets_bp) app.register_blueprint(moon_bp) diff --git a/app/models/planet.py b/app/models/planet.py index ce0f8cd1c..1df3eac8e 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -21,14 +21,17 @@ def to_dict(self): moon_names = [] for moon in self.moons: moon_names.append(moon.name) - planet_as_dict["moons"] = moon_names + 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']) + 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 \ No newline at end of file diff --git a/app/moon_routes.py b/app/routes/moon_routes.py similarity index 89% rename from app/moon_routes.py rename to app/routes/moon_routes.py index 2f9e67d03..0f147883c 100644 --- a/app/moon_routes.py +++ b/app/routes/moon_routes.py @@ -2,7 +2,7 @@ from app import db from app.models.moon import Moon from app.models.planet import Planet -from app.planet_routes import validate_model +from app.routes.planet_routes import validate_model moon_bp = Blueprint("moons_bp", __name__, url_prefix="/moons") @@ -22,12 +22,7 @@ def create_new_moon(): def read_moon_by_id(moon_id): moon_response = validate_model(Moon, moon_id) - result_dict = { - "id": moon_response.id, - "name": moon_response.name, - } - - return make_response(jsonify(result_dict), 200) + return make_response(jsonify(moon_response.to_dict()), 200) @moon_bp.route("", methods=["GET"]) diff --git a/app/planet_routes.py b/app/routes/planet_routes.py similarity index 96% rename from app/planet_routes.py rename to app/routes/planet_routes.py index fddef4286..a8102262b 100644 --- a/app/planet_routes.py +++ b/app/routes/planet_routes.py @@ -5,38 +5,6 @@ planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") -#---------------------------------------------- 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 - # ---------------------------------------------- Route Functions ---------------------------------------------- @planets_bp.route("", methods = ["POST"]) def create_planet(): @@ -84,9 +52,7 @@ def read_planets(): abort(make_response("Too many parameters", 400)) # Sort records by client's request - if attribute == "name": - planet_query = sort_helper(planet_query, Planet.name, sort_method) - elif attribute == "distance_from_earth": + 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) @@ -141,4 +107,37 @@ def delete_planet_by_id(planet_id): db.session.delete(planet) db.session.commit() - return make_response(jsonify(f"Planet: {planet_id} has been deleted successfully."), 200) \ No newline at end of file + 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 diff --git a/tests/test_models.py b/tests/test_models.py index 3251760d9..a32ea0fae 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,6 @@ from app.models.planet import Planet from app.models.moon import Moon -from app.planet_routes import validate_model +from app.routes.planet_routes import validate_model import pytest def test_to_dict_no_missing_data(): diff --git a/tests/test_routes.py b/tests/test_routes.py index 53a0652db..20506d75d 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,6 +1,6 @@ import pytest from werkzeug.exceptions import HTTPException -from app.planet_routes import validate_model +from app.routes.planet_routes import validate_model from app.models.planet import Planet from app.models.moon import Moon @@ -306,18 +306,18 @@ def test_create_one_planet_return_201_successfully_created(client): def test_create_one_planet_no_name_return_400(client): response = client.post("/planets", - json={"description": "This is planet: Venus", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"description": "This is planet: Venus", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 def test_create_one_planet_no_description_return_400(client): response = client.post("/planets", - json={"name": "Mars", - "gravity": 9.87, - "distance_from_earth": 67.685}) + json={"name": "Mars", + "gravity": 9.87, + "distance_from_earth": 67.685}) assert response.status_code == 400 @@ -507,7 +507,7 @@ def test_create_one_moon_return_201_successfully_created(client): def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): response = client.post("/moons/1/moon", - json={ + json={ "name": "planet1_moon"}) response_body = response.get_json() From 2a7ea7dad90a4f8a9874fbcfa26a363770a3f3d2 Mon Sep 17 00:00:00 2001 From: Yufei Bao Date: Mon, 16 Jan 2023 15:26:47 -0800 Subject: [PATCH 62/62] Fixed test file based on instructor's feedback --- tests/test_moon_routes | 124 ++++++++++++++++++++++++++++++++++++++++ tests/test_routes.py | 125 ----------------------------------------- 2 files changed, 124 insertions(+), 125 deletions(-) create mode 100644 tests/test_moon_routes diff --git a/tests/test_moon_routes b/tests/test_moon_routes new file mode 100644 index 000000000..13332a228 --- /dev/null +++ b/tests/test_moon_routes @@ -0,0 +1,124 @@ +# --------------------------------------- +# --------------------------------------- +# ---------Moon route tests-------------- +# --------------------------------------- +# --------------------------------------- + +def test_get_moon_by_id_return_200_successful_code(client, saved_two_moons): + response = client.get("/moons/1") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["name"] == "Moon1" + + +def test_get_moon_by_not_exist_id_return_404(client): + response = client.get("/moons/1") + response_body = response.get_json() + + assert response.status_code == 404 + + +def test_get_moon_by_invalid_moon_id_return_400_bad_request_error(client, saved_two_moons): + response = client.get("/moons/hello") + response_body = response.get_json() + + assert response.status_code == 400 + assert response_body == {"message": "Moon hello is invalid"} + + +def test_get_all_moons_with_no_records_return_empty_array(client): + response = client.get("/moons") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == [] + + +def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_two_moons): + response = client.get("/moons") + response_body = response.get_json() + + assert response.status_code == 200 + assert len(response_body) == 2 + assert response_body[0]["id"] == 1 + assert response_body[0]["name"] == "Moon1" + assert response_body[1]["id"] == 2 + assert response_body[1]["name"] == "Moon2" + + +def test_create_one_moon_return_201_successfully_created(client): + response = client.post("/moons", + json={"name": "Moon3"}) + response_body = response.get_json() + + assert response.status_code == 201 + assert response_body == "Moon Moon3 successfully created." + + +def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): + response = client.post("/moons/1/moon", + json={ + "name": "planet1_moon"}) + response_body = response.get_json() + + assert response.status_code == 201 + assert response_body["name"] == "planet1_moon" + assert response_body["planet_id"] == 1 + assert response_body["planet"] == "Mars" + + +def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_planets): + response = client.get("moons/1/moons") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["id"] == 1 + assert response_body["name"] == "Mars" + assert response_body["description"] == "This is planet: Mars" + assert response_body["gravity"] == 3.721 + assert response_body["distance_from_earth"] == 60.81 + assert response_body["moons"] == [] + + +def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets): + post_response = client.post("/moons/1/moon", + json={"name": "Moon1" + }) + post_response = client.post("/moons/1/moon", + json={"name": "Moon2" + }) + response = client.get("moons/1/moons") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["id"] == 1 + assert response_body["name"] == "Mars" + assert response_body["description"] == "This is planet: Mars" + assert response_body["gravity"] == 3.721 + assert response_body["distance_from_earth"] == 60.81 + assert response_body["moons"] == ["Moon1", "Moon2"] + + +def test_create_moons_by_invalid_planet_id(client, saved_two_planets): + response = client.post("/moons/invalid/moon", json={"name": "Moon1"}) + assert response.status_code == 400 + assert response.get_json() == {"message": "Planet invalid is invalid"} + + +def test_create_moons_by_a_non_existing_planet_id(client, saved_two_planets): + response = client.post("/moons/100/moon",json={"name": "Moon1"}) + assert response.status_code == 404 + assert response.get_json() == {"message": "Planet 100 not found"} + + +def test_validate_model_missing_moon_record(saved_two_moons): + # Calling `validate_model` without being invoked by a route will + # cause an `HTTPException` when an `abort` statement is reached + with pytest.raises(HTTPException): + result_planet = validate_model(Moon, "3") + + +def test_validate_model_invalid_moon_id(saved_two_moons): + with pytest.raises(HTTPException): + result_planet = validate_model(Moon, "invalid_id") diff --git a/tests/test_routes.py b/tests/test_routes.py index 20506d75d..a8d9704a9 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -446,128 +446,3 @@ def test_validate_model_invalid_id(saved_two_planets): with pytest.raises(HTTPException): result_planet = validate_model(Planet, "invalid_id") -# --------------------------------------- -# --------------------------------------- -# ---------Moon route tests-------------- -# --------------------------------------- -# --------------------------------------- - - -def test_get_moon_by_id_return_200_successful_code(client, saved_two_moons): - response = client.get("/moons/1") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body["name"] == "Moon1" - - -def test_get_moon_by_not_exist_id_return_404(client): - response = client.get("/moons/1") - response_body = response.get_json() - - assert response.status_code == 404 - - -def test_get_moon_by_invalid_moon_id_return_400_bad_request_error(client, saved_two_moons): - response = client.get("/moons/hello") - response_body = response.get_json() - - assert response.status_code == 400 - assert response_body == {"message": "Moon hello is invalid"} - - -def test_get_all_moons_with_no_records_return_empty_array(client): - response = client.get("/moons") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body == [] - - -def test_get_all_moons_with_two_records_return_array_with_size_2(client, saved_two_moons): - response = client.get("/moons") - response_body = response.get_json() - - assert response.status_code == 200 - assert len(response_body) == 2 - assert response_body[0]["id"] == 1 - assert response_body[0]["name"] == "Moon1" - assert response_body[1]["id"] == 2 - assert response_body[1]["name"] == "Moon2" - - -def test_create_one_moon_return_201_successfully_created(client): - response = client.post("/moons", - json={"name": "Moon3"}) - response_body = response.get_json() - - assert response.status_code == 201 - assert response_body == "Moon Moon3 successfully created." - - -def test_create_moon_to_planet_by_planet_id(client, saved_two_planets): - response = client.post("/moons/1/moon", - json={ - "name": "planet1_moon"}) - response_body = response.get_json() - - assert response.status_code == 201 - assert response_body["name"] == "planet1_moon" - assert response_body["planet_id"] == 1 - assert response_body["planet"] == "Mars" - - -def test_get_moons_by_planet_id_return_empty_list_of_moons(client, saved_two_planets): - response = client.get("moons/1/moons") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body["id"] == 1 - assert response_body["name"] == "Mars" - assert response_body["description"] == "This is planet: Mars" - assert response_body["gravity"] == 3.721 - assert response_body["distance_from_earth"] == 60.81 - assert response_body["moons"] == [] - - -def test_get_moons_by_planet_id_return_list_of_two_moons(client, saved_two_planets): - post_response = client.post("/moons/1/moon", - json={"name": "Moon1" - }) - post_response = client.post("/moons/1/moon", - json={"name": "Moon2" - }) - response = client.get("moons/1/moons") - response_body = response.get_json() - - assert response.status_code == 200 - assert response_body["id"] == 1 - assert response_body["name"] == "Mars" - assert response_body["description"] == "This is planet: Mars" - assert response_body["gravity"] == 3.721 - assert response_body["distance_from_earth"] == 60.81 - assert response_body["moons"] == ["Moon1", "Moon2"] - - -def test_create_moons_by_invalid_planet_id(client, saved_two_planets): - response = client.post("/moons/invalid/moon", json={"name": "Moon1"}) - assert response.status_code == 400 - assert response.get_json() == {"message": "Planet invalid is invalid"} - - -def test_create_moons_by_a_non_existing_planet_id(client, saved_two_planets): - response = client.post("/moons/100/moon",json={"name": "Moon1"}) - assert response.status_code == 404 - assert response.get_json() == {"message": "Planet 100 not found"} - - -def test_validate_model_missing_moon_record(saved_two_moons): - # Calling `validate_model` without being invoked by a route will - # cause an `HTTPException` when an `abort` statement is reached - with pytest.raises(HTTPException): - result_planet = validate_model(Moon, "3") - - -def test_validate_model_invalid_moon_id(saved_two_moons): - with pytest.raises(HTTPException): - result_planet = validate_model(Moon, "invalid_id")