From fb053427dffc71b0a75e1424fa78f45144ca06f7 Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Wed, 14 Dec 2022 13:52:37 -0500 Subject: [PATCH 01/36] add app folder and planet class in routes.py --- app/routes.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..e10dc1f2c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,13 @@ from flask import Blueprint +class Planet: + def __init__(self, id, name, description, color): + self.id = id + self.name = name + self.description = description + self.color = color + +list_planets = [ + Planet(1, "Mercury", "is the smallest planet in the Solar System", "gray"), + Planet(3, "Earth", "The planet that we live on.", "blue") +] \ No newline at end of file From 2e959f86c8047d9d6c69f797691170ba5abb4679 Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Wed, 14 Dec 2022 14:01:27 -0500 Subject: [PATCH 02/36] added get_all_planets endpoint --- app/__init__.py | 3 +++ app/routes.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 70b4cabfe..ab9eee40e 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 e10dc1f2c..359b4784d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint +from flask import Blueprint, jsonify class Planet: def __init__(self, id, name, description, color): @@ -10,4 +10,19 @@ def __init__(self, id, name, description, color): list_planets = [ Planet(1, "Mercury", "is the smallest planet in the Solar System", "gray"), Planet(3, "Earth", "The planet that we live on.", "blue") -] \ No newline at end of file +] + +planets_bp = Blueprint("planets", __name__, url_prefix="/planets") + +@planets_bp.route("", methods=["GET"]) +def get_all_planets(): + planets_response = [] + for planet in list_planets: + planets_response.append({ + "id": planet.id, + "name": planet.name, + "description": planet.description, + "color": planet.color + }) + + return jsonify(planets_response) \ No newline at end of file From f83423666238be775fbacd65bc8ea002e0ff1b39 Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 15 Dec 2022 13:26:46 -0500 Subject: [PATCH 03/36] create endpoint for getting one planet by id --- app/routes.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/routes.py b/app/routes.py index 359b4784d..8751c56e2 100644 --- a/app/routes.py +++ b/app/routes.py @@ -7,6 +7,14 @@ def __init__(self, id, name, description, color): self.description = description self.color = color + def convert_planet_to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "color": self.color + } + list_planets = [ Planet(1, "Mercury", "is the smallest planet in the Solar System", "gray"), Planet(3, "Earth", "The planet that we live on.", "blue") @@ -18,11 +26,13 @@ def __init__(self, id, name, description, color): def get_all_planets(): planets_response = [] for planet in list_planets: - planets_response.append({ - "id": planet.id, - "name": planet.name, - "description": planet.description, - "color": planet.color - }) + planets_response.append(planet.convert_planet_to_dict()) + + return jsonify(planets_response) - return jsonify(planets_response) \ No newline at end of file +@planets_bp.route("/", methods=["GET"]) +def get_one_planet_by_id(planet_id): + planet_id = int(planet_id) + for planet in list_planets: + if planet.id == planet_id: + return planet.convert_planet_to_dict() From 721804df707dd84da50dce226d63ed455b997e04 Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 15 Dec 2022 13:53:02 -0500 Subject: [PATCH 04/36] handle invalid planet id requests --- app/routes.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/routes.py b/app/routes.py index 8751c56e2..afe0cc263 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 Planet: def __init__(self, id, name, description, color): @@ -30,9 +30,20 @@ def get_all_planets(): return jsonify(planets_response) -@planets_bp.route("/", methods=["GET"]) -def get_one_planet_by_id(planet_id): - planet_id = int(planet_id) + +def validate_id_return_planet(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response({"message": f"planet_id {planet_id} invalid."},400)) + for planet in list_planets: if planet.id == planet_id: - return planet.convert_planet_to_dict() + return planet + + abort(make_response({"message":f"planet_id {planet_id} not found."},404)) + +@planets_bp.route("/", methods=["GET"]) +def get_one_planet_by_id(planet_id): + planet = validate_id_return_planet(planet_id) + return planet.convert_planet_to_dict() From d9f4c75d2a28eb88f2ac361064a4fd0852b53574 Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 20 Dec 2022 11:58:45 -0600 Subject: [PATCH 05/36] added models folder, hooked up db --- app/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index ab9eee40e..2c305c2ea 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,9 +1,19 @@ from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate +db = SQLAlchemy() +migrate = Migrate() def create_app(test_config=None): app = Flask(__name__) + app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + app.config["SQLALCHEMY_DATABASE_URI"] = "postresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development" + + db.init_app(app) + migrate.init_app(app, db) + from .routes import planets_bp app.register_blueprint(planets_bp) From 1ff7357ff84c8f5704484dd1f144564692fbb37c Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 20 Dec 2022 12:06:14 -0600 Subject: [PATCH 06/36] define planet model --- app/routes.py | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 app/routes.py diff --git a/app/routes.py b/app/routes.py deleted file mode 100644 index afe0cc263..000000000 --- a/app/routes.py +++ /dev/null @@ -1,49 +0,0 @@ -from flask import Blueprint, jsonify, abort, make_response - -class Planet: - def __init__(self, id, name, description, color): - self.id = id - self.name = name - self.description = description - self.color = color - - def convert_planet_to_dict(self): - return { - "id": self.id, - "name": self.name, - "description": self.description, - "color": self.color - } - -list_planets = [ - Planet(1, "Mercury", "is the smallest planet in the Solar System", "gray"), - Planet(3, "Earth", "The planet that we live on.", "blue") -] - -planets_bp = Blueprint("planets", __name__, url_prefix="/planets") - -@planets_bp.route("", methods=["GET"]) -def get_all_planets(): - planets_response = [] - for planet in list_planets: - planets_response.append(planet.convert_planet_to_dict()) - - return jsonify(planets_response) - - -def validate_id_return_planet(planet_id): - try: - planet_id = int(planet_id) - except: - abort(make_response({"message": f"planet_id {planet_id} invalid."},400)) - - for planet in list_planets: - if planet.id == planet_id: - return planet - - abort(make_response({"message":f"planet_id {planet_id} not found."},404)) - -@planets_bp.route("/", methods=["GET"]) -def get_one_planet_by_id(planet_id): - planet = validate_id_return_planet(planet_id) - return planet.convert_planet_to_dict() From fc8a4f3c1557682b3552679f9f20639b9f7d14f2 Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 20 Dec 2022 12:27:41 -0600 Subject: [PATCH 07/36] add endpoint to create new planet --- app/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 2c305c2ea..e33377c78 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,12 +9,14 @@ def create_app(test_config=None): app = Flask(__name__) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False - app.config["SQLALCHEMY_DATABASE_URI"] = "postresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development" + app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development" + + from app.models.planet import Planet db.init_app(app) migrate.init_app(app, db) - from .routes import planets_bp + from .planet_routes import planets_bp app.register_blueprint(planets_bp) return app From dc7b9837ae2c25041f90bf777293ad409f866b31 Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 20 Dec 2022 12:33:42 -0600 Subject: [PATCH 08/36] debugging create planet endpoint --- app/models/__init__.py | 0 app/models/planet.py | 8 ++ app/planet_routes.py | 49 ++++++++++ migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ ...b0d037fd8680_add_planet_model_to_schema.py | 34 +++++++ 8 files changed, 257 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 create mode 100644 migrations/versions/b0d037fd8680_add_planet_model_to_schema.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..45fab1bdb --- /dev/null +++ b/app/models/planet.py @@ -0,0 +1,8 @@ +#get db access +from app import db + +class Planet(db.Model): + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + name = db.Column(db.String, nullable=False) + description = db.Column(db.String, nullable=False) + color = db.Column(db.String, nullable=False) \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py new file mode 100644 index 000000000..bc4d9882a --- /dev/null +++ b/app/planet_routes.py @@ -0,0 +1,49 @@ +from flask import Blueprint, jsonify, abort, make_response, request +from app import db +from app.models.planet import Planet + +planets_bp = Blueprint("planets", __name__, url_prefix="/planets") + +@planets_bp.route("", methods=["POST"]) +def create_planet(): + planet_data = request.get_json() + + new_planet = Planet( + name = planet_data["name"], + description = planet_data["description"], + color = planet_data["color"] + ) + + #add new planet to db + db.session.add(new_planet) + #commit new planet to db + db.session.commit() + + return make_response(f"Planet {new_planet.name} created", 201) + + + +#@planets_bp.route("", methods=["GET"]) +# def get_all_planets(): +# planets_response = [] +# for planet in list_planets: +# planets_response.append(planet.convert_planet_to_dict()) + +# return jsonify(planets_response) + + +# @planets_bp.route("/", methods=["GET"]) +# def get_one_planet_by_id(planet_id): +# planet = validate_id_return_planet(planet_id) +# return planet.convert_planet_to_dict() + + +# def validate_id_return_planet(planet_id): +# try: +# planet_id = int(planet_id) +# except: +# abort(make_response({"message": f"planet_id {planet_id} invalid."},400)) + +# for planet in list_planets: +# if planet.id == planet_id: +# return planet \ No newline at end of file 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/b0d037fd8680_add_planet_model_to_schema.py b/migrations/versions/b0d037fd8680_add_planet_model_to_schema.py new file mode 100644 index 000000000..60bef4d9f --- /dev/null +++ b/migrations/versions/b0d037fd8680_add_planet_model_to_schema.py @@ -0,0 +1,34 @@ +"""add Planet model to schema + +Revision ID: b0d037fd8680 +Revises: +Create Date: 2022-12-20 12:13:45.414393 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b0d037fd8680' +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=False), + sa.Column('description', sa.String(), nullable=False), + sa.Column('color', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('planet') + # ### end Alembic commands ### From 33574d8448141cb166876f8960dfd93546332f42 Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 20 Dec 2022 12:36:45 -0600 Subject: [PATCH 09/36] added new get all planets endpoint --- app/planet_routes.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index bc4d9882a..ee6436abd 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -23,13 +23,20 @@ def create_planet(): -#@planets_bp.route("", methods=["GET"]) -# def get_all_planets(): -# planets_response = [] -# for planet in list_planets: -# planets_response.append(planet.convert_planet_to_dict()) - -# return jsonify(planets_response) +@planets_bp.route("", methods=["GET"]) +def get_all_planets(): + planets = Planet.query.all() + planets_response = [] + + for planet in planets: + planets_response.append({ + "id": planet.id, + "name": planet.name, + "description": planet.description, + "color": planet.color + }) + + return jsonify(planets_response) # @planets_bp.route("/", methods=["GET"]) From e5848ef84faa57675bf0b0dd623f90dd7207580d Mon Sep 17 00:00:00 2001 From: mckay Date: Wed, 21 Dec 2022 12:31:18 -0600 Subject: [PATCH 10/36] added planet id validation and endpoint to replace data for 1 planet --- app/planet_routes.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index ee6436abd..cdcaba3f3 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -22,7 +22,6 @@ def create_planet(): return make_response(f"Planet {new_planet.name} created", 201) - @planets_bp.route("", methods=["GET"]) def get_all_planets(): planets = Planet.query.all() @@ -38,19 +37,37 @@ def get_all_planets(): return jsonify(planets_response) +def validate_id_return_planet(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response({"message": f"planet_id {planet_id} invalid."},400)) + + planet = Planet.query.get(planet_id) + + if not planet: + abort(make_response({"message": f"planet_id {planet_id} not found."},404)) + + return planet + + +@planets_bp.route("/", methods=["PUT"]) +def update_planet(planet_id): + planet = validate_id_return_planet(planet_id) + + request_body = request.get_json() + + planet.name = request_body["name"] + planet.description = request_body["description"] + planet.color = request_body["color"] + + db.session.commit() + + return make_response(f"Planet #{planet.id} successfully updated.", 200) + # @planets_bp.route("/", methods=["GET"]) # def get_one_planet_by_id(planet_id): # planet = validate_id_return_planet(planet_id) # return planet.convert_planet_to_dict() - -# def validate_id_return_planet(planet_id): -# try: -# planet_id = int(planet_id) -# except: -# abort(make_response({"message": f"planet_id {planet_id} invalid."},400)) - -# for planet in list_planets: -# if planet.id == planet_id: -# return planet \ No newline at end of file From 3ea2a574b88ae48ef8dcc97e414307f471f4f417 Mon Sep 17 00:00:00 2001 From: mckay Date: Wed, 21 Dec 2022 12:33:10 -0600 Subject: [PATCH 11/36] add endpoint for reading 1 planet by id --- app/planet_routes.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index cdcaba3f3..edf0db422 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -66,8 +66,14 @@ def update_planet(planet_id): return make_response(f"Planet #{planet.id} successfully updated.", 200) -# @planets_bp.route("/", methods=["GET"]) -# def get_one_planet_by_id(planet_id): -# planet = validate_id_return_planet(planet_id) -# return planet.convert_planet_to_dict() +@planets_bp.route("/", methods=["GET"]) +def get_one_planet_by_id(planet_id): + planet = validate_id_return_planet(planet_id) + return { + "id": planet.id, + "name": planet.name, + "description": planet.description, + "color": planet.color + } + From 897f7717f85af4b5ed89c13e144eef78dc0569d6 Mon Sep 17 00:00:00 2001 From: mckay Date: Wed, 21 Dec 2022 12:42:03 -0600 Subject: [PATCH 12/36] add endpoint to delete one planet by id --- app/planet_routes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/planet_routes.py b/app/planet_routes.py index edf0db422..3a4fcdad5 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -77,3 +77,11 @@ def get_one_planet_by_id(planet_id): } +@planets_bp.route("/", methods=["DELETE"]) +def delete_planet(planet_id): + planet = validate_id_return_planet(planet_id) + + db.session.delete(planet) + db.session.commit() + + return make_response(f"Planet #{planet.id} successfully deleted.", 200) \ No newline at end of file From 8907abe838aea630d94a3b40e4338c6eae199244 Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 22 Dec 2022 12:38:31 -0600 Subject: [PATCH 13/36] handle sorting query params --- app/planet_routes.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 3a4fcdad5..b6f4d445a 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -24,9 +24,16 @@ def create_planet(): @planets_bp.route("", methods=["GET"]) def get_all_planets(): - planets = Planet.query.all() - planets_response = [] + planets_query = Planet.query + sort_query = request.args.get("sort") + if sort_query == "asc": + planets_query = planets_query.order_by(Planet.name.asc()) + if sort_query == "desc": + planets_query = planets_query.order_by(Planet.name.desc()) + + planets = planets_query.all() + planets_response = [] for planet in planets: planets_response.append({ "id": planet.id, @@ -35,6 +42,19 @@ def get_all_planets(): "color": planet.color }) +# @planets_bp.route("", methods=["GET"]) +# def get_all_planets(): +# planets = Planet.query.all() +# planets_response = [] + +# for planet in planets: +# planets_response.append({ +# "id": planet.id, +# "name": planet.name, +# "description": planet.description, +# "color": planet.color +# }) + return jsonify(planets_response) def validate_id_return_planet(planet_id): From 975cdab9d41b72dd34fdf05821e5f82063f5ea3a Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 22 Dec 2022 12:52:07 -0600 Subject: [PATCH 14/36] handle query params for filtering by name --- app/planet_routes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/planet_routes.py b/app/planet_routes.py index b6f4d445a..b9734fd8f 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -25,6 +25,11 @@ def create_planet(): @planets_bp.route("", methods=["GET"]) def get_all_planets(): planets_query = Planet.query + + name_query = request.args.get("name") + if name_query: + planets_query = planets_query.filter(Planet.name.ilike(f"%{name_query}%")) + sort_query = request.args.get("sort") if sort_query == "asc": planets_query = planets_query.order_by(Planet.name.asc()) From 9f66c236fca3b0f46ae2eee62074735de386b352 Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 3 Jan 2023 14:56:07 -0500 Subject: [PATCH 15/36] created envars for testing and development environments --- app/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index e33377c78..5e5178f64 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,21 +1,30 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate +from dotenv import load_dotenv +import os db = SQLAlchemy() migrate = Migrate() +load_dotenv() def create_app(test_config=None): app = Flask(__name__) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False - app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development" - from app.models.planet import Planet + if not test_config: + app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_DATABASE_URI") + + else: + app.config["TESTING"] = True + app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") db.init_app(app) migrate.init_app(app, db) + from app.models.planet import Planet + from .planet_routes import planets_bp app.register_blueprint(planets_bp) From 5a398c8f216c139474e3d3fe6082104e648c7f7e Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 3 Jan 2023 14:59:21 -0500 Subject: [PATCH 16/36] create test folder --- tests/__init__.py | 0 tests/conftest.py | 0 tests/test_routes.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_routes.py 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..e69de29bb diff --git a/tests/test_routes.py b/tests/test_routes.py new file mode 100644 index 000000000..e69de29bb From 927e217e2c3e7a3f677aea08b209daa1cfcbab71 Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Tue, 3 Jan 2023 16:09:00 -0500 Subject: [PATCH 17/36] confirm conftest.py and test_routes.py test and run. --- tests/conftest.py | 43 +++++++++++++++++++++++++++++++++++++++++++ tests/test_routes.py | 9 +++++++++ 2 files changed, 52 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index e69de29bb..15f9498c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,43 @@ +import pytest +from app import create_app, db +from flask.signals import request_finished +from app.models.planet import Planet + +@pytest.fixture +def app(): + app = create_app({"TESTING": True}) + + @request_finished.connect_via(app) + def expire_session(sender, response, **extra): + db.session.remove() + + with app.app_context(): + db.create_all() + yield app + + with app.app_context(): + db.drop_all() + + +@pytest.fixture +def client(app): + return app.test_client() + +# def saved_two_planets(app): +# #Arrange +# planet_1 = Planet( +# name = "Mercury" +# color = "gray", +# description = "is the smallest planet in the Solar System", +# ) +# planet_2 = Planet( +# name = "Earth" +# color = "blue", +# description = "The planet that we live on", +# ) + +# #Act +# db.session.add([planet_1, planet_2]) +# db.session.commit() +# db.session.refresh(planet_1, ["id"]) +# db.session.refresh(planet_2, ["id"]) \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index e69de29bb..83b321143 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -0,0 +1,9 @@ +def test_get_planets_optional_query_empty_db_returns_empty_list(client): + # Act + response = client.get("/planets") + + # Assert + assert response.status_code == 200 + assert response.get_json() == [] + + From 4744c133099faf72ff01f6f26ea17b349a0bbf2b Mon Sep 17 00:00:00 2001 From: mckay Date: Tue, 3 Jan 2023 18:00:10 -0500 Subject: [PATCH 18/36] add tests for POST, PUT, DELETE requests --- app/planet_routes.py | 2 +- tests/test_routes.py | 69 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index b9734fd8f..10708652c 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -19,7 +19,7 @@ def create_planet(): #commit new planet to db db.session.commit() - return make_response(f"Planet {new_planet.name} created", 201) + return make_response(f"Planet {new_planet.name} created.", 201) @planets_bp.route("", methods=["GET"]) diff --git a/tests/test_routes.py b/tests/test_routes.py index 83b321143..f5a953bbe 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -7,3 +7,72 @@ def test_get_planets_optional_query_empty_db_returns_empty_list(client): assert response.get_json() == [] +def test_create_one_planet(client): + #Act + response = client.post("/planets", json={ + "name": "Mercury", + "description": "is the smallest planet in the Solar System", + "color": "gray" + }) + response_body = response.get_json() + + #Assert + assert response.status_code == 201 + assert response_body == "Planet Mercury created." + +def test_replace_one_planet(client, saved_two_planets): + #Arrange + test_data = { + "name": "Mars", + "description": "Still a planet in our hearts" + } + + #Act + response = client.put("/planets/1", json=test_data) + response_body = response.get_json() + + #Assert + assert response.status_code == 200 + assert response_body == "Planet #1 successfully updated." + +def test_replace_planet_id_not_found(client, saved_two_planets): + #Arrange + test_data = { + "name": "Mars", + "description": "Still a planet in our hearts" + } + + #Act + response = client.put("/planets/9", json=test_data) + response_body = response.get_json() + + #Assert + assert response.status_code == 404 + assert response_body == {"message": "planet_id 9 not found."} + +def test_delete_one_planet(client, saved_two_planets): + #Act + response = client.delete("/planets/1") + response_body = response.get_json() + + #Assert + assert response.status_code == 200 + assert response_body == "Planet #1 successfully deleted." + +def test_delete_planet_id_not_found(client, saved_two_planets): + #Act + response = client.delete("/planets/5") + response_body = response.get_json() + + #Assert + assert response.status_code == 404 + assert response_body == {"message": "planet_id 5 not found."} + +def test_delete_planet_invalid(client, saved_two_planets): + #Act + response = client.delete("/planets/cat") + response_body = response.get_json() + + #Assert + assert response.status_code == 400 + assert response_body == {"message": "planet_id cat is invalid."} \ No newline at end of file From 08314018be43dd8240705aa2dc02b53ff95197a1 Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Tue, 3 Jan 2023 18:55:47 -0500 Subject: [PATCH 19/36] add app folder and planet class in routes.py --- tests/conftest.py | 37 +++++++++++++++++--------------- tests/test_routes.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 15f9498c2..6c0ff0892 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,21 +23,24 @@ def expire_session(sender, response, **extra): def client(app): return app.test_client() -# def saved_two_planets(app): -# #Arrange -# planet_1 = Planet( -# name = "Mercury" -# color = "gray", -# description = "is the smallest planet in the Solar System", -# ) -# planet_2 = Planet( -# name = "Earth" -# color = "blue", -# description = "The planet that we live on", -# ) + +@pytest.fixture +def saved_two_planets(app): + #Arrange + planet_1 = Planet( + name = "Mercury", + color = "gray", + description = "is the smallest planet in the Solar System" + ) + planet_2 = Planet( + name = "Earth", + color = "blue", + description = "The planet that we live on" + ) -# #Act -# db.session.add([planet_1, planet_2]) -# db.session.commit() -# db.session.refresh(planet_1, ["id"]) -# db.session.refresh(planet_2, ["id"]) \ No newline at end of file + + db.session.add(planet_1) + db.session.add(planet_2) + db.session.commit() + db.session.refresh(planet_1, ["id"]) + db.session.refresh(planet_2, ["id"]) diff --git a/tests/test_routes.py b/tests/test_routes.py index 83b321143..ffcc7e7fe 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -7,3 +7,54 @@ def test_get_planets_optional_query_empty_db_returns_empty_list(client): assert response.get_json() == [] +# GET /planets/1 returns a 200 with a response +# body that matches our fixture +def test_get_one_planet(client, saved_two_planets): + #Act + response = client.get("/planets/1") + response_body = response.get_json() + + #Assert + assert response.status_code == 200 + assert response_body == { + "id" : 1, + "name" : "Mercury", + "color" : "gray", + "description" : "is the smallest planet in the Solar System" + } + +# GET /planets/1 with no data in test database +# (no fixture) returns a 404 +def test_get_one_no_data_planet(client): + #Act + response = client.get("/planets/1") + response_body = response.get_json() + + #Assert + assert response.status_code == 404 + assert response_body == {'message': 'planet_id 1 not found.'} + + +# GET /planets with valid test data (fixtures) +# returns a 200 with an array including appropriate test data +def test_get_all_planets_with_valid_data(client, saved_two_planets): + #Act + response = client.get("/planets") + response_body = response.get_json() + + #Assert + assert response.status_code == 200 + assert response_body ==[ + { + "id" : 1, + "name" : "Mercury", + "color" : "gray", + "description" : "is the smallest planet in the Solar System" + }, + { + "id" : 2, + "name" : "Earth", + "color" : "blue", + "description" : "The planet that we live on" + } + ] \ No newline at end of file From f52c79d051404275ddaa7c99c24954ef5112db1d Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Tue, 3 Jan 2023 21:35:50 -0500 Subject: [PATCH 20/36] change respose body to josnify. --- app/planet_routes.py | 6 +++--- tests/conftest.py | 2 ++ tests/test_routes.py | 8 +++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 10708652c..24f241844 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -19,7 +19,7 @@ def create_planet(): #commit new planet to db db.session.commit() - return make_response(f"Planet {new_planet.name} created.", 201) + return make_response(jsonify(f"Planet {new_planet.name} created."), 201) @planets_bp.route("", methods=["GET"]) @@ -88,7 +88,7 @@ def update_planet(planet_id): db.session.commit() - return make_response(f"Planet #{planet.id} successfully updated.", 200) + return make_response(jsonify(f"Planet #{planet.id} successfully updated."), 200) @planets_bp.route("/", methods=["GET"]) @@ -109,4 +109,4 @@ def delete_planet(planet_id): db.session.delete(planet) db.session.commit() - return make_response(f"Planet #{planet.id} successfully deleted.", 200) \ No newline at end of file + return make_response(jsonify(f"Planet #{planet.id} successfully deleted."), 200) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 6c0ff0892..89acad0b3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,3 +44,5 @@ def saved_two_planets(app): db.session.commit() db.session.refresh(planet_1, ["id"]) db.session.refresh(planet_2, ["id"]) + + diff --git a/tests/test_routes.py b/tests/test_routes.py index 76a20159b..c5bc24331 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -80,7 +80,8 @@ def test_replace_one_planet(client, saved_two_planets): #Arrange test_data = { "name": "Mars", - "description": "Still a planet in our hearts" + "description": "Still a planet in our hearts", + "color" : "Red" } #Act @@ -130,10 +131,11 @@ def test_delete_planet_id_not_found(client, saved_two_planets): def test_delete_planet_invalid(client, saved_two_planets): #Act + response = client.delete("/planets/cat") response_body = response.get_json() + # response_body = response.get_data(as_text=True) #Assert assert response.status_code == 400 - assert response_body == {"message": "planet_id cat is invalid."} - + assert response_body == {'message': 'planet_id cat invalid.'} \ No newline at end of file From 6e543a4e06b1919320e621290f32498f65fb5188 Mon Sep 17 00:00:00 2001 From: mckay Date: Wed, 4 Jan 2023 13:10:23 -0500 Subject: [PATCH 21/36] refactored Planet class to include to_dict method --- app/models/planet.py | 10 +++++++++- app/planet_routes.py | 16 +++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 45fab1bdb..c7a340c0a 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -5,4 +5,12 @@ class Planet(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False) - color = db.Column(db.String, nullable=False) \ No newline at end of file + color = db.Column(db.String, nullable=False) + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "color": self.color + } \ No newline at end of file diff --git a/app/planet_routes.py b/app/planet_routes.py index 24f241844..3848bb16f 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -40,12 +40,7 @@ def get_all_planets(): planets_response = [] for planet in planets: - planets_response.append({ - "id": planet.id, - "name": planet.name, - "description": planet.description, - "color": planet.color - }) + planets_response.append(planet.to_dict()) # @planets_bp.route("", methods=["GET"]) # def get_all_planets(): @@ -66,7 +61,7 @@ def validate_id_return_planet(planet_id): try: planet_id = int(planet_id) except: - abort(make_response({"message": f"planet_id {planet_id} invalid."},400)) + abort(make_response(jsonify({"message": f"planet_id {planet_id} invalid."}),400)) planet = Planet.query.get(planet_id) @@ -94,12 +89,7 @@ def update_planet(planet_id): @planets_bp.route("/", methods=["GET"]) def get_one_planet_by_id(planet_id): planet = validate_id_return_planet(planet_id) - return { - "id": planet.id, - "name": planet.name, - "description": planet.description, - "color": planet.color - } + return planet.to_dict() @planets_bp.route("/", methods=["DELETE"]) From b3b8922e986917ad13ae11a0caa5a812f8fb3a18 Mon Sep 17 00:00:00 2001 From: mckay Date: Wed, 4 Jan 2023 13:33:42 -0500 Subject: [PATCH 22/36] add from_dict class method to Planet model --- app/models/planet.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/planet.py b/app/models/planet.py index c7a340c0a..cc392146f 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -13,4 +13,14 @@ def to_dict(self): "name": self.name, "description": self.description, "color": self.color - } \ No newline at end of file + } + + + @classmethod + def from_dict(cls, planet_data): + new_planet = Planet( + name = planet_data["name"], + description = planet_data["description"], + color = planet_data["color"] + ) + return new_planet \ No newline at end of file From 888875610407183c21f85458f3d13634042a29cd Mon Sep 17 00:00:00 2001 From: mckay Date: Wed, 4 Jan 2023 13:51:00 -0500 Subject: [PATCH 23/36] refactored validate_planet fn to more general validate_model fn --- app/planet_routes.py | 20 ++++++++++---------- tests/test_routes.py | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 3848bb16f..8ae8b98b3 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -57,23 +57,23 @@ def get_all_planets(): return jsonify(planets_response) -def validate_id_return_planet(planet_id): +def validate_model(cls, model_id): try: - planet_id = int(planet_id) + model_id = int(model_id) except: - abort(make_response(jsonify({"message": f"planet_id {planet_id} invalid."}),400)) + abort(make_response(jsonify({"message": f"{cls.__name__} {model_id} 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)) + if not model: + abort(make_response({"message": f"{cls.__name__} {model_id} not found."},404)) - return planet + return model @planets_bp.route("/", methods=["PUT"]) def update_planet(planet_id): - planet = validate_id_return_planet(planet_id) + planet = validate_model(Planet, planet_id) request_body = request.get_json() @@ -88,13 +88,13 @@ def update_planet(planet_id): @planets_bp.route("/", methods=["GET"]) def get_one_planet_by_id(planet_id): - planet = validate_id_return_planet(planet_id) + planet = validate_model(Planet, planet_id) return planet.to_dict() @planets_bp.route("/", methods=["DELETE"]) def delete_planet(planet_id): - planet = validate_id_return_planet(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 c5bc24331..94ae22ab5 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,3 +1,8 @@ +from werkzeug.exceptions import HTTPException +from app.planet_routes import validate_model +from app.models.planet import Planet +import pytest + def test_get_planets_optional_query_empty_db_returns_empty_list(client): # Act response = client.get("/planets") @@ -33,7 +38,7 @@ def test_get_one_no_data_planet(client): #Assert assert response.status_code == 404 - assert response_body == {'message': 'planet_id 1 not found.'} + assert response_body == {'message': 'Planet 1 not found.'} # GET /planets with valid test data (fixtures) @@ -106,7 +111,7 @@ def test_replace_planet_id_not_found(client, saved_two_planets): #Assert assert response.status_code == 404 - assert response_body == {"message": "planet_id 9 not found."} + assert response_body == {"message": "Planet 9 not found."} def test_delete_one_planet(client, saved_two_planets): @@ -126,7 +131,7 @@ def test_delete_planet_id_not_found(client, saved_two_planets): #Assert assert response.status_code == 404 - assert response_body == {"message": "planet_id 5 not found."} + assert response_body == {"message": "Planet 5 not found."} def test_delete_planet_invalid(client, saved_two_planets): @@ -138,4 +143,28 @@ def test_delete_planet_invalid(client, saved_two_planets): #Assert assert response.status_code == 400 - assert response_body == {'message': 'planet_id cat invalid.'} \ No newline at end of file + assert response_body == {'message': 'Planet cat invalid.'} + +def test_validate_planet(saved_two_planets): + # Act + result_planet = validate_model(Planet, 1) + + # Assert + assert result_planet.id == 1 + assert result_planet.name == "Mercury" + assert result_planet.color == "gray" + assert result_planet.description == "is the smallest planet in the Solar System" + +def test_validate_planet_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_planet_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, "cat") \ No newline at end of file From 4a701369199a90ba51c2d1cccf9e0e1aa6396f51 Mon Sep 17 00:00:00 2001 From: mckay Date: Wed, 4 Jan 2023 15:11:40 -0500 Subject: [PATCH 24/36] add test_models file --- tests/test_models.py | 164 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 tests/test_models.py diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 000000000..3a0637d65 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,164 @@ +# from werkzeug.exceptions import HTTPException +from app.models.planet import Planet +import pytest + +def test_to_dict_no_missing_data(): + #Arrange + test_data = Planet( + id = 1, + name = "Mercury", + color = "gray", + description = "is the smallest planet in the Solar System" + ) + + #Act + result = test_data.to_dict() + + #Assert + assert len(result) == 4 + assert result["id"] == 1 + assert result["name"] == "Mercury" + assert result["color"] == "gray" + assert result["description"] == "is the smallest planet in the Solar System" + +def test_to_dict_missing_name(): + #Arrange + test_data = Planet( + id = 1, + color = "gray", + description = "is the smallest planet in the Solar System" + ) + + #Act + result = test_data.to_dict() + + #Assert + assert len(result) == 4 + assert result["id"] == 1 + assert result["name"] is None + assert result["color"] == "gray" + assert result["description"] == "is the smallest planet in the Solar System" + +def test_to_dict_missing_color(): + #Arrange + test_data = Planet( + id = 1, + name = "Mercury", + description = "is the smallest planet in the Solar System" + ) + + #Act + result = test_data.to_dict() + + #Assert + assert len(result) == 4 + assert result["id"] == 1 + assert result["name"] == "Mercury" + assert result["color"] is None + assert result["description"] == "is the smallest planet in the Solar System" + +def test_to_dict_missing_description(): + #Arrange + test_data = Planet( + id = 1, + name = "Mercury", + color = "gray", + ) + + #Act + result = test_data.to_dict() + + #Assert + assert len(result) == 4 + assert result["id"] == 1 + assert result["name"] == "Mercury" + assert result["color"] == "gray" + assert result["description"] is None + +def test_to_dict_missing_id(): + #Arrange + test_data = Planet( + name = "Mercury", + color = "gray", + description = "is the smallest planet in the Solar System" + ) + + #Act + result = test_data.to_dict() + + #Assert + assert len(result) == 4 + assert result["id"] is None + assert result["name"] == "Mercury" + assert result["color"] == "gray" + assert result["description"] == "is the smallest planet in the Solar System" + + +def test_from_dict_return_planet(): + #Arrange + planet_data = { + "name": "Mercury", + "description": "is the smallest planet in the Solar System", + "color":"gray" + } + + #Act + new_planet = Planet.from_dict(planet_data) + + #Assert + assert planet_data.name == "Mercury" + assert planet_data.color == "gray" + assert planet_data.description == "is the smallest planet in the Solar System" + +def test_from_dict_missing_name(): + #Arrange + planet_data = { + "description": "is the smallest planet in the Solar System", + "color":"gray" + } + + #Act, Assert + with pytest.raises(KeyError, match = "name"): + new_planet = Planet.from_dict(planet_data) + +def test_from_dict_missing_color(): + #Arrange + planet_data = { + "name": "Mercury", + "description": "is the smallest planet in the Solar System", + } + + #Act, Assert + with pytest.raises(KeyError, match="color"): + new_planet = Planet.from_dict(planet_data) + + + +def test_from_dict_missing_description(): + #Arrange + planet_data = { + "name": "Mercury", + "color":"gray" + } + + #Act, Assert + with pytest.raises(KeyError, match="description"): + new_planet = Planet.from_dict(planet_data) + + +def test_from_dict_with_extra_key(): + #Arrange + planet_data = { + "name": "Mercury", + "description": "is the smallest planet in the Solar System", + "color":"gray", + "aliens": "yes" + } + + #Act + new_planet = Planet.from_dict(planet_data) + + #Assert + assert planet_data.name == "Mercury" + assert planet_data.color == "gray" + assert planet_data.description == "is the smallest planet in the Solar System" \ No newline at end of file From bc150814544c79ba9d6a8b15373d264f58992f1f Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Wed, 4 Jan 2023 17:26:41 -0500 Subject: [PATCH 25/36] from_dict pass test model --- tests/test_models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index 3a0637d65..8d314c2fb 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -106,9 +106,9 @@ def test_from_dict_return_planet(): new_planet = Planet.from_dict(planet_data) #Assert - assert planet_data.name == "Mercury" - assert planet_data.color == "gray" - assert planet_data.description == "is the smallest planet in the Solar System" + assert new_planet.name == "Mercury" + assert new_planet.color == "gray" + assert new_planet.description == "is the smallest planet in the Solar System" def test_from_dict_missing_name(): #Arrange @@ -159,6 +159,6 @@ def test_from_dict_with_extra_key(): new_planet = Planet.from_dict(planet_data) #Assert - assert planet_data.name == "Mercury" - assert planet_data.color == "gray" - assert planet_data.description == "is the smallest planet in the Solar System" \ No newline at end of file + assert new_planet.name == "Mercury" + assert new_planet.color == "gray" + assert new_planet.description == "is the smallest planet in the Solar System" \ No newline at end of file From 8a9b79522bf9793740080a2bf0f3c4ac39a2221e Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Wed, 4 Jan 2023 17:40:10 -0500 Subject: [PATCH 26/36] add from_dict refactor in planet_routes.py --- app/planet_routes.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 8ae8b98b3..947c975d7 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -8,11 +8,7 @@ def create_planet(): planet_data = request.get_json() - new_planet = Planet( - name = planet_data["name"], - description = planet_data["description"], - color = planet_data["color"] - ) + new_planet = Planet.from_dict(planet_data) #add new planet to db db.session.add(new_planet) From 80face33f75ac94849976ab32b547cb9d2bc94ca Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Wed, 4 Jan 2023 17:58:27 -0500 Subject: [PATCH 27/36] add validate_model and test --- app/planet_routes.py | 14 ++++++++------ tests/test_models.py | 30 ++++++++++++++++++++++++++++-- tests/test_routes.py | 8 ++++---- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/app/planet_routes.py b/app/planet_routes.py index 947c975d7..a135049c8 100644 --- a/app/planet_routes.py +++ b/app/planet_routes.py @@ -53,18 +53,20 @@ def get_all_planets(): return jsonify(planets_response) +# helper function def validate_model(cls, model_id): try: model_id = int(model_id) except: - abort(make_response(jsonify({"message": f"{cls.__name__} {model_id} invalid."}),400)) - + abort(make_response({"message" : f" {cls.__name__} {model_id} invalid."}, 400)) + model = cls.query.get(model_id) - - if not model: - abort(make_response({"message": f"{cls.__name__} {model_id} not found."},404)) + + if model: + return model - return model + abort(make_response({"message" : f" {cls.__name__} {model_id} not found."}, 404)) + @planets_bp.route("/", methods=["PUT"]) diff --git a/tests/test_models.py b/tests/test_models.py index 8d314c2fb..f12233cd4 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,12 @@ -# from werkzeug.exceptions import HTTPException +from app.planet_routes import validate_model +from werkzeug.exceptions import HTTPException from app.models.planet import Planet import pytest +######################## +# test to_dict function# +######################## + def test_to_dict_no_missing_data(): #Arrange test_data = Planet( @@ -94,6 +99,11 @@ def test_to_dict_missing_id(): assert result["description"] == "is the smallest planet in the Solar System" + +########################## +# test from_dict function# +########################## + def test_from_dict_return_planet(): #Arrange planet_data = { @@ -161,4 +171,20 @@ def test_from_dict_with_extra_key(): #Assert assert new_planet.name == "Mercury" assert new_planet.color == "gray" - assert new_planet.description == "is the smallest planet in the Solar System" \ No newline at end of file + assert new_planet.description == "is the smallest planet in the Solar System" + + + +######################## +# test to dict function# +######################## + +def test_validate_model(saved_two_planets): + # Act & Assert + with pytest.raises(HTTPException): + result_planet = validate_model(Planet, "3") + +def test_validate_model_missing_record(saved_two_planets): + # Act & Assert + with pytest.raises(HTTPException): + result_planet = validate_model(Planet, "cat") \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index 94ae22ab5..77e247d29 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -38,7 +38,7 @@ def test_get_one_no_data_planet(client): #Assert assert response.status_code == 404 - assert response_body == {'message': 'Planet 1 not found.'} + assert response_body == {'message': ' Planet 1 not found.'} # GET /planets with valid test data (fixtures) @@ -111,7 +111,7 @@ def test_replace_planet_id_not_found(client, saved_two_planets): #Assert assert response.status_code == 404 - assert response_body == {"message": "Planet 9 not found."} + assert response_body == {'message': ' Planet 9 not found.'} def test_delete_one_planet(client, saved_two_planets): @@ -131,7 +131,7 @@ def test_delete_planet_id_not_found(client, saved_two_planets): #Assert assert response.status_code == 404 - assert response_body == {"message": "Planet 5 not found."} + assert response_body == {"message": " Planet 5 not found."} def test_delete_planet_invalid(client, saved_two_planets): @@ -143,7 +143,7 @@ def test_delete_planet_invalid(client, saved_two_planets): #Assert assert response.status_code == 400 - assert response_body == {'message': 'Planet cat invalid.'} + assert response_body == {'message': ' Planet cat invalid.'} def test_validate_planet(saved_two_planets): # Act From ec94bf5e54f0016f8885ba0f39dc52cf6e0f400c Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Wed, 4 Jan 2023 18:01:59 -0500 Subject: [PATCH 28/36] fix typo --- tests/test_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index f12233cd4..8b5ed446e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -175,9 +175,9 @@ def test_from_dict_with_extra_key(): -######################## -# test to dict function# -######################## +############################### +# test validate_model function# +############################### def test_validate_model(saved_two_planets): # Act & Assert From 94e9e7d8f1b5ddb0a36575fa141dfeb2d5d5c622 Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 5 Jan 2023 13:55:39 -0500 Subject: [PATCH 29/36] add Moon model --- app/__init__.py | 1 + app/models/moon.py | 28 +++++++++++++++ app/models/planet.py | 1 + .../055ca4320138_update_moon_model.py | 36 +++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 app/models/moon.py create mode 100644 migrations/versions/055ca4320138_update_moon_model.py diff --git a/app/__init__.py b/app/__init__.py index 5e5178f64..979e6e9e6 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -24,6 +24,7 @@ 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 app.register_blueprint(planets_bp) diff --git a/app/models/moon.py b/app/models/moon.py new file mode 100644 index 000000000..a495ea190 --- /dev/null +++ b/app/models/moon.py @@ -0,0 +1,28 @@ +#get db access +from app import db + +class Moon(db.Model): + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + name = db.Column(db.String, nullable=False) + size = db.Column(db.Integer, nullable=False) + description = db.Column(db.String, nullable=False) + planet_id = db.Column(db.Integer, db.ForeignKey("planet.id")) + planet = db.relationship("Planet", back_populates="moons") + + # def to_dict(self): + # return { + # "id": self.id, + # "name": self.name, + # "description": self.description, + # "color": self.color + # } + + + # @classmethod + # def from_dict(cls, planet_data): + # new_planet = Planet( + # name = planet_data["name"], + # description = planet_data["description"], + # color = planet_data["color"] + # ) + # return new_planet \ No newline at end of file diff --git a/app/models/planet.py b/app/models/planet.py index cc392146f..548d50cd4 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -6,6 +6,7 @@ class Planet(db.Model): name = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False) color = db.Column(db.String, nullable=False) + moons = db.relationship("Moon", back_populates="planet") def to_dict(self): return { diff --git a/migrations/versions/055ca4320138_update_moon_model.py b/migrations/versions/055ca4320138_update_moon_model.py new file mode 100644 index 000000000..2e2167a4f --- /dev/null +++ b/migrations/versions/055ca4320138_update_moon_model.py @@ -0,0 +1,36 @@ +"""update Moon model + +Revision ID: 055ca4320138 +Revises: b0d037fd8680 +Create Date: 2023-01-05 13:51:50.810923 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '055ca4320138' +down_revision = 'b0d037fd8680' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('moon', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('size', sa.Integer(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + 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') + # ### end Alembic commands ### From 4aed045ac18f8236efcbfd589f3586ad55781e7c Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 5 Jan 2023 13:59:09 -0500 Subject: [PATCH 30/36] create and register Moon blueprint --- app/__init__.py | 4 +- app/planet_routes.py | 100 ------------------------------------------- 2 files changed, 3 insertions(+), 101 deletions(-) delete mode 100644 app/planet_routes.py diff --git a/app/__init__.py b/app/__init__.py index 979e6e9e6..489328625 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -26,7 +26,9 @@ 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 .routes.planet_routes import planets_bp + from .routes.moon_routes import moons_bp app.register_blueprint(planets_bp) + app.register_blueprint(moons_bp) return app diff --git a/app/planet_routes.py b/app/planet_routes.py deleted file mode 100644 index a135049c8..000000000 --- a/app/planet_routes.py +++ /dev/null @@ -1,100 +0,0 @@ -from flask import Blueprint, jsonify, abort, make_response, request -from app import db -from app.models.planet import Planet - -planets_bp = Blueprint("planets", __name__, url_prefix="/planets") - -@planets_bp.route("", methods=["POST"]) -def create_planet(): - planet_data = request.get_json() - - new_planet = Planet.from_dict(planet_data) - - #add new planet to db - db.session.add(new_planet) - #commit new planet to db - db.session.commit() - - return make_response(jsonify(f"Planet {new_planet.name} created."), 201) - - -@planets_bp.route("", methods=["GET"]) -def get_all_planets(): - planets_query = Planet.query - - name_query = request.args.get("name") - if name_query: - planets_query = planets_query.filter(Planet.name.ilike(f"%{name_query}%")) - - sort_query = request.args.get("sort") - if sort_query == "asc": - planets_query = planets_query.order_by(Planet.name.asc()) - if sort_query == "desc": - planets_query = planets_query.order_by(Planet.name.desc()) - - planets = planets_query.all() - - planets_response = [] - for planet in planets: - planets_response.append(planet.to_dict()) - -# @planets_bp.route("", methods=["GET"]) -# def get_all_planets(): -# planets = Planet.query.all() -# planets_response = [] - -# for planet in planets: -# planets_response.append({ -# "id": planet.id, -# "name": planet.name, -# "description": planet.description, -# "color": planet.color -# }) - - return jsonify(planets_response) - -# helper function -def validate_model(cls, model_id): - try: - model_id = int(model_id) - except: - abort(make_response({"message" : f" {cls.__name__} {model_id} invalid."}, 400)) - - model = cls.query.get(model_id) - - if model: - return model - - abort(make_response({"message" : f" {cls.__name__} {model_id} not found."}, 404)) - - - -@planets_bp.route("/", methods=["PUT"]) -def update_planet(planet_id): - planet = validate_model(Planet, planet_id) - - request_body = request.get_json() - - planet.name = request_body["name"] - planet.description = request_body["description"] - planet.color = request_body["color"] - - db.session.commit() - - return make_response(jsonify(f"Planet #{planet.id} successfully updated."), 200) - - -@planets_bp.route("/", methods=["GET"]) -def get_one_planet_by_id(planet_id): - planet = validate_model(Planet, planet_id) - return planet.to_dict() - - -@planets_bp.route("/", methods=["DELETE"]) -def delete_planet(planet_id): - planet = validate_model(Planet, planet_id) - - db.session.delete(planet) - db.session.commit() - - return make_response(jsonify(f"Planet #{planet.id} successfully deleted."), 200) \ No newline at end of file From 8ced999e1568f7817bd44adc05bf3751f15a6856 Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 5 Jan 2023 14:06:56 -0500 Subject: [PATCH 31/36] update to_dict Planet method --- app/models/planet.py | 3 +- app/routes/moon_routes.py | 5 ++ app/routes/planet_routes.py | 100 ++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 app/routes/moon_routes.py create mode 100644 app/routes/planet_routes.py diff --git a/app/models/planet.py b/app/models/planet.py index 548d50cd4..6d7887a96 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -13,7 +13,8 @@ def to_dict(self): "id": self.id, "name": self.name, "description": self.description, - "color": self.color + "color": self.color, + "moons": [moon.name for moon in self.moons] } diff --git a/app/routes/moon_routes.py b/app/routes/moon_routes.py new file mode 100644 index 000000000..6078fe495 --- /dev/null +++ b/app/routes/moon_routes.py @@ -0,0 +1,5 @@ +from flask import Blueprint, jsonify, abort, make_response, request +from app import db +from app.models.moon import Moon + +moons_bp = Blueprint("moons", __name__, url_prefix="/moons") diff --git a/app/routes/planet_routes.py b/app/routes/planet_routes.py new file mode 100644 index 000000000..a135049c8 --- /dev/null +++ b/app/routes/planet_routes.py @@ -0,0 +1,100 @@ +from flask import Blueprint, jsonify, abort, make_response, request +from app import db +from app.models.planet import Planet + +planets_bp = Blueprint("planets", __name__, url_prefix="/planets") + +@planets_bp.route("", methods=["POST"]) +def create_planet(): + planet_data = request.get_json() + + new_planet = Planet.from_dict(planet_data) + + #add new planet to db + db.session.add(new_planet) + #commit new planet to db + db.session.commit() + + return make_response(jsonify(f"Planet {new_planet.name} created."), 201) + + +@planets_bp.route("", methods=["GET"]) +def get_all_planets(): + planets_query = Planet.query + + name_query = request.args.get("name") + if name_query: + planets_query = planets_query.filter(Planet.name.ilike(f"%{name_query}%")) + + sort_query = request.args.get("sort") + if sort_query == "asc": + planets_query = planets_query.order_by(Planet.name.asc()) + if sort_query == "desc": + planets_query = planets_query.order_by(Planet.name.desc()) + + planets = planets_query.all() + + planets_response = [] + for planet in planets: + planets_response.append(planet.to_dict()) + +# @planets_bp.route("", methods=["GET"]) +# def get_all_planets(): +# planets = Planet.query.all() +# planets_response = [] + +# for planet in planets: +# planets_response.append({ +# "id": planet.id, +# "name": planet.name, +# "description": planet.description, +# "color": planet.color +# }) + + return jsonify(planets_response) + +# helper function +def validate_model(cls, model_id): + try: + model_id = int(model_id) + except: + abort(make_response({"message" : f" {cls.__name__} {model_id} invalid."}, 400)) + + model = cls.query.get(model_id) + + if model: + return model + + abort(make_response({"message" : f" {cls.__name__} {model_id} not found."}, 404)) + + + +@planets_bp.route("/", methods=["PUT"]) +def update_planet(planet_id): + planet = validate_model(Planet, planet_id) + + request_body = request.get_json() + + planet.name = request_body["name"] + planet.description = request_body["description"] + planet.color = request_body["color"] + + db.session.commit() + + return make_response(jsonify(f"Planet #{planet.id} successfully updated."), 200) + + +@planets_bp.route("/", methods=["GET"]) +def get_one_planet_by_id(planet_id): + planet = validate_model(Planet, planet_id) + return planet.to_dict() + + +@planets_bp.route("/", methods=["DELETE"]) +def delete_planet(planet_id): + planet = validate_model(Planet, planet_id) + + db.session.delete(planet) + db.session.commit() + + return make_response(jsonify(f"Planet #{planet.id} successfully deleted."), 200) \ No newline at end of file From 7e7e296c1d0237c6edb69e6f011b2befbec4b6d0 Mon Sep 17 00:00:00 2001 From: mckay Date: Thu, 5 Jan 2023 14:22:27 -0500 Subject: [PATCH 32/36] create to_dict instance method and from_dict class me thod for Moon model --- app/models/moon.py | 32 +++++++++++++++++--------------- tests/test_models.py | 2 +- tests/test_routes.py | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/models/moon.py b/app/models/moon.py index a495ea190..8c43585fa 100644 --- a/app/models/moon.py +++ b/app/models/moon.py @@ -9,20 +9,22 @@ class Moon(db.Model): planet_id = db.Column(db.Integer, db.ForeignKey("planet.id")) planet = db.relationship("Planet", back_populates="moons") - # def to_dict(self): - # return { - # "id": self.id, - # "name": self.name, - # "description": self.description, - # "color": self.color - # } + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "size": self.size, + "description": self.description, + "planet": self.planet + } - # @classmethod - # def from_dict(cls, planet_data): - # new_planet = Planet( - # name = planet_data["name"], - # description = planet_data["description"], - # color = planet_data["color"] - # ) - # return new_planet \ No newline at end of file + @classmethod + def from_dict(cls, moon_data): + new_moon = Moon( + name = moon_data["name"], + size = moon_data["size"], + description = moon_data["description"], + planet = moon_data["planet"] + ) + return new_moon \ No newline at end of file diff --git a/tests/test_models.py b/tests/test_models.py index 8b5ed446e..d239446f3 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,4 +1,4 @@ -from app.planet_routes import validate_model +from app.routes.planet_routes import validate_model from werkzeug.exceptions import HTTPException from app.models.planet import Planet import pytest diff --git a/tests/test_routes.py b/tests/test_routes.py index 77e247d29..979b4f3d9 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,5 +1,5 @@ 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 import pytest From 3b764119e5470e7d9927547f9595c4cb6d1fb35b Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Thu, 5 Jan 2023 18:47:40 -0500 Subject: [PATCH 33/36] add nested roustes in planet_routes.py, routes_helper.py and modified tests. --- app/routes/planet_routes.py | 55 +++++++++++++++++++++++++------------ app/routes/routes_helper.py | 16 +++++++++++ tests/test_models.py | 24 +++++++++------- tests/test_routes.py | 9 ++++-- 4 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 app/routes/routes_helper.py diff --git a/app/routes/planet_routes.py b/app/routes/planet_routes.py index a135049c8..f86417097 100644 --- a/app/routes/planet_routes.py +++ b/app/routes/planet_routes.py @@ -1,6 +1,8 @@ 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 +from app.routes.routes_helper import validate_model planets_bp = Blueprint("planets", __name__, url_prefix="/planets") @@ -37,6 +39,8 @@ def get_all_planets(): planets_response = [] for planet in planets: planets_response.append(planet.to_dict()) + + return jsonify(planets_response) # @planets_bp.route("", methods=["GET"]) # def get_all_planets(): @@ -51,22 +55,7 @@ def get_all_planets(): # "color": planet.color # }) - return jsonify(planets_response) - -# helper function -def validate_model(cls, model_id): - try: - model_id = int(model_id) - except: - abort(make_response({"message" : f" {cls.__name__} {model_id} invalid."}, 400)) - - model = cls.query.get(model_id) - - if model: - return model - - abort(make_response({"message" : f" {cls.__name__} {model_id} not found."}, 404)) - +# return jsonify(planets_response) @planets_bp.route("/", methods=["PUT"]) @@ -97,4 +86,36 @@ def delete_planet(planet_id): db.session.delete(planet) db.session.commit() - return make_response(jsonify(f"Planet #{planet.id} successfully deleted."), 200) \ No newline at end of file + return make_response(jsonify(f"Planet #{planet.id} successfully deleted."), 200) + + +########################### +# nested routes with moon # +########################### + +# POST /planets//moons +@planets_bp.route("//moons", methods=["POST"]) +def create_new_moon_to_planet(planet_id): + planet = validate_model(Planet, planet_id) + + moon_data = request.get_json() + new_moon = Moon.from_dict(moon_data) + new_moon.planet = planet + + db.session.add(new_moon) + db.session.commit() + + message = f"Moon {new_moon.name} created and connect with a {new_moon.planet}." + return make_response(jsonify(message),201) + + +# GET /planets//moons +@planets_bp.route("//moons", methods=["GET"]) +def get_moons_by_planet_id(planet_id): + planet = validate_model(Planet, planet_id) + + planets_response = [planet.to_dict() for planet in planet.planets] + + return jsonify(planets_response) + + \ No newline at end of file diff --git a/app/routes/routes_helper.py b/app/routes/routes_helper.py new file mode 100644 index 000000000..604250b4d --- /dev/null +++ b/app/routes/routes_helper.py @@ -0,0 +1,16 @@ +from flask import abort, make_response + +# helper function +def validate_model(cls, model_id): + try: + model_id = int(model_id) + except: + abort(make_response({"message" : f" {cls.__name__} {model_id} invalid."}, 400)) + + model = cls.query.get(model_id) + + if model: + return model + + abort(make_response({"message" : f" {cls.__name__} {model_id} not found."}, 404)) + diff --git a/tests/test_models.py b/tests/test_models.py index d239446f3..35cb2b9cc 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -20,12 +20,13 @@ def test_to_dict_no_missing_data(): result = test_data.to_dict() #Assert - assert len(result) == 4 + assert len(result) == 5 assert result["id"] == 1 assert result["name"] == "Mercury" assert result["color"] == "gray" assert result["description"] == "is the smallest planet in the Solar System" - + assert result["moons"] == [] + def test_to_dict_missing_name(): #Arrange test_data = Planet( @@ -38,12 +39,13 @@ def test_to_dict_missing_name(): result = test_data.to_dict() #Assert - assert len(result) == 4 + assert len(result) == 5 assert result["id"] == 1 assert result["name"] is None assert result["color"] == "gray" assert result["description"] == "is the smallest planet in the Solar System" - + assert result["moons"] == [] + def test_to_dict_missing_color(): #Arrange test_data = Planet( @@ -56,12 +58,13 @@ def test_to_dict_missing_color(): result = test_data.to_dict() #Assert - assert len(result) == 4 + assert len(result) == 5 assert result["id"] == 1 assert result["name"] == "Mercury" assert result["color"] is None assert result["description"] == "is the smallest planet in the Solar System" - + assert result["moons"] == [] + def test_to_dict_missing_description(): #Arrange test_data = Planet( @@ -74,12 +77,13 @@ def test_to_dict_missing_description(): result = test_data.to_dict() #Assert - assert len(result) == 4 + assert len(result) == 5 assert result["id"] == 1 assert result["name"] == "Mercury" assert result["color"] == "gray" assert result["description"] is None - + assert result["moons"] == [] + def test_to_dict_missing_id(): #Arrange test_data = Planet( @@ -92,12 +96,12 @@ def test_to_dict_missing_id(): result = test_data.to_dict() #Assert - assert len(result) == 4 + assert len(result) == 5 assert result["id"] is None assert result["name"] == "Mercury" assert result["color"] == "gray" assert result["description"] == "is the smallest planet in the Solar System" - + assert result["moons"] == [] ########################## diff --git a/tests/test_routes.py b/tests/test_routes.py index 979b4f3d9..0ae36ea7d 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -26,7 +26,8 @@ def test_get_one_planet(client, saved_two_planets): "id" : 1, "name" : "Mercury", "color" : "gray", - "description" : "is the smallest planet in the Solar System" + "description" : "is the smallest planet in the Solar System", + "moons" : [] } # GET /planets/1 with no data in test database @@ -55,13 +56,15 @@ def test_get_all_planets_with_valid_data(client, saved_two_planets): "id" : 1, "name" : "Mercury", "color" : "gray", - "description" : "is the smallest planet in the Solar System" + "description" : "is the smallest planet in the Solar System", + "moons" : [] }, { "id" : 2, "name" : "Earth", "color" : "blue", - "description" : "The planet that we live on" + "description" : "The planet that we live on", + "moons" : [] } ] From 73aa8ce92bfffe08b98eb2cf3a852e7d3fa8ce20 Mon Sep 17 00:00:00 2001 From: mckay Date: Fri, 6 Jan 2023 09:38:13 -0500 Subject: [PATCH 34/36] refactored nested routes --- app/routes/planet_routes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/routes/planet_routes.py b/app/routes/planet_routes.py index f86417097..9e91e3b9a 100644 --- a/app/routes/planet_routes.py +++ b/app/routes/planet_routes.py @@ -99,8 +99,8 @@ def create_new_moon_to_planet(planet_id): planet = validate_model(Planet, planet_id) moon_data = request.get_json() + moon_data["planet"] = planet new_moon = Moon.from_dict(moon_data) - new_moon.planet = planet db.session.add(new_moon) db.session.commit() @@ -114,8 +114,8 @@ def create_new_moon_to_planet(planet_id): def get_moons_by_planet_id(planet_id): planet = validate_model(Planet, planet_id) - planets_response = [planet.to_dict() for planet in planet.planets] + moons_response = [] + for moon in planet.mooons: + moons_response.append(moon.to_dict()) - return jsonify(planets_response) - - \ No newline at end of file + return jsonify(moons_response) \ No newline at end of file From a6454f985aeef5a6f922e392782d09fee132f625 Mon Sep 17 00:00:00 2001 From: mckay Date: Fri, 6 Jan 2023 12:11:05 -0500 Subject: [PATCH 35/36] add routes for Moons --- app/routes/moon_routes.py | 44 +++++++++++++++++++++++++++++++++++++ app/routes/planet_routes.py | 11 +++++----- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/app/routes/moon_routes.py b/app/routes/moon_routes.py index 6078fe495..4dab980e7 100644 --- a/app/routes/moon_routes.py +++ b/app/routes/moon_routes.py @@ -1,5 +1,49 @@ 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 +from app.routes.routes_helper import validate_model moons_bp = Blueprint("moons", __name__, url_prefix="/moons") + +@moons_bp.route("", methods=["POST"]) +def create_moon(): + moon_data = request.get_json() + + new_moon = Moon.from_dict(moon_data) + + #add new moon to db + db.session.add(new_moon) + #commit new moon to db + db.session.commit() + + return make_response(jsonify(f"Moon {new_moon.name} created."), 201) + + +@moons_bp.route("", methods=["GET"]) +def get_all_moons(): + moons_query = Moon.query + + name_query = request.args.get("name") + if name_query: + moons_query = moons_query.filter(Moon.name.ilike(f"%{name_query}%")) + + sort_query = request.args.get("sort") + if sort_query == "asc": + moons_query = moons_query.order_by(Moon.name.asc()) + if sort_query == "desc": + moons_query = moons_query.order_by(Moon.name.desc()) + + moons = moons_query.all() + + moons_response = [] + for moon in moons: + moons_response.append(moon.to_dict()) + + return jsonify(moons_response) + + +@moons_bp.route("/", methods=["GET"]) +def get_one_moon_by_id(moon_id): + moon = validate_model(Moon, moon_id) + return moon.to_dict() \ No newline at end of file diff --git a/app/routes/planet_routes.py b/app/routes/planet_routes.py index 9e91e3b9a..3ad9a15ee 100644 --- a/app/routes/planet_routes.py +++ b/app/routes/planet_routes.py @@ -58,6 +58,11 @@ def get_all_planets(): # return jsonify(planets_response) +@planets_bp.route("/", methods=["GET"]) +def get_one_planet_by_id(planet_id): + planet = validate_model(Planet, planet_id) + return planet.to_dict() + @planets_bp.route("/", methods=["PUT"]) def update_planet(planet_id): planet = validate_model(Planet, planet_id) @@ -73,12 +78,6 @@ def update_planet(planet_id): return make_response(jsonify(f"Planet #{planet.id} successfully updated."), 200) -@planets_bp.route("/", methods=["GET"]) -def get_one_planet_by_id(planet_id): - planet = validate_model(Planet, planet_id) - return planet.to_dict() - - @planets_bp.route("/", methods=["DELETE"]) def delete_planet(planet_id): planet = validate_model(Planet, planet_id) From 7423f6d0fddb78fcd2cc92957467b092e48c2fbc Mon Sep 17 00:00:00 2001 From: Eva Liu Date: Sat, 7 Jan 2023 13:39:46 -0500 Subject: [PATCH 36/36] routes and models adjustments --- Procfile | 0 app/models/moon.py | 7 +++-- app/models/planet.py | 3 +- app/routes/moon_routes.py | 19 +++---------- app/routes/planet_routes.py | 12 ++++++-- requirements.txt | 8 ++++++ tests/conftest.py | 15 ++++++++++ tests/test_models.py | 6 ++-- tests/test_routes.py | 55 +++++++++++++++++++++++++++++++++++-- 9 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..e69de29bb diff --git a/app/models/moon.py b/app/models/moon.py index 8c43585fa..6d8f0005a 100644 --- a/app/models/moon.py +++ b/app/models/moon.py @@ -10,14 +10,15 @@ class Moon(db.Model): planet = db.relationship("Planet", back_populates="moons") def to_dict(self): - return { + moon_dict = { "id": self.id, "name": self.name, "size": self.size, "description": self.description, - "planet": self.planet + "planet_id": self.planet_id } - + + return moon_dict @classmethod def from_dict(cls, moon_data): diff --git a/app/models/planet.py b/app/models/planet.py index 6d7887a96..84b9023c3 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -23,6 +23,7 @@ def from_dict(cls, planet_data): new_planet = Planet( name = planet_data["name"], description = planet_data["description"], - color = planet_data["color"] + color = planet_data["color"], + moons = planet_data["moons"] ) return new_planet \ No newline at end of file diff --git a/app/routes/moon_routes.py b/app/routes/moon_routes.py index 4dab980e7..eba1f4b9c 100644 --- a/app/routes/moon_routes.py +++ b/app/routes/moon_routes.py @@ -6,22 +6,10 @@ moons_bp = Blueprint("moons", __name__, url_prefix="/moons") -@moons_bp.route("", methods=["POST"]) -def create_moon(): - moon_data = request.get_json() - - new_moon = Moon.from_dict(moon_data) - - #add new moon to db - db.session.add(new_moon) - #commit new moon to db - db.session.commit() - - return make_response(jsonify(f"Moon {new_moon.name} created."), 201) @moons_bp.route("", methods=["GET"]) -def get_all_moons(): +def get_all_moons_name(): moons_query = Moon.query name_query = request.args.get("name") @@ -38,12 +26,13 @@ def get_all_moons(): moons_response = [] for moon in moons: - moons_response.append(moon.to_dict()) + moons_response.append(moon.to_dict()["name"]) return jsonify(moons_response) + @moons_bp.route("/", methods=["GET"]) def get_one_moon_by_id(moon_id): moon = validate_model(Moon, moon_id) - return moon.to_dict() \ No newline at end of file + return moon.to_dict()["name"] \ No newline at end of file diff --git a/app/routes/planet_routes.py b/app/routes/planet_routes.py index 3ad9a15ee..75bbac50b 100644 --- a/app/routes/planet_routes.py +++ b/app/routes/planet_routes.py @@ -98,8 +98,14 @@ def create_new_moon_to_planet(planet_id): planet = validate_model(Planet, planet_id) moon_data = request.get_json() - moon_data["planet"] = planet - new_moon = Moon.from_dict(moon_data) + # new_moon = Moon.from_dict(moon_data) + # new_moon["planet"] = planet + new_moon = Moon( + name = moon_data["name"], + size = moon_data["size"], + description = moon_data["description"], + planet_id = planet.id + ) db.session.add(new_moon) db.session.commit() @@ -114,7 +120,7 @@ def get_moons_by_planet_id(planet_id): planet = validate_model(Planet, planet_id) moons_response = [] - for moon in planet.mooons: + for moon in planet.moons: moons_response.append(moon.to_dict()) return jsonify(moons_response) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fba2b3e38..f1c31b3f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,25 @@ alembic==1.5.4 +attrs==22.1.0 autopep8==1.5.5 blinker==1.4 certifi==2020.12.5 chardet==4.0.0 click==7.1.2 +coverage==6.5.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 diff --git a/tests/conftest.py b/tests/conftest.py index 89acad0b3..a30914788 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ from app import create_app, db from flask.signals import request_finished from app.models.planet import Planet +from app.models.moon import Moon @pytest.fixture def app(): @@ -46,3 +47,17 @@ def saved_two_planets(app): db.session.refresh(planet_2, ["id"]) + + +# moon_name = "Earth's moom" +# size = 1079 +# description = "the only place beyond Earth where humans have set foot." +# planet_id = 2 + +# @pytest.fixture +# def one_moont(app): + + + +# db.session.add(data) +# db.session.commit() \ No newline at end of file diff --git a/tests/test_models.py b/tests/test_models.py index 35cb2b9cc..abec4a4ca 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -113,7 +113,8 @@ def test_from_dict_return_planet(): planet_data = { "name": "Mercury", "description": "is the smallest planet in the Solar System", - "color":"gray" + "color":"gray", + "moons": [] } #Act @@ -166,7 +167,8 @@ def test_from_dict_with_extra_key(): "name": "Mercury", "description": "is the smallest planet in the Solar System", "color":"gray", - "aliens": "yes" + "aliens": "yes", + "moons": [] } #Act diff --git a/tests/test_routes.py b/tests/test_routes.py index 0ae36ea7d..0e0835699 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,8 +1,15 @@ from werkzeug.exceptions import HTTPException from app.routes.planet_routes import validate_model from app.models.planet import Planet +from app.models.moon import Moon import pytest + + +#################### +# Test get_planet # +#################### + def test_get_planets_optional_query_empty_db_returns_empty_list(client): # Act response = client.get("/planets") @@ -68,14 +75,17 @@ def test_get_all_planets_with_valid_data(client, saved_two_planets): } ] - +####################### +# Test create_planet # +####################### def test_create_one_planet(client): #Act response = client.post("/planets", json={ "name": "Mercury", "description": "is the smallest planet in the Solar System", - "color": "gray" + "color": "gray", + "moons":[] }) response_body = response.get_json() @@ -84,6 +94,10 @@ def test_create_one_planet(client): assert response_body == "Planet Mercury created." +####################### +# Test update_planet # +####################### + def test_replace_one_planet(client, saved_two_planets): #Arrange test_data = { @@ -117,6 +131,10 @@ def test_replace_planet_id_not_found(client, saved_two_planets): assert response_body == {'message': ' Planet 9 not found.'} +####################### +# Test delete_planet # +####################### + def test_delete_one_planet(client, saved_two_planets): #Act response = client.delete("/planets/1") @@ -148,6 +166,11 @@ def test_delete_planet_invalid(client, saved_two_planets): assert response.status_code == 400 assert response_body == {'message': ' Planet cat invalid.'} + +######################## +# Test validate_planet # +######################## + def test_validate_planet(saved_two_planets): # Act result_planet = validate_model(Planet, 1) @@ -170,4 +193,30 @@ def test_validate_planet_invalid_id(saved_two_planets): # 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, "cat") \ No newline at end of file + result_planet = validate_model(Planet, "cat") + +#################### +# Test Moon routes # +#################### + +def test_get_all_moons_with_empty_db_return_empty_list(client): + # Act + response = client.get("/moons") + + # Assert + assert response.status_code == 200 + assert response.get_json() == [] + + + +# def test_get_all_moons(client, one_moon_in_one_planet): +# #Act +# response = client.get("/moons") +# response_body = response.get_json() + +# #Assert +# assert response.status_code == 200 +# assert response_body == [ +# "Earth's moon", +# "Earth's sec moon" +# ] \ No newline at end of file