From d0c1880f05b31fc93b0632a09b7f8c6b0bf992e0 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Wed, 14 Dec 2022 13:56:22 -0500 Subject: [PATCH 01/30] added planets.py --- app/planets.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/planets.py diff --git a/app/planets.py b/app/planets.py new file mode 100644 index 000000000..d9d3ce941 --- /dev/null +++ b/app/planets.py @@ -0,0 +1,11 @@ +class Planet(): + def __init__(self, id, name, description): + self.id = id + self.name = name + self.description = description + +planets = [ + Planet(id = 3, name = "Earth", description = "habitable"), + Planet(id = 1, name = "Mercury", description = "inhabitable"), + Planet(id = 4, name = "Mars", description = "inhabitable") +] \ No newline at end of file From 4dcfc9b65bb6636d584d20724a695c610fc9ba73 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Wed, 14 Dec 2022 14:06:42 -0500 Subject: [PATCH 02/30] Created Planet class in planets.py --- app/planets.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/planets.py diff --git a/app/planets.py b/app/planets.py new file mode 100644 index 000000000..d9d3ce941 --- /dev/null +++ b/app/planets.py @@ -0,0 +1,11 @@ +class Planet(): + def __init__(self, id, name, description): + self.id = id + self.name = name + self.description = description + +planets = [ + Planet(id = 3, name = "Earth", description = "habitable"), + Planet(id = 1, name = "Mercury", description = "inhabitable"), + Planet(id = 4, name = "Mars", description = "inhabitable") +] \ No newline at end of file From f7c2f4ab27c1158334553d58cb52922722ad6b98 Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Wed, 14 Dec 2022 14:44:32 -0500 Subject: [PATCH 03/30] created planets blueprint --- app/__init__.py | 3 +++ app/planets.py | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 70b4cabfe..aa9cc052c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,5 +3,8 @@ def create_app(test_config=None): app = Flask(__name__) + + from .planets import planets_bp + app.register_blueprint(planets_bp) return app diff --git a/app/planets.py b/app/planets.py index d9d3ce941..f49635539 100644 --- a/app/planets.py +++ b/app/planets.py @@ -1,3 +1,5 @@ +from flask import Blueprint, jsonify + class Planet(): def __init__(self, id, name, description): self.id = id @@ -8,4 +10,19 @@ def __init__(self, id, name, description): Planet(id = 3, name = "Earth", description = "habitable"), Planet(id = 1, name = "Mercury", description = "inhabitable"), Planet(id = 4, name = "Mars", description = "inhabitable") -] \ No newline at end of file +] + +planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") + +@planets_bp.route("", methods=["GET"]) + +def get_all_planets(): + planet_response = [] + for planet in planets: + planet_response.append({ + "id": planet.id, + "name": planet.name, + "description": planet.description + }) + + return jsonify(planet_response) \ No newline at end of file From 7e71408ecbdd04c087dc8af53a51ca808032c8c6 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 15 Dec 2022 13:21:46 -0500 Subject: [PATCH 04/30] created to_dict method in Planet class --- app/planets.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/planets.py b/app/planets.py index f49635539..44e774bb8 100644 --- a/app/planets.py +++ b/app/planets.py @@ -6,12 +6,21 @@ def __init__(self, id, name, description): self.name = name self.description = description + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description + } + + planets = [ Planet(id = 3, name = "Earth", description = "habitable"), Planet(id = 1, name = "Mercury", description = "inhabitable"), Planet(id = 4, name = "Mars", description = "inhabitable") ] + planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") @planets_bp.route("", methods=["GET"]) @@ -19,10 +28,6 @@ def __init__(self, id, name, description): def get_all_planets(): planet_response = [] for planet in planets: - planet_response.append({ - "id": planet.id, - "name": planet.name, - "description": planet.description - }) + planet_response.append(planet.to_dict()) return jsonify(planet_response) \ No newline at end of file From c4ff0402f479692555802d3c9be81928e7e0f76e Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 15 Dec 2022 14:13:36 -0500 Subject: [PATCH 05/30] Added get_planet_by_id function and 400/404 error handling to planets.py --- app/planets.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/planets.py b/app/planets.py index 44e774bb8..3b1b89bd7 100644 --- a/app/planets.py +++ b/app/planets.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): @@ -30,4 +30,17 @@ def get_all_planets(): for planet in planets: planet_response.append(planet.to_dict()) - return jsonify(planet_response) \ No newline at end of file + return jsonify(planet_response) + +@planets_bp.route("/", methods=["GET"]) +def get_planet_by_id(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) + + for planet in planets: + if planet.id == planet_id: + return jsonify(planet.to_dict()) + + abort(make_response({"message":f"planet {planet_id} not found"}, 404)) \ No newline at end of file From 0d44f67dee3621f7dc5b11bed69b658c1272c38f Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 15 Dec 2022 14:26:31 -0500 Subject: [PATCH 06/30] updated get_planet_by_id function, created new validate_planet function that handles 400/404 errors --- app/planets.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/planets.py b/app/planets.py index 3b1b89bd7..4b9033ca3 100644 --- a/app/planets.py +++ b/app/planets.py @@ -20,11 +20,22 @@ def to_dict(self): Planet(id = 4, name = "Mars", description = "inhabitable") ] +def validate_planet(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) + + for planet in planets: + if planet.id == planet_id: + return planet + + abort(make_response({"message":f"planet {planet_id} not found"}, 404)) + planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") @planets_bp.route("", methods=["GET"]) - def get_all_planets(): planet_response = [] for planet in planets: @@ -34,13 +45,5 @@ def get_all_planets(): @planets_bp.route("/", methods=["GET"]) def get_planet_by_id(planet_id): - try: - planet_id = int(planet_id) - except: - abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) - - for planet in planets: - if planet.id == planet_id: - return jsonify(planet.to_dict()) - - abort(make_response({"message":f"planet {planet_id} not found"}, 404)) \ No newline at end of file + planet = validate_planet(planet_id) + return jsonify(planet.to_dict()) From 823a64a98cece61f5a0dfeac57b42316b3ee05ac Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Tue, 20 Dec 2022 14:24:01 -0500 Subject: [PATCH 07/30] set up Planet modeland database --- app/__init__.py | 13 +++ app/models/__init__.py | 0 app/models/planet.py | 7 ++ app/planets.py | 12 ++- migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ .../versions/224c8797ae08_add_planet_model.py | 34 +++++++ 9 files changed, 227 insertions(+), 5 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/224c8797ae08_add_planet_model.py diff --git a/app/__init__.py b/app/__init__.py index aa9cc052c..2b4842eba 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 +db = SQLAlchemy() +migrate = Migrate() 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' + + #import Planet model + from app.models.planet import Planet + + db.init_app(app) + migrate.init_app(app, db) + from .planets import planets_bp app.register_blueprint(planets_bp) 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..d6198c08e --- /dev/null +++ b/app/models/planet.py @@ -0,0 +1,7 @@ +from app import db + +class Planet(db.Model): + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + name = db.Column(db.String, nullable=False) + description = db.Column(db.String) + diameter_in_km = db.Column(db.Integer) \ No newline at end of file diff --git a/app/planets.py b/app/planets.py index 4b9033ca3..b8b34e4cc 100644 --- a/app/planets.py +++ b/app/planets.py @@ -1,23 +1,25 @@ from flask import Blueprint, jsonify, abort, make_response class Planet(): - def __init__(self, id, name, description): + def __init__(self, id, name, description, diameter): self.id = id self.name = name self.description = description + self.diameter = diameter def to_dict(self): return { "id": self.id, "name": self.name, - "description": self.description + "description": self.description, + "diameter": self.diameter } planets = [ - Planet(id = 3, name = "Earth", description = "habitable"), - Planet(id = 1, name = "Mercury", description = "inhabitable"), - Planet(id = 4, name = "Mars", description = "inhabitable") + Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), + Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879), + Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) ] def validate_planet(planet_id): 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/224c8797ae08_add_planet_model.py b/migrations/versions/224c8797ae08_add_planet_model.py new file mode 100644 index 000000000..745509c58 --- /dev/null +++ b/migrations/versions/224c8797ae08_add_planet_model.py @@ -0,0 +1,34 @@ +"""Add Planet model + +Revision ID: 224c8797ae08 +Revises: +Create Date: 2022-12-20 14:21:01.763332 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '224c8797ae08' +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=True), + sa.Column('diameter_in_km', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('planet') + # ### end Alembic commands ### From a1ba03e909e215c0c3956cc345eaabcfbe0045e3 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Tue, 20 Dec 2022 15:18:11 -0500 Subject: [PATCH 08/30] created a route that creates model records --- app/planets.py | 103 ++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/app/planets.py b/app/planets.py index 004d5a7eb..e4aa98786 100644 --- a/app/planets.py +++ b/app/planets.py @@ -1,65 +1,64 @@ -<<<<<<< HEAD -class Planet(): - def __init__(self, id, name, description): - self.id = id - self.name = name - self.description = description +from app import db +from app.models.planet import Planet +from flask import Blueprint, jsonify, abort, make_response, request -planets = [ - Planet(id = 3, name = "Earth", description = "habitable"), - Planet(id = 1, name = "Mercury", description = "inhabitable"), - Planet(id = 4, name = "Mars", description = "inhabitable") -] -======= -from flask import Blueprint, jsonify, abort, make_response +# class Planet(): +# def __init__(self, id, name, description, diameter): +# self.id = id +# self.name = name +# self.description = description +# self.diameter = diameter -class Planet(): - def __init__(self, id, name, description, diameter): - self.id = id - self.name = name - self.description = description - self.diameter = diameter + # def to_dict(self): + # return { + # "id": self.id, + # "name": self.name, + # "description": self.description, + # "diameter": self.diameter + # } - def to_dict(self): - return { - "id": self.id, - "name": self.name, - "description": self.description, - "diameter": self.diameter - } +# planets = [ +# Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), +# Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879), +# Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) +# ] -planets = [ - Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), - Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879), - Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) -] +# def validate_planet(planet_id): +# try: +# planet_id = int(planet_id) +# except: +# abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) -def validate_planet(planet_id): - try: - planet_id = int(planet_id) - except: - abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) +# for planet in planets: +# if planet.id == planet_id: +# return planet - for planet in planets: - if planet.id == planet_id: - return planet - - abort(make_response({"message":f"planet {planet_id} not found"}, 404)) +# abort(make_response({"message":f"planet {planet_id} not found"}, 404)) planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") -@planets_bp.route("", methods=["GET"]) -def get_all_planets(): - planet_response = [] - for planet in planets: - planet_response.append(planet.to_dict()) +# @planets_bp.route("", methods=["GET"]) +# def get_all_planets(): +# planet_response = [] +# for planet in planets: +# planet_response.append(planet.to_dict()) + +# return jsonify(planet_response) + +# @planets_bp.route("/", methods=["GET"]) +# def get_planet_by_id(planet_id): +# planet = validate_planet(planet_id) +# return jsonify(planet.to_dict()) - return jsonify(planet_response) +@planets_bp.route("", methods=["POST"]) +def create_planet(): + request_body = request.get_json() + new_planet = Planet(name = request_body["name"], + description = request_body["description"], + diameter_in_km = request_body["diameter_in_km"]) + db.session.add(new_planet) + db.session.commit() -@planets_bp.route("/", methods=["GET"]) -def get_planet_by_id(planet_id): - planet = validate_planet(planet_id) - return jsonify(planet.to_dict()) ->>>>>>> 842c63a0d2f27396fe7fc45303c97c355b4df6a1 + return make_response(f"Planet {new_planet.name} successfully created", 201) \ No newline at end of file From e7051b20669d34c4152f2e533c98eb619d41f648 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Tue, 20 Dec 2022 15:41:14 -0500 Subject: [PATCH 09/30] defined route that reads model records --- app/planets.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/planets.py b/app/planets.py index e4aa98786..8ab729fb0 100644 --- a/app/planets.py +++ b/app/planets.py @@ -55,10 +55,26 @@ @planets_bp.route("", methods=["POST"]) def create_planet(): request_body = request.get_json() + if "name" not in request_body or "description" not in request_body or 'diameter_in_km' not in request_body: + return make_response("Invalid Request", 400) + new_planet = Planet(name = request_body["name"], description = request_body["description"], diameter_in_km = request_body["diameter_in_km"]) db.session.add(new_planet) db.session.commit() - return make_response(f"Planet {new_planet.name} successfully created", 201) \ No newline at end of file + return make_response(f"Planet {new_planet.name} successfully created", 201) + +@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, + "diameter_in_km": planet.diameter_in_km + }) + return jsonify(planets_response) \ No newline at end of file From 99d983e401dd20c0dfffcaa96c1a5b548bcc2d7b Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Wed, 21 Dec 2022 13:53:41 -0500 Subject: [PATCH 10/30] created a GET endpoint for getting one planet --- app/planets.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/app/planets.py b/app/planets.py index 8ab729fb0..c07e5f0e2 100644 --- a/app/planets.py +++ b/app/planets.py @@ -24,17 +24,20 @@ # Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) # ] -# def validate_planet(planet_id): -# try: -# planet_id = int(planet_id) -# except: -# abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) +def validate_planet(planet_id): + try: + planet_id = int(planet_id) + except: + abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) + + planet = Planet.query.get(planet_id) -# for planet in planets: -# if planet.id == planet_id: -# return planet + if not planet: + abort(make_response({"message":f"planet {planet_id} not found"}, 404)) + + return planet -# abort(make_response({"message":f"planet {planet_id} not found"}, 404)) + planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") @@ -77,4 +80,14 @@ def get_all_planets(): "description": planet.description, "diameter_in_km": planet.diameter_in_km }) - return jsonify(planets_response) \ No newline at end of file + return jsonify(planets_response) + +@planets_bp.route("/", methods=["GET"]) +def get_planet_by_id(planet_id): + planet = validate_planet(planet_id) + return { + "id": planet.id, + "name": planet.name, + "description": planet.description, + "diameter": planet.diameter_in_km + } From 715df1cb366ce9c6f638611e12e763bd30146077 Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Wed, 21 Dec 2022 14:57:51 -0500 Subject: [PATCH 11/30] define update route --- app/__init__.py | 1 - app/planets.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 2b4842eba..50bd67c77 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,7 +11,6 @@ def create_app(test_config=None): app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development' - #import Planet model from app.models.planet import Planet db.init_app(app) diff --git a/app/planets.py b/app/planets.py index c07e5f0e2..cc37f15cd 100644 --- a/app/planets.py +++ b/app/planets.py @@ -91,3 +91,15 @@ def get_planet_by_id(planet_id): "description": planet.description, "diameter": planet.diameter_in_km } + +@planets_bp.route("/", methods=["PUT"]) +def update_planet(planet_id): + planet = validate_planet(planet_id) + request_body = request.get_json() + + planet.name = request_body["name"] + planet.description = request_body["description"] + planet.diameter_in_km = request_body["diameter_in_km"] + + db.session.commit() + return make_response(f"Planet {planet_id} successfully updated.") \ No newline at end of file From cc0275a540034163a79ebce9448411cdb1b7be29 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Wed, 21 Dec 2022 15:10:28 -0500 Subject: [PATCH 12/30] defined route that deletes a planet --- app/planets.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/planets.py b/app/planets.py index cc37f15cd..d7a2857b9 100644 --- a/app/planets.py +++ b/app/planets.py @@ -28,12 +28,12 @@ def validate_planet(planet_id): try: planet_id = int(planet_id) except: - abort(make_response({"message": f"Planet {planet_id} is not an int."}, 400)) + abort(make_response({"message": f"Planet #{planet_id} is not an int."}, 400)) planet = Planet.query.get(planet_id) if not planet: - abort(make_response({"message":f"planet {planet_id} not found"}, 404)) + abort(make_response({"message":f"Planet #{planet_id} not found"}, 404)) return planet @@ -102,4 +102,13 @@ def update_planet(planet_id): planet.diameter_in_km = request_body["diameter_in_km"] db.session.commit() - return make_response(f"Planet {planet_id} successfully updated.") \ No newline at end of file + return make_response(f"Planet #{planet_id} successfully updated.") + +@planets_bp.route("/", methods=["DELETE"]) +def delete_planet(planet_id): + planet = validate_planet(planet_id) + + db.session.delete(planet) + db.session.commit() + + return make_response(f"Planet #{planet_id} successfully deleted") \ No newline at end of file From 6a8add50a931b79b5411f96b29fc628c2e4a9ddb Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Thu, 22 Dec 2022 14:36:34 -0500 Subject: [PATCH 13/30] Added functionality to allow the user to make a request to both filter by name and sort in either ascending or descending order. --- app/planets.py | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/app/planets.py b/app/planets.py index d7a2857b9..5e3725b4b 100644 --- a/app/planets.py +++ b/app/planets.py @@ -1,6 +1,7 @@ from app import db from app.models.planet import Planet from flask import Blueprint, jsonify, abort, make_response, request +from sqlalchemy import desc, asc # class Planet(): # def __init__(self, id, name, description, diameter): @@ -9,13 +10,7 @@ # self.description = description # self.diameter = diameter - # def to_dict(self): - # return { - # "id": self.id, - # "name": self.name, - # "description": self.description, - # "diameter": self.diameter - # } + # planets = [ @@ -24,6 +19,14 @@ # Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) # ] +def to_dict(planet): + return { + "id": planet.id, + "name": planet.name, + "description": planet.description, + "diameter_in_km": planet.diameter_in_km + } + def validate_planet(planet_id): try: planet_id = int(planet_id) @@ -70,27 +73,29 @@ def create_planet(): return make_response(f"Planet {new_planet.name} successfully created", 201) @planets_bp.route("", methods=["GET"]) -def get_all_planets(): - planets = Planet.query.all() +def get_planets_optional_query(): + planet_query = Planet.query + name_query = request.args.get("name") + if name_query: + planet_query = planet_query.filter(Planet.name.ilike(f"%{name_query}%")) + + sort_query = request.args.get("sort") + if sort_query: + if sort_query == "desc": + planet_query = planet_query.order_by(desc("name")) + else: + planet_query = planet_query.order_by(asc("name")) + + planets = planet_query.all() planets_response = [] for planet in planets: - planets_response.append({ - "id": planet.id, - "name": planet.name, - "description": planet.description, - "diameter_in_km": planet.diameter_in_km - }) + planets_response.append(to_dict(planet)) return jsonify(planets_response) @planets_bp.route("/", methods=["GET"]) def get_planet_by_id(planet_id): planet = validate_planet(planet_id) - return { - "id": planet.id, - "name": planet.name, - "description": planet.description, - "diameter": planet.diameter_in_km - } + return to_dict(planet) @planets_bp.route("/", methods=["PUT"]) def update_planet(planet_id): From 55d4435bfc5e440759dcf895b624740c24724e27 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 22 Dec 2022 16:46:35 -0500 Subject: [PATCH 14/30] renamed planets.py to planets_routes.py and put routes back into this file from routes.py. Deleted routes.py. Added functionality that allows the user to sort by any column. --- app/__init__.py | 2 +- app/{planets.py => planets_routes.py} | 66 +++++++++++++-------------- app/routes.py | 2 - 3 files changed, 33 insertions(+), 37 deletions(-) rename app/{planets.py => planets_routes.py} (77%) delete mode 100644 app/routes.py diff --git a/app/__init__.py b/app/__init__.py index 50bd67c77..5a989dddd 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -16,7 +16,7 @@ def create_app(test_config=None): db.init_app(app) migrate.init_app(app, db) - from .planets import planets_bp + from .planets_routes import planets_bp app.register_blueprint(planets_bp) return app diff --git a/app/planets.py b/app/planets_routes.py similarity index 77% rename from app/planets.py rename to app/planets_routes.py index 5e3725b4b..ae34ca02d 100644 --- a/app/planets.py +++ b/app/planets_routes.py @@ -3,21 +3,21 @@ from flask import Blueprint, jsonify, abort, make_response, request from sqlalchemy import desc, asc -# class Planet(): -# def __init__(self, id, name, description, diameter): -# self.id = id -# self.name = name -# self.description = description -# self.diameter = diameter +# ---------helper functions-------- +def sort_planet_query(planet_query, sort_query, order_query): + if not sort_query: + sort_query = "name" + if not order_query: + order_query = "asc" + if order_query == "desc": + planet_query = planet_query.order_by(desc(sort_query)) + else: + planet_query = planet_query.order_by(asc(sort_query)) -# planets = [ -# Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), -# Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879), -# Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) -# ] + return planet_query def to_dict(planet): return { @@ -40,24 +40,10 @@ def validate_planet(planet_id): return planet - - +# ------------route implementations----------- planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") -# @planets_bp.route("", methods=["GET"]) -# def get_all_planets(): -# planet_response = [] -# for planet in planets: -# planet_response.append(planet.to_dict()) - -# return jsonify(planet_response) - -# @planets_bp.route("/", methods=["GET"]) -# def get_planet_by_id(planet_id): -# planet = validate_planet(planet_id) -# return jsonify(planet.to_dict()) - @planets_bp.route("", methods=["POST"]) def create_planet(): request_body = request.get_json() @@ -72,36 +58,40 @@ def create_planet(): return make_response(f"Planet {new_planet.name} successfully created", 201) + @planets_bp.route("", methods=["GET"]) def get_planets_optional_query(): planet_query = Planet.query name_query = request.args.get("name") + if name_query: planet_query = planet_query.filter(Planet.name.ilike(f"%{name_query}%")) - sort_query = request.args.get("sort") - if sort_query: - if sort_query == "desc": - planet_query = planet_query.order_by(desc("name")) - else: - planet_query = planet_query.order_by(asc("name")) + sort_query = request.args.get("sort") # what is being returned here? + order_query = request.args.get("order") + + if sort_query or order_query: + planet_query = sort_planet_query(planet_query, sort_query, order_query) planets = planet_query.all() planets_response = [] + for planet in planets: planets_response.append(to_dict(planet)) + return jsonify(planets_response) + @planets_bp.route("/", methods=["GET"]) def get_planet_by_id(planet_id): planet = validate_planet(planet_id) return to_dict(planet) + @planets_bp.route("/", methods=["PUT"]) def update_planet(planet_id): planet = validate_planet(planet_id) request_body = request.get_json() - planet.name = request_body["name"] planet.description = request_body["description"] planet.diameter_in_km = request_body["diameter_in_km"] @@ -109,11 +99,19 @@ def update_planet(planet_id): db.session.commit() return make_response(f"Planet #{planet_id} successfully updated.") + @planets_bp.route("/", methods=["DELETE"]) def delete_planet(planet_id): planet = validate_planet(planet_id) db.session.delete(planet) db.session.commit() + return make_response(f"Planet #{planet_id} successfully deleted") - return make_response(f"Planet #{planet_id} successfully deleted") \ No newline at end of file + +# --------------for testing----------------- +# planets = [ +# Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), +# Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879), +# Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) +# \ No newline at end of file diff --git a/app/routes.py b/app/routes.py deleted file mode 100644 index 8e9dfe684..000000000 --- a/app/routes.py +++ /dev/null @@ -1,2 +0,0 @@ -from flask import Blueprint - From 7eb943a9e0d5c624639bf7746977927cd5da23b6 Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Tue, 3 Jan 2023 15:00:30 -0500 Subject: [PATCH 15/30] commit before making a pull request --- app/planets.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/planets.py b/app/planets.py index 5e3725b4b..f81c05e5f 100644 --- a/app/planets.py +++ b/app/planets.py @@ -11,8 +11,6 @@ # self.diameter = diameter - - # planets = [ # Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), # Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879), @@ -40,9 +38,6 @@ def validate_planet(planet_id): return planet - - - planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") # @planets_bp.route("", methods=["GET"]) From 0f090cce05ac57c5a0f711612545847cae44cf57 Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Tue, 3 Jan 2023 15:32:31 -0500 Subject: [PATCH 16/30] created test database and .env and refactor create_app --- app/__init__.py | 9 ++++++++- app/planets_routes.py | 15 ++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 5a989dddd..07fc92161 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,15 +1,22 @@ 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' + if not test_config: + app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_DATABASE_URI') + else: + app.config["Testing"] = True + app.config['SQLALCHEMY_TEST_DATABASE_URI'] = os.environ.get('SQLALCHEMY_TEST_DATABASE_URI') from app.models.planet import Planet diff --git a/app/planets_routes.py b/app/planets_routes.py index 9f3b214ba..dd5579a85 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -9,13 +9,6 @@ def sort_planet_query(planet_query, sort_query, order_query): if not sort_query: sort_query = "name" -<<<<<<< HEAD:app/planets.py -# planets = [ -# Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), -# Planet(id = 1, name = "Mercury", description = "inhabitable", diameter = 4879), -# Planet(id = 4, name = "Mars", description = "inhabitable", diameter = 6792) -# ] -======= if not order_query: order_query = "asc" @@ -25,7 +18,6 @@ def sort_planet_query(planet_query, sort_query, order_query): planet_query = planet_query.order_by(asc(sort_query)) return planet_query ->>>>>>> 55d4435bfc5e440759dcf895b624740c24724e27:app/planets_routes.py def to_dict(planet): return { @@ -48,11 +40,8 @@ def validate_planet(planet_id): return planet -<<<<<<< HEAD:app/planets.py -======= -# ------------route implementations----------- ->>>>>>> 55d4435bfc5e440759dcf895b624740c24724e27:app/planets_routes.py +# ------------route implementations----------- planets_bp = Blueprint("planets_bp", __name__, url_prefix="/planets") @planets_bp.route("", methods=["POST"]) @@ -78,7 +67,7 @@ def get_planets_optional_query(): if name_query: planet_query = planet_query.filter(Planet.name.ilike(f"%{name_query}%")) - sort_query = request.args.get("sort") # what is being returned here? + sort_query = request.args.get("sort") order_query = request.args.get("order") if sort_query or order_query: From 9c42bf6656c1ccd1a04391085c7b54efd2e92aef Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Tue, 3 Jan 2023 16:29:23 -0500 Subject: [PATCH 17/30] added branch test_Setup --- tests/__init__.py | 0 tests/conftest.py | 23 +++++++++++++++++++++++ tests/test_routes.py | 0 3 files changed, 23 insertions(+) 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..29bafabce --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,23 @@ +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() \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py new file mode 100644 index 000000000..e69de29bb From f067be1f00dd69a213a751dea4a52d7070e85e0b Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Tue, 3 Jan 2023 18:50:40 -0500 Subject: [PATCH 18/30] create test fixture for unit tests for get,post,update and delete --- tests/conftest.py | 22 ++++++++- tests/test_routes.py | 104 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 29bafabce..2c367ac60 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,4 +20,24 @@ def expire_session(sender, response, **extra): @pytest.fixture def client(app): - return app.test_client() \ No newline at end of file + return app.test_client() + +@pytest.fixture +def two_planets(app): + earth = Planet( + name = "Earth", + description = "habitable", + diameter_in_km = 12756 + ) + + mars = Planet( + name = "Mars", + description = "inhabitable", + diameter_in_km = 6792 + ) + + db.session.add_all([earth, mars]) + db.session.commit() + db.session.refresh(earth, ["id"]) + db.session.refresh(mars, ["id"]) + #return planet \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index e69de29bb..1a8f3c249 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -0,0 +1,104 @@ +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() == [] + +def test_get_planets_optional_query_returns_seeded_planet(client, two_planets): + response = client.get("/planets") + planet_list = response.get_json() + + assert response.status_code == 200 + assert len(planet_list) == 2 + assert planet_list[0]["id"] == 1 + assert planet_list[0]["name"] == "Earth" + assert planet_list[0]["description"] == "habitable" + assert planet_list[0]["diameter_in_km"] == 12756 + + assert planet_list[1]["id"] == 2 + assert planet_list[1]["name"] == "Mars" + assert planet_list[1]["description"] == "inhabitable" + assert planet_list[1]["diameter_in_km"] == 6792 + +def test_get_planet_optional_query_missing_record_returns_404(client, two_planets): + response = client.get("/planets/3") + response_body = response.get_json() + + assert response.status_code == 404 + assert response_body == {"message":"Planet #3 not found"} + +def test_get_planet_by_id_returns_200_with_matching_response(client, two_planets): + response = client.get("/planets/1") + planet_list = response.get_json() + + assert response.status_code == 200 + assert planet_list == { + "id": 1, + "name": "Earth", + "description": "habitable", + "diameter_in_km": 12756 + } + +def test_get_planet_by_id_invalid_id_returns_400(client, two_planets): + # Act + response = client.get("/planets/earth") + response_body = response.get_json() + + # Assert + assert response.status_code == 400 + assert response_body == {"message": "Planet #earth is not an int."} + +def test_create_planet_returns_201(client): + test_data = { + "name": "Mercury", + "description": "inhabitable", + "diameter_in_km": 4879 + } + response = client.post("/planets", json=test_data) + response_body = response.get_data(as_text=True) + + assert response.status_code == 201 + assert response_body == "Planet Mercury successfully created" + +def test_update_planet_returns_200(client, two_planets): + test_data = { + "name": "Mercury", + "description": "inhabitable", + "diameter_in_km": 4879 + } + + response = client.put("/planets/1", json=test_data) + response_body = response.get_data(as_text=True) + + assert response.status_code == 200 + assert response_body == "Planet #1 successfully updated." + +def test_update_planet_invalid_id_returns_400(client, two_planets): + test_data = { + "name": "Mercury", + "description": "inhabitable", + "diameter_in_km": 4879 + } + + response = client.put("/planets/earth", json=test_data) + response_body = response.get_json() + + assert response.status_code == 400 + assert response_body == {"message": "Planet #earth is not an int."} + +def test_delete_planet_returns_200(client, two_planets): + response = client.delete("/planets/1") + response_body = response.get_data(as_text=True) + + assert response.status_code == 200 + assert response_body == "Planet #1 successfully deleted" + +def test_delete_planet_returns_404(client, two_planets): + response = client.delete("/planets/3") + response_body = response.get_json() + + assert response.status_code == 404 + assert response_body == {"message":"Planet #3 not found"} + From 6bf4e810cd415d8e1bd1ca660c85fea9e96b5697 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Wed, 4 Jan 2023 13:36:12 -0500 Subject: [PATCH 19/30] refactored to_dict and added more tests --- app/models/planet.py | 12 ++++++++++- app/planets_routes.py | 12 +++-------- tests/test_models.py | 0 tests/test_routes.py | 48 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 tests/test_models.py diff --git a/app/models/planet.py b/app/models/planet.py index d6198c08e..23296a560 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -4,4 +4,14 @@ class Planet(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String, nullable=False) description = db.Column(db.String) - diameter_in_km = db.Column(db.Integer) \ No newline at end of file + diameter_in_km = db.Column(db.Integer) + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "diameter_in_km": self.diameter_in_km + } + + \ No newline at end of file diff --git a/app/planets_routes.py b/app/planets_routes.py index dd5579a85..6ec983502 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -19,13 +19,7 @@ def sort_planet_query(planet_query, sort_query, order_query): return planet_query -def to_dict(planet): - return { - "id": planet.id, - "name": planet.name, - "description": planet.description, - "diameter_in_km": planet.diameter_in_km - } + def validate_planet(planet_id): try: @@ -77,7 +71,7 @@ def get_planets_optional_query(): planets_response = [] for planet in planets: - planets_response.append(to_dict(planet)) + planets_response.append(planet.to_dict()) return jsonify(planets_response) @@ -85,7 +79,7 @@ def get_planets_optional_query(): @planets_bp.route("/", methods=["GET"]) def get_planet_by_id(planet_id): planet = validate_planet(planet_id) - return to_dict(planet) + return planet.to_dict() @planets_bp.route("/", methods=["PUT"]) diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_routes.py b/tests/test_routes.py index 1a8f3c249..01785b222 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,3 +1,5 @@ +import pytest + def test_get_planets_optional_query_empty_db_returns_empty_list(client): # Act response = client.get("/planets") @@ -102,3 +104,49 @@ def test_delete_planet_returns_404(client, two_planets): assert response.status_code == 404 assert response_body == {"message":"Planet #3 not found"} +def test_delete_planet_invalid_id_returns_400(client, two_planets): + response = client.delete("/planets/earth") + response_body = response.get_json() + + assert response.status_code == 400 + assert response_body == {"message": "Planet #earth is not an int."} + +def test_update_planet_missing_planet_returns_404(client, two_planets): + test_data = { + "name": "Mercury", + "description": "inhabitable", + "diameter_in_km": 4879 + } + + response = client.put("/planets/3", json=test_data) + response_body = response.get_json() + + assert response.status_code == 404 + assert response_body == {"message":"Planet #3 not found"} + +def test_create_planet_missing_name_attribute(client, two_planets): + test_data = { + "description": 'habitable', + "diameter_in_km": 12345 + } + with pytest.raises(KeyError, match='name'): + response = client.post("/planets", json=test_data) + + + + +"""def test_create_one_book_no_title(client): + # Arrange + test_data = {"description": "The Best!"} + + # Act & Assert + with pytest.raises(KeyError, match='title'): + response = client.post("/books", json=test_data) + +def test_create_one_book_no_description(client): + # Arrange + test_data = {"title": "New Book"} + + # Act & Assert + with pytest.raises(KeyError, match = 'description'): + response = client.post("/books", json=test_data)""" \ No newline at end of file From 442ab49e2cc40a5c03fe1f269e8342386905dc6d Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Wed, 4 Jan 2023 18:08:37 -0500 Subject: [PATCH 20/30] refactored tests --- app/models/planet.py | 9 +++++++- app/planets_routes.py | 17 +++++++-------- tests/test_routes.py | 48 +++++++++++++++++++++++-------------------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 23296a560..2b82bbe8c 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -14,4 +14,11 @@ def to_dict(self): "diameter_in_km": self.diameter_in_km } - \ No newline at end of file + @classmethod + def from_dict(cls, planet_data): + new_planet = Planet( + name = planet_data["name"], + description = planet_data["description"], + diameter_in_km = planet_data["diameter_in_km"] + ) + return new_planet \ No newline at end of file diff --git a/app/planets_routes.py b/app/planets_routes.py index 6ec983502..e66420c3b 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -41,16 +41,17 @@ def validate_planet(planet_id): @planets_bp.route("", methods=["POST"]) def create_planet(): request_body = request.get_json() - if "name" not in request_body or "description" not in request_body or 'diameter_in_km' not in request_body: - return make_response("Invalid Request", 400) + # if "name" not in request_body or "description" not in request_body or 'diameter_in_km' not in request_body: + # return make_response("Invalid Request", 400) + try: + new_planet = Planet.from_dict(request_body) + except KeyError as err: + abort(make_response({"message":f"Missing {err.args[0]}."}, 400)) - new_planet = Planet(name = request_body["name"], - description = request_body["description"], - diameter_in_km = request_body["diameter_in_km"]) db.session.add(new_planet) db.session.commit() - return make_response(f"Planet {new_planet.name} successfully created", 201) + return make_response(jsonify(f"Planet {new_planet.name} successfully created"), 201) @planets_bp.route("", methods=["GET"]) @@ -91,7 +92,7 @@ def update_planet(planet_id): planet.diameter_in_km = request_body["diameter_in_km"] db.session.commit() - return make_response(f"Planet #{planet_id} successfully updated.") + return make_response(jsonify(f"Planet #{planet_id} successfully updated.")) @planets_bp.route("/", methods=["DELETE"]) @@ -100,7 +101,7 @@ def delete_planet(planet_id): db.session.delete(planet) db.session.commit() - return make_response(f"Planet #{planet_id} successfully deleted") + return make_response(jsonify(f"Planet #{planet_id} successfully deleted")) # --------------for testing----------------- diff --git a/tests/test_routes.py b/tests/test_routes.py index 01785b222..845373e41 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,4 +1,5 @@ import pytest +from app.models.planet import Planet def test_get_planets_optional_query_empty_db_returns_empty_list(client): # Act @@ -59,7 +60,7 @@ def test_create_planet_returns_201(client): "diameter_in_km": 4879 } response = client.post("/planets", json=test_data) - response_body = response.get_data(as_text=True) + response_body = response.get_json() assert response.status_code == 201 assert response_body == "Planet Mercury successfully created" @@ -72,7 +73,7 @@ def test_update_planet_returns_200(client, two_planets): } response = client.put("/planets/1", json=test_data) - response_body = response.get_data(as_text=True) + response_body = response.get_json() assert response.status_code == 200 assert response_body == "Planet #1 successfully updated." @@ -92,7 +93,7 @@ def test_update_planet_invalid_id_returns_400(client, two_planets): def test_delete_planet_returns_200(client, two_planets): response = client.delete("/planets/1") - response_body = response.get_data(as_text=True) + response_body = response.get_json() assert response.status_code == 200 assert response_body == "Planet #1 successfully deleted" @@ -129,24 +130,27 @@ def test_create_planet_missing_name_attribute(client, two_planets): "description": 'habitable', "diameter_in_km": 12345 } - with pytest.raises(KeyError, match='name'): - response = client.post("/planets", json=test_data) - - - - -"""def test_create_one_book_no_title(client): - # Arrange - test_data = {"description": "The Best!"} - - # Act & Assert - with pytest.raises(KeyError, match='title'): - response = client.post("/books", json=test_data) + response = client.post("/planets", json=test_data) + response_body = response.get_json() + assert response.status_code == 400 + assert response_body == {"message":"Missing name."} -def test_create_one_book_no_description(client): - # Arrange - test_data = {"title": "New Book"} +def test_create_planet_missing_description_attribute(client, two_planets): + test_data = { + "name": "Mars", + "diameter_in_km": 12345 + } + response = client.post("/planets", json=test_data) + response_body = response.get_json() + assert response.status_code == 400 + assert response_body == {"message":"Missing description."} - # Act & Assert - with pytest.raises(KeyError, match = 'description'): - response = client.post("/books", json=test_data)""" \ No newline at end of file +def test_create_planet_missing_diameter_in_km_attribute(client): + test_data = { + "name": "Mars", + "description": 'habitable' + } + response = client.post("/planets", json=test_data) + response_body = response.get_json() + assert response.status_code == 400 + assert response_body == {"message":"Missing diameter_in_km."} \ No newline at end of file From d328a24ae330fdd9c9802874fefa1a444f7c39bb Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Wed, 4 Jan 2023 18:09:51 -0500 Subject: [PATCH 21/30] from_dict classmethod --- app/models/planet.py | 8 ++++++++ app/planets_routes.py | 8 ++------ tests/test_routes.py | 8 +++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 23296a560..46d09d4e3 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -14,4 +14,12 @@ def to_dict(self): "diameter_in_km": self.diameter_in_km } + @classmethod + def from_dict(cls, planet_data): + new_planet = Planet( + name = planet_data["name"], + description = planet_data["description"], + diameter_in_km = planet_data["diameter_in_km"] + ) + return new_planet \ No newline at end of file diff --git a/app/planets_routes.py b/app/planets_routes.py index 6ec983502..e4e44a8bc 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -41,12 +41,8 @@ def validate_planet(planet_id): @planets_bp.route("", methods=["POST"]) def create_planet(): request_body = request.get_json() - if "name" not in request_body or "description" not in request_body or 'diameter_in_km' not in request_body: - return make_response("Invalid Request", 400) - - new_planet = Planet(name = request_body["name"], - description = request_body["description"], - diameter_in_km = request_body["diameter_in_km"]) + new_planet = Planet.from_dict(request_body) + db.session.add(new_planet) db.session.commit() diff --git a/tests/test_routes.py b/tests/test_routes.py index 01785b222..833c0b7c5 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,3 +1,4 @@ +from app.models.planet import Planet import pytest def test_get_planets_optional_query_empty_db_returns_empty_list(client): @@ -124,16 +125,13 @@ def test_update_planet_missing_planet_returns_404(client, two_planets): assert response.status_code == 404 assert response_body == {"message":"Planet #3 not found"} -def test_create_planet_missing_name_attribute(client, two_planets): +def test_create_planet_missing_name_attribute(client): test_data = { "description": 'habitable', "diameter_in_km": 12345 } with pytest.raises(KeyError, match='name'): - response = client.post("/planets", json=test_data) - - - + response = client.post("/planets", json=test_data) """def test_create_one_book_no_title(client): # Arrange From 4c114f07965f8d47bba963b46a83c8dc6936c7e0 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Wed, 4 Jan 2023 18:43:12 -0500 Subject: [PATCH 22/30] added to_dict and from_dict test to tests_models.py and create a class method for the Planet class --- tests/test_models.py | 125 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index e69de29bb..a8b23101d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -0,0 +1,125 @@ +from app.models.planet import Planet +import pytest + + +def test_from_dict_returns_planet(): + # Arrange + planet_data = { + 'name': "Earth", + 'description':"habitable", + 'diameter_in_km': 12756 + } + + # Act + new_planet = Planet.from_dict(planet_data) + + # Assert + assert new_planet.name == "Earth" + assert new_planet.description == "habitable" + assert new_planet.diameter_in_km == 12756 + +def test_from_dict_with_no_name(): + # Arrange + planet_data = { + 'description':"habitable", + 'diameter_in_km': 12756 + } + + # Act & Assert + with pytest.raises(KeyError, match = 'name'): + new_planet = Planet.from_dict(planet_data) + +def test_from_dict_with_no_description(): + # Arrange + planet_data = { + "name": "Earth", + 'diameter_in_km': 12756 + } + + # 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 = { + 'name': "Earth", + 'description':"habitable", + 'diameter_in_km': 12756, + 'has_air': True + } + + # Act + new_planet = Planet.from_dict(planet_data) + + # Assert + assert new_planet.name == 'Earth' + assert new_planet.description == "habitable" + assert new_planet.diameter_in_km == 12756 + +def test_to_dict_no_missing_data(): + # Arrange + test_data = Planet( + id = 1, + name = "Earth", + description = "habitable", + diameter_in_km = 12756) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 4 + assert result["id"] == 1 + assert result["name"] == "Earth" + assert result["description"] == "habitable" + assert result["diameter_in_km"] == 12756 + +def test_to_dict_missing_id(): + # Arrange + test_data = Planet( + name = "Earth", + description = "habitable", + diameter_in_km = 12756) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 4 + assert result["id"] is None + assert result["name"] == "Earth" + assert result["description"] == "habitable" + assert result["diameter_in_km"] == 12756 + +def test_to_dict_missing_name(): + # Arrange + test_data = Planet(id=1, + description = "habitable", + diameter_in_km = 12756) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 4 + assert result["id"] == 1 + assert result["name"] is None + assert result["description"] == "habitable" + assert result["diameter_in_km"] == 12756 + +def test_to_dict_missing_description(): + # Arrange + test_data = Planet(id = 1, + name = "Earth", + diameter_in_km = 12756) + + # Act + result = test_data.to_dict() + + # Assert + assert len(result) == 4 + assert result["id"] == 1 + assert result["name"] == "Earth" + assert result["description"] is None + assert result["diameter_in_km"] == 12756 From 0642a6a7e6655cde2e84bbf56a1e93a97b1686d1 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 5 Jan 2023 13:50:40 -0500 Subject: [PATCH 23/30] refactored validate_planet to validate_model --- app/planets_routes.py | 24 ++++++++++++------------ tests/test_routes.py | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/planets_routes.py b/app/planets_routes.py index e66420c3b..dc2da8e38 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -21,18 +21,18 @@ def sort_planet_query(planet_query, sort_query, order_query): -def validate_planet(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 #{planet_id} is not an int."}, 400)) - - planet = Planet.query.get(planet_id) + abort(make_response(jsonify({"message":f"{cls.__name__} {model_id} invalid"}), 400)) - if not planet: - abort(make_response({"message":f"Planet #{planet_id} not found"}, 404)) - - return planet + model = cls.query.get(model_id) + + if not model: + abort(make_response(jsonify({"message":f"{cls.__name__} #{model_id} not found"}), 404)) + + return model # ------------route implementations----------- @@ -79,13 +79,13 @@ def get_planets_optional_query(): @planets_bp.route("/", methods=["GET"]) def get_planet_by_id(planet_id): - planet = validate_planet(planet_id) + planet = validate_model(Planet, planet_id) return planet.to_dict() @planets_bp.route("/", methods=["PUT"]) def update_planet(planet_id): - planet = validate_planet(planet_id) + planet = validate_model(Planet, planet_id) request_body = request.get_json() planet.name = request_body["name"] planet.description = request_body["description"] @@ -97,7 +97,7 @@ def update_planet(planet_id): @planets_bp.route("/", methods=["DELETE"]) def delete_planet(planet_id): - planet = validate_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 845373e41..8f35b8b55 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -51,7 +51,7 @@ def test_get_planet_by_id_invalid_id_returns_400(client, two_planets): # Assert assert response.status_code == 400 - assert response_body == {"message": "Planet #earth is not an int."} + assert response_body == {"message": "Planet earth invalid"} def test_create_planet_returns_201(client): test_data = { @@ -89,7 +89,7 @@ def test_update_planet_invalid_id_returns_400(client, two_planets): response_body = response.get_json() assert response.status_code == 400 - assert response_body == {"message": "Planet #earth is not an int."} + assert response_body == {"message": "Planet earth invalid"} def test_delete_planet_returns_200(client, two_planets): response = client.delete("/planets/1") @@ -110,7 +110,7 @@ def test_delete_planet_invalid_id_returns_400(client, two_planets): response_body = response.get_json() assert response.status_code == 400 - assert response_body == {"message": "Planet #earth is not an int."} + assert response_body == {"message": "Planet earth invalid"} def test_update_planet_missing_planet_returns_404(client, two_planets): test_data = { From 3f463391c0532a0f5f0b263df893bb3fc05cede3 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 5 Jan 2023 14:41:56 -0500 Subject: [PATCH 24/30] Created Moon model --- app/models/moon.py | 26 ++++++++++++++ app/models/planet.py | 5 ++- app/planets_routes.py | 1 + .../versions/2734fb2348a0_adds_moon_model.py | 36 +++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 app/models/moon.py create mode 100644 migrations/versions/2734fb2348a0_adds_moon_model.py diff --git a/app/models/moon.py b/app/models/moon.py new file mode 100644 index 000000000..93e770145 --- /dev/null +++ b/app/models/moon.py @@ -0,0 +1,26 @@ +from app import db + +class Moon(db.Model): + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + size = db.Column(db.Integer) + description = db.Column(db.String) + name = db.Column(db.String) + planet_id = db.Column(db.Integer, db.ForeignKey('moon.id')) + planet = db.relationship("Planet", back_populates = "moons") + + def to_dict(self): + moon_dict = {} + moon_dict["id"] = self.id + moon_dict["size"] = self.size + moon_dict["description"] = self.description + moon_dict["name"] = self.name + return moon_dict + + @classmethod + def from_dict(cls, moon_data): + new_moon = Moon( + name = moon_data["name"], + description = moon_data["description"], + size = moon_data["size"] + ) + return new_moon \ No newline at end of file diff --git a/app/models/planet.py b/app/models/planet.py index 2b82bbe8c..6d0c0c574 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -5,13 +5,16 @@ class Planet(db.Model): name = db.Column(db.String, nullable=False) description = db.Column(db.String) diameter_in_km = db.Column(db.Integer) + moons = db.relationship("Moon", back_populates = "planets") + def to_dict(self): return { "id": self.id, "name": self.name, "description": self.description, - "diameter_in_km": self.diameter_in_km + "diameter_in_km": self.diameter_in_km, + "moons": self.moons # testing } @classmethod diff --git a/app/planets_routes.py b/app/planets_routes.py index dc2da8e38..8cb4a8ad4 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -1,5 +1,6 @@ from app import db from app.models.planet import Planet +from app.models.moon import Moon from flask import Blueprint, jsonify, abort, make_response, request from sqlalchemy import desc, asc diff --git a/migrations/versions/2734fb2348a0_adds_moon_model.py b/migrations/versions/2734fb2348a0_adds_moon_model.py new file mode 100644 index 000000000..c52ae712e --- /dev/null +++ b/migrations/versions/2734fb2348a0_adds_moon_model.py @@ -0,0 +1,36 @@ +"""adds Moon model + +Revision ID: 2734fb2348a0 +Revises: 224c8797ae08 +Create Date: 2023-01-05 14:40:19.153903 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2734fb2348a0' +down_revision = '224c8797ae08' +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('size', sa.Integer(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('planet_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['planet_id'], ['moon.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('moon') + # ### end Alembic commands ### From 801b7ab24ca54c3d92992a2ee005c255b523288d Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 5 Jan 2023 16:06:48 -0500 Subject: [PATCH 25/30] added nested routes --- app/__init__.py | 4 ++++ app/models/moon.py | 4 ++-- app/models/planet.py | 18 +++++++++++------- app/moons_routes.py | 27 +++++++++++++++++++++++++++ app/planets_routes.py | 22 ++++++++++++++++++++++ 5 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 app/moons_routes.py diff --git a/app/__init__.py b/app/__init__.py index 07fc92161..192f7086a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -19,6 +19,7 @@ def create_app(test_config=None): app.config['SQLALCHEMY_TEST_DATABASE_URI'] = os.environ.get('SQLALCHEMY_TEST_DATABASE_URI') from app.models.planet import Planet + from app.models.moon import Moon db.init_app(app) migrate.init_app(app, db) @@ -26,4 +27,7 @@ def create_app(test_config=None): from .planets_routes import planets_bp app.register_blueprint(planets_bp) + from .moons_routes import moons_bp + app.register_blueprint(moons_bp) + return app diff --git a/app/models/moon.py b/app/models/moon.py index 93e770145..8ad88f8fb 100644 --- a/app/models/moon.py +++ b/app/models/moon.py @@ -5,8 +5,8 @@ class Moon(db.Model): size = db.Column(db.Integer) description = db.Column(db.String) name = db.Column(db.String) - planet_id = db.Column(db.Integer, db.ForeignKey('moon.id')) - planet = db.relationship("Planet", back_populates = "moons") + planet_id = db.Column(db.Integer, db.ForeignKey('planet.id')) + planets = db.relationship("Planet", back_populates = "moons") def to_dict(self): moon_dict = {} diff --git a/app/models/planet.py b/app/models/planet.py index 6d0c0c574..839551faa 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -9,13 +9,17 @@ class Planet(db.Model): def to_dict(self): - return { - "id": self.id, - "name": self.name, - "description": self.description, - "diameter_in_km": self.diameter_in_km, - "moons": self.moons # testing - } + planet_dict = {} + planet_dict["id"] = self.id + planet_dict["name"] = self.name, + planet_dict["description"] = self.description + planet_dict["diameter_in_km"] = self.diameter_in_km + moon_names = [] + for moon in self.moons: + moon_names.append(moon.name) + planet_dict["moons"] = moon_names + return planet_dict + @classmethod def from_dict(cls, planet_data): diff --git a/app/moons_routes.py b/app/moons_routes.py new file mode 100644 index 000000000..886597111 --- /dev/null +++ b/app/moons_routes.py @@ -0,0 +1,27 @@ +from app import db +from app.models.planet import Planet +from app.models.moon import Moon +from app.planets_routes import validate_model +from app.models.moon import Moon +from flask import Blueprint, jsonify, abort, make_response, request + +moons_bp = Blueprint("moons_bp", __name__, url_prefix="/moons") + +@moons_bp.route("", methods=["POST"]) +def create_moon(): + moon_data = request.get_json() + new_moon = Moon.from_dict(moon_data) + + db.session.add(new_moon) + db.session.commit() + + return make_response(jsonify(f"Moon {new_moon.id} successfully created"), 201) + +@moons_bp.route("", methods=["GET"]) +def get_all_moons(): + moons = Moon.query.all() + + moons_response = [] + for moon in moons: + moons_response.append(moon.to_dict()) + return jsonify(moons_response) diff --git a/app/planets_routes.py b/app/planets_routes.py index 8cb4a8ad4..0ab10dc76 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -104,7 +104,29 @@ def delete_planet(planet_id): db.session.commit() return make_response(jsonify(f"Planet #{planet_id} successfully deleted")) +@planets_bp.route("//moons", methods=["GET"]) +def get_all_moons_for_planet(planet_id): + planet = validate_model(Planet, planet_id) + + moons_response = [] + for moon in planet.moons: + moons_response.append(moon.to_dict()) + + return jsonify(moons_response) + +@planets_bp.route("//moons", methods=["POST"]) +def add_new_moon_to_planet(planet_id): + planet = validate_model(Planet, planet_id) + + request_body = request.get_json() + new_moon = Moon.from_dict(request_body) + new_moon.planet = planet + + db.session.add(new_moon) + db.session.commit() + message = f"Moon {new_moon.name} created with planet {planet.name}" + return make_response(jsonify(message), 201) # --------------for testing----------------- # planets = [ # Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), From a9f0bc98ac34e72f3617c75279cf448f5730d195 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Thu, 5 Jan 2023 20:28:34 -0500 Subject: [PATCH 26/30] fixed issue with route that creates a new moon with a planet --- app/models/planet.py | 2 +- app/planets_routes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 839551faa..fb665708b 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -11,7 +11,7 @@ class Planet(db.Model): def to_dict(self): planet_dict = {} planet_dict["id"] = self.id - planet_dict["name"] = self.name, + planet_dict["name"] = self.name planet_dict["description"] = self.description planet_dict["diameter_in_km"] = self.diameter_in_km moon_names = [] diff --git a/app/planets_routes.py b/app/planets_routes.py index 0ab10dc76..9ea6a6ac7 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -120,7 +120,7 @@ def add_new_moon_to_planet(planet_id): request_body = request.get_json() new_moon = Moon.from_dict(request_body) - new_moon.planet = planet + new_moon.planet_id = planet_id db.session.add(new_moon) db.session.commit() From af46358ee865289f28bc677e24eccd6b8b125724 Mon Sep 17 00:00:00 2001 From: Xuan Hien Pham Date: Fri, 6 Jan 2023 14:27:20 -0500 Subject: [PATCH 27/30] added read one moon --- app/moons_routes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/moons_routes.py b/app/moons_routes.py index 886597111..572f19da7 100644 --- a/app/moons_routes.py +++ b/app/moons_routes.py @@ -25,3 +25,8 @@ def get_all_moons(): for moon in moons: moons_response.append(moon.to_dict()) return jsonify(moons_response) + +@moons_bp.route("/", methods=["GET"]) +def get_one_moon(moon_id): + moon = validate_model(Moon, moon_id) + return moon.to_dict() From 0dba82d75881ba60f003fb1808885ec2e736e8fe Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Fri, 6 Jan 2023 15:35:40 -0500 Subject: [PATCH 28/30] added gunicorn to requirements.txt --- requirements.txt | 8 ++++++++ 1 file changed, 8 insertions(+) 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 From 9a49851c059343eda52b8a85ec800d3bd311555d Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Fri, 6 Jan 2023 15:37:42 -0500 Subject: [PATCH 29/30] added Procfile --- Procfile | 1 + 1 file changed, 1 insertion(+) 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 From 8641101dc199172069669bf41f459861a18306b7 Mon Sep 17 00:00:00 2001 From: Lisa Utsett Date: Sun, 8 Jan 2023 20:48:56 -0500 Subject: [PATCH 30/30] added tests for nested routes --- app/planets_routes.py | 6 ++++-- tests/conftest.py | 35 ++++++++++++++++++++++++++++++++++- tests/test_models.py | 8 ++++---- tests/test_routes.py | 29 ++++++++++++++++++++++++++--- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/app/planets_routes.py b/app/planets_routes.py index 78563dcf0..894a940d7 100644 --- a/app/planets_routes.py +++ b/app/planets_routes.py @@ -126,8 +126,10 @@ def add_new_moon_to_planet(planet_id): db.session.add(new_moon) db.session.commit() - message = f"Moon {new_moon.name} created with planet {planet.name}" - return make_response(jsonify(message), 201) + + return make_response(jsonify({"message": f"Moon {new_moon.name} created with planet {planet.name}"}), 201) + + # --------------for testing----------------- # planets = [ # Planet(id = 3, name = "Earth", description = "habitable", diameter = 12756), diff --git a/tests/conftest.py b/tests/conftest.py index 2c367ac60..844382a84 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(): @@ -22,6 +23,26 @@ def expire_session(sender, response, **extra): def client(app): return app.test_client() +@pytest.fixture +def one_planet(app): + moon = Moon( + name = "Titan", + size = 1000, + description = "icy" + ) + + earth = Planet( + name = "Earth", + description = "habitable", + diameter_in_km = 12756, + moons = [moon] + ) + + db.session.add(earth) + db.session.commit() + return earth + + @pytest.fixture def two_planets(app): earth = Planet( @@ -40,4 +61,16 @@ def two_planets(app): db.session.commit() db.session.refresh(earth, ["id"]) db.session.refresh(mars, ["id"]) - #return planet \ No newline at end of file + + +@pytest.fixture +def one_moon(app): + moon = Moon( + name = "Titan", + size = 1000, + description = "icy" + ) + + db.session.add(moon) + db.session.commit() + return moon \ No newline at end of file diff --git a/tests/test_models.py b/tests/test_models.py index a8b23101d..d06857258 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -69,7 +69,7 @@ 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"] == "Earth" assert result["description"] == "habitable" @@ -86,7 +86,7 @@ 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"] == "Earth" assert result["description"] == "habitable" @@ -102,7 +102,7 @@ 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["description"] == "habitable" @@ -118,7 +118,7 @@ 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"] == "Earth" assert result["description"] is None diff --git a/tests/test_routes.py b/tests/test_routes.py index 264484b8e..948c077f3 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,6 +1,4 @@ -from app.models.planet import Planet import pytest -from app.models.planet import Planet def test_get_planets_optional_query_empty_db_returns_empty_list(client): # Act @@ -42,7 +40,8 @@ def test_get_planet_by_id_returns_200_with_matching_response(client, two_planets "id": 1, "name": "Earth", "description": "habitable", - "diameter_in_km": 12756 + "diameter_in_km": 12756, + "moons": [] } def test_get_planet_by_id_invalid_id_returns_400(client, two_planets): @@ -157,3 +156,27 @@ def test_create_planet_missing_diameter_in_km_attribute(client): assert response.status_code == 400 assert response_body == {"message":"Missing diameter_in_km."} + +# testing nested routes +def test_add_new_moon_to_planet_returns_201(client, two_planets): + test_data = { + "name": "Titan", + "size": 1000, + "description": "icy" + } + response = client.post("/planets/2/moons", json=test_data) + response_body = response.get_json() + assert response.status_code == 201 + assert response_body == {"message": "Moon Titan created with planet Mars"} + + +def test_get_all_moons_for_planet_returns_201(client, one_planet): + response = client.get("/planets/1/moons") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body[0]["name"] == "Titan" + assert response_body[0]["size"] == 1000 + assert response_body[0]["description"] == "icy" + +