From 1c6a9635dff5f30a56bca8aeac8f1da85a006eca Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Mon, 24 Apr 2023 16:53:41 -0500 Subject: [PATCH 01/18] Wave 1 in progress --- app/routes.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..57f2499fd 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,26 @@ from flask import Blueprint +class Planet: + def __init__(self, id, name, description, radius): + self.id = id + self.name = name + self.description = description + self.radius = radius + +planets = [ + Planet(1, "Mercury", "Mercury is the smallest planet in our solar system.", + "1,516 mi"), + Planet(2, "Venus", "In addition to being extremely hot, Venus is unusual \ + because it spins in the opposite direction of Earth and most other planets.", "3,760.4 mi"), + Planet(3, "Earth", "Earth is the third planet from the Sun and the only place\ + known in the universe where life has originated and found habitability.", "3,958.8 mi"), + Planet(4, "Mars", "Mars is the fourth planet from the Sun and the third largest\ + and massive terrestrial object in the Solar System.", "2,106.1 mi"), + Planet(5, "Jupiter", "Jupiter is the fifth planet from the Sun and the largest\ + in the Solar System.", "43,441 mi"), + Planet(6, "Saturn", "Saturn is the sixth planet from the Sun and the second-\ + largest in the Solar System, after Jupiter.", "36,184 mi"), + Planet(7, "Uranus", "Uranus is the seventh planet from the Sun and is named\ + after Greek sky deity Uranus.", "15,759.2 mi"), + Planet(8, "Neptune", "Neptune is the eighth planet from the Sun and the \ + farthest known planet in the Solar System.", "15,299.4 mi")] \ No newline at end of file From ab9b2d69ccd6f4a0953b41736b6d6a11e212d433 Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Mon, 24 Apr 2023 18:27:36 -0400 Subject: [PATCH 02/18] Create two routes get planet and get planets --- app/__init__.py | 3 +++ app/routes.py | 64 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 70b4cabfe..b2bd60609 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 planet_bp + app.register_blueprint(planet_bp) + return app diff --git a/app/routes.py b/app/routes.py index 57f2499fd..ef53bae87 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint +from flask import Blueprint, jsonify, abort, make_response class Planet: def __init__(self, id, name, description, radius): @@ -7,20 +7,54 @@ def __init__(self, id, name, description, radius): self.description = description self.radius = radius +# Revisit how to fix formatting of strings in description planets = [ Planet(1, "Mercury", "Mercury is the smallest planet in our solar system.", "1,516 mi"), - Planet(2, "Venus", "In addition to being extremely hot, Venus is unusual \ - because it spins in the opposite direction of Earth and most other planets.", "3,760.4 mi"), - Planet(3, "Earth", "Earth is the third planet from the Sun and the only place\ - known in the universe where life has originated and found habitability.", "3,958.8 mi"), - Planet(4, "Mars", "Mars is the fourth planet from the Sun and the third largest\ - and massive terrestrial object in the Solar System.", "2,106.1 mi"), - Planet(5, "Jupiter", "Jupiter is the fifth planet from the Sun and the largest\ - in the Solar System.", "43,441 mi"), - Planet(6, "Saturn", "Saturn is the sixth planet from the Sun and the second-\ - largest in the Solar System, after Jupiter.", "36,184 mi"), - Planet(7, "Uranus", "Uranus is the seventh planet from the Sun and is named\ - after Greek sky deity Uranus.", "15,759.2 mi"), - Planet(8, "Neptune", "Neptune is the eighth planet from the Sun and the \ - farthest known planet in the Solar System.", "15,299.4 mi")] \ No newline at end of file + Planet(2, "Venus", "In addition to being extremely hot, Venus is unusual"\ + " because it spins in the opposite direction of Earth and most other planets.", "3,760.4 mi"), + Planet(3, "Earth", "Earth is the third planet from the Sun and the only place"\ + " known in the universe where life has originated and found habitability.", "3,958.8 mi"), + Planet(4, "Mars", "Mars is the fourth planet from the Sun and the third largest"\ + " and massive terrestrial object in the Solar System.", "2,106.1 mi"), + Planet(5, "Jupiter", "Jupiter is the fifth planet from the Sun and the largest"\ + " in the Solar System.", "43,441 mi"), + Planet(6, "Saturn", "Saturn is the sixth planet from the Sun and the second-"\ + " largest in the Solar System, after Jupiter.", "36,184 mi"), + Planet(7, "Uranus", "Uranus is the seventh planet from the Sun and is named"\ + " after Greek sky deity Uranus.", "15,759.2 mi"), + Planet(8, "Neptune", "Neptune is the eighth planet from the Sun and the" \ + " farthest known planet in the Solar System.", "15,299.4 mi")] + +planet_bp = Blueprint("planets", __name__, url_prefix="/planets") + +@planet_bp.route("", methods=["GET"]) +def get_planets(): + result = [] + for planet in planets: + result.append(dict( + id = planet.id, + name = planet.name, + description = planet.description, + radius = planet.radius, + )) + + return jsonify(result) + +@planet_bp.route("/", methods=["GET"]) +def get_planet(id): + try: + id = int(id) + except: + abort(make_response({"message":f"id {id} is invalid"}, 400)) + + for planet in planets: + if planet.id == id: + return dict( + id = planet.id, + name = planet.name, + description = planet.description, + radius = planet.radius, + ) + + abort(make_response({"message":f"planet {id} not found"}, 404)) \ No newline at end of file From ec51845e3673c59e252cb905c4924e9e13318861 Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Tue, 25 Apr 2023 15:21:06 -0500 Subject: [PATCH 03/18] added makeplanetdict helper function to make planet dictionaries --- app/routes.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/app/routes.py b/app/routes.py index ef53bae87..024ff12fd 100644 --- a/app/routes.py +++ b/app/routes.py @@ -7,7 +7,14 @@ def __init__(self, id, name, description, radius): self.description = description self.radius = radius -# Revisit how to fix formatting of strings in description + def make_planet_dict(self): + return dict( + id = self.id, + name = self.name, + description = self.description, + radius = self.radius, + ) + planets = [ Planet(1, "Mercury", "Mercury is the smallest planet in our solar system.", "1,516 mi"), @@ -32,12 +39,7 @@ def __init__(self, id, name, description, radius): def get_planets(): result = [] for planet in planets: - result.append(dict( - id = planet.id, - name = planet.name, - description = planet.description, - radius = planet.radius, - )) + result.append(planet.make_planet_dict()) return jsonify(result) @@ -50,11 +52,6 @@ def get_planet(id): for planet in planets: if planet.id == id: - return dict( - id = planet.id, - name = planet.name, - description = planet.description, - radius = planet.radius, - ) + return planet.make_planet_dict() abort(make_response({"message":f"planet {id} not found"}, 404)) \ No newline at end of file From 3d68c14b07e833070c95fcf2502966dae9e3412e Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Tue, 25 Apr 2023 16:25:25 -0400 Subject: [PATCH 04/18] Add validate planet helper function --- app/routes.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/routes.py b/app/routes.py index 024ff12fd..167dca350 100644 --- a/app/routes.py +++ b/app/routes.py @@ -43,15 +43,20 @@ def get_planets(): return jsonify(result) -@planet_bp.route("/", methods=["GET"]) -def get_planet(id): +def validate_planet(id): try: id = int(id) except: abort(make_response({"message":f"id {id} is invalid"}, 400)) - + for planet in planets: if planet.id == id: - return planet.make_planet_dict() + return planet + + abort(make_response({"message":f"planet {id} not found"}, 404)) + +@planet_bp.route("/", methods=["GET"]) +def get_planet(id): + planet = validate_planet(id) + return planet.make_planet_dict() - abort(make_response({"message":f"planet {id} not found"}, 404)) \ No newline at end of file From 160c9d0dd4151b28219db8536a1214ebc5680bc4 Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Fri, 28 Apr 2023 13:10:46 -0500 Subject: [PATCH 05/18] created Planet model and completed db connections and applied migrations --- app/__init__.py | 12 +++ app/models/planet.py | 7 ++ migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ .../e219ffd97a6a_adds_planet_model.py | 34 +++++++ 7 files changed, 219 insertions(+) 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/e219ffd97a6a_adds_planet_model.py diff --git a/app/__init__.py b/app/__init__.py index b2bd60609..7bc613494 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,9 +1,21 @@ 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' + + db.init_app(app) + migrate.init_app(app, db) + + from app.models.planet import Planet + from .routes import planet_bp app.register_blueprint(planet_bp) diff --git a/app/models/planet.py b/app/models/planet.py new file mode 100644 index 000000000..2e48c4063 --- /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) + description = db.Column(db.String) + radius = db.Column(db.String) \ 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/e219ffd97a6a_adds_planet_model.py b/migrations/versions/e219ffd97a6a_adds_planet_model.py new file mode 100644 index 000000000..341570a49 --- /dev/null +++ b/migrations/versions/e219ffd97a6a_adds_planet_model.py @@ -0,0 +1,34 @@ +"""Adds Planet model + +Revision ID: e219ffd97a6a +Revises: +Create Date: 2023-04-28 13:08:17.487290 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e219ffd97a6a' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('planet', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('radius', sa.String(), 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 6712ce36019411475c8bf6d387b31c460e814d47 Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Fri, 28 Apr 2023 15:00:38 -0400 Subject: [PATCH 06/18] Create planet and GET all planet routes --- app/routes.py | 134 +++++++++++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 51 deletions(-) diff --git a/app/routes.py b/app/routes.py index 167dca350..eeb301000 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,62 +1,94 @@ -from flask import Blueprint, jsonify, abort, make_response - -class Planet: - def __init__(self, id, name, description, radius): - self.id = id - self.name = name - self.description = description - self.radius = radius - - def make_planet_dict(self): - return dict( - id = self.id, - name = self.name, - description = self.description, - radius = self.radius, - ) - -planets = [ - Planet(1, "Mercury", "Mercury is the smallest planet in our solar system.", - "1,516 mi"), - Planet(2, "Venus", "In addition to being extremely hot, Venus is unusual"\ - " because it spins in the opposite direction of Earth and most other planets.", "3,760.4 mi"), - Planet(3, "Earth", "Earth is the third planet from the Sun and the only place"\ - " known in the universe where life has originated and found habitability.", "3,958.8 mi"), - Planet(4, "Mars", "Mars is the fourth planet from the Sun and the third largest"\ - " and massive terrestrial object in the Solar System.", "2,106.1 mi"), - Planet(5, "Jupiter", "Jupiter is the fifth planet from the Sun and the largest"\ - " in the Solar System.", "43,441 mi"), - Planet(6, "Saturn", "Saturn is the sixth planet from the Sun and the second-"\ - " largest in the Solar System, after Jupiter.", "36,184 mi"), - Planet(7, "Uranus", "Uranus is the seventh planet from the Sun and is named"\ - " after Greek sky deity Uranus.", "15,759.2 mi"), - Planet(8, "Neptune", "Neptune is the eighth planet from the Sun and the" \ - " farthest known planet in the Solar System.", "15,299.4 mi")] +from app import db +from app.models.planet import Planet +from flask import Blueprint, jsonify, abort, make_response, request + +# class Planet: +# def __init__(self, id, name, description, radius): +# self.id = id +# self.name = name +# self.description = description +# self.radius = radius + +# def make_planet_dict(self): +# return dict( +# id = self.id, +# name = self.name, +# description = self.description, +# radius = self.radius, +# ) + +# planets = [ +# Planet(1, "Mercury", "Mercury is the smallest planet in our solar system.", +# "1,516 mi"), +# Planet(2, "Venus", "In addition to being extremely hot, Venus is unusual"\ +# " because it spins in the opposite direction of Earth and most other planets.", "3,760.4 mi"), +# Planet(3, "Earth", "Earth is the third planet from the Sun and the only place"\ +# " known in the universe where life has originated and found habitability.", "3,958.8 mi"), +# Planet(4, "Mars", "Mars is the fourth planet from the Sun and the third largest"\ +# " and massive terrestrial object in the Solar System.", "2,106.1 mi"), +# Planet(5, "Jupiter", "Jupiter is the fifth planet from the Sun and the largest"\ +# " in the Solar System.", "43,441 mi"), +# Planet(6, "Saturn", "Saturn is the sixth planet from the Sun and the second-"\ +# " largest in the Solar System, after Jupiter.", "36,184 mi"), +# Planet(7, "Uranus", "Uranus is the seventh planet from the Sun and is named"\ +# " after Greek sky deity Uranus.", "15,759.2 mi"), +# Planet(8, "Neptune", "Neptune is the eighth planet from the Sun and the" \ +# " farthest known planet in the Solar System.", "15,299.4 mi")] planet_bp = Blueprint("planets", __name__, url_prefix="/planets") +@planet_bp.route("", methods=["POST"]) +def create_planet(): + request_body = request.get_json() + new_planet = Planet( + name = request_body["name"], + description = request_body["description"], + radius = request_body["radius"] + ) + db.session.add(new_planet) + db.session.commit() + + message = f"Planet {new_planet.name} successfully created." + + return make_response(message, 201) + @planet_bp.route("", methods=["GET"]) -def get_planets(): - result = [] +def get_all_planets(): + planets = Planet.query.all() + results = [] + for planet in planets: - result.append(planet.make_planet_dict()) + results.append(dict( + id=planet.id, + name=planet.name, + description=planet.description, + radius = planet.radius + )) - return jsonify(result) + return jsonify(results) -def validate_planet(id): - try: - id = int(id) - except: - abort(make_response({"message":f"id {id} is invalid"}, 400)) +# @planet_bp.route("", methods=["GET"]) +# def get_planets(): +# result = [] +# for planet in planets: +# result.append(planet.make_planet_dict()) - for planet in planets: - if planet.id == id: - return planet +# return jsonify(result) + +# def validate_planet(id): +# try: +# id = int(id) +# except: +# abort(make_response({"message":f"id {id} is invalid"}, 400)) + +# for planet in planets: +# if planet.id == id: +# return planet - abort(make_response({"message":f"planet {id} not found"}, 404)) +# abort(make_response({"message":f"planet {id} not found"}, 404)) -@planet_bp.route("/", methods=["GET"]) -def get_planet(id): - planet = validate_planet(id) - return planet.make_planet_dict() +# @planet_bp.route("/", methods=["GET"]) +# def get_planet(id): +# planet = validate_planet(id) +# return planet.make_planet_dict() From 129d2dc181eae16a941ebfe804c295d1a639b66d Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Tue, 2 May 2023 13:19:27 -0500 Subject: [PATCH 07/18] created GET planet by id route and PUT route to update planet information --- app/routes.py | 56 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/app/routes.py b/app/routes.py index eeb301000..1f8bd3bbf 100644 --- a/app/routes.py +++ b/app/routes.py @@ -67,28 +67,40 @@ def get_all_planets(): return jsonify(results) -# @planet_bp.route("", methods=["GET"]) -# def get_planets(): -# result = [] -# for planet in planets: -# result.append(planet.make_planet_dict()) - -# return jsonify(result) +def validate_planet(id): + try: + id = int(id) + except: + message = f"Planet {id} is invalid" + abort(make_response({"message": message}, 400)) -# def validate_planet(id): -# try: -# id = int(id) -# except: -# abort(make_response({"message":f"id {id} is invalid"}, 400)) - -# for planet in planets: -# if planet.id == id: -# return planet - -# abort(make_response({"message":f"planet {id} not found"}, 404)) + planet = Planet.query.get(id) -# @planet_bp.route("/", methods=["GET"]) -# def get_planet(id): -# planet = validate_planet(id) -# return planet.make_planet_dict() + if not planet: + message = f"Planet {id} is not found" + abort(make_response({"message": message}, 404)) + return planet + +@planet_bp.route("/", methods=["GET"]) +def get_planet(id): + planet = validate_planet(id) + planet_dict = dict( + id=planet.id, + name=planet.name, + description=planet.description, + radius=planet.radius + ) + return planet_dict + +@planet_bp.route("/", methods=["PUT"]) +def update_planet(id): + planet_data = request.get_json() + planet_to_update = validate_planet(id) + + planet_to_update.name = planet_data["name"] + planet_to_update.description = planet_data["description"] + planet_to_update.radius = planet_data["radius"] + + db.session.commit() + return make_response(f"Planet {planet_to_update.name} updated", 200) \ No newline at end of file From ccf3baa9d12ff35cafcef326cbd0857f331de662 Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Tue, 2 May 2023 14:46:12 -0400 Subject: [PATCH 08/18] Adds delete planet route --- app/routes.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 1f8bd3bbf..6af23e8ba 100644 --- a/app/routes.py +++ b/app/routes.py @@ -103,4 +103,13 @@ def update_planet(id): planet_to_update.radius = planet_data["radius"] db.session.commit() - return make_response(f"Planet {planet_to_update.name} updated", 200) \ No newline at end of file + return make_response(f"Planet {planet_to_update.name} updated", 200) + +@planet_bp.route("/", methods=["DELETE"]) +def delete_planet(id): + planet_to_delete = validate_planet(id) + + db.session.delete(planet_to_delete) + db.session.commit() + + return make_response(f"Planet {planet_to_delete.name} deleted", 200) \ No newline at end of file From dddd960d6abc3333996a5314677d0bfd926d61fc Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Wed, 3 May 2023 13:18:07 -0500 Subject: [PATCH 09/18] refactored code to use pythondotenv and created test database --- app/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 7bc613494..549d3da09 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,15 +1,23 @@ 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' + if not test_config: + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_DATABASE_URI") + else: + app.config["TESTING"] = True + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") db.init_app(app) migrate.init_app(app, db) From 225d39b5d1e4a951148ac4fbad3d7727ee4855a5 Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Wed, 3 May 2023 13:29:04 -0500 Subject: [PATCH 10/18] configured test folder and conftest file --- tests/__init__.py | 0 tests/conftest.py | 24 ++++++++++++++++++++++++ tests/test_routes.py | 0 3 files changed, 24 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..537e2a3b8 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,24 @@ +import pytest +from app import create_app, db +from flask.signals import request_finished + + +@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 58adbd458ffb3d7f1a8667253f25f1fb5b43493c Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Wed, 3 May 2023 13:34:54 -0500 Subject: [PATCH 11/18] created test to check GET planets with no records --- tests/test_routes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_routes.py b/tests/test_routes.py index e69de29bb..1124a9765 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -0,0 +1,8 @@ +def test_get_all_planets_with_no_records(client): + # Act + response = client.get("/planets") + response_body = response.get_json() + + # Assert + assert response.status_code == 200 + assert response_body == [] \ No newline at end of file From 006b4ddff10732d153a2323643d575693d4e0dd6 Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Wed, 3 May 2023 14:52:21 -0400 Subject: [PATCH 12/18] Write test for get one planet and get one planet with no data --- tests/conftest.py | 16 +++++++++++++++- tests/test_routes.py | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 537e2a3b8..1152b36e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import pytest from app import create_app, db from flask.signals import request_finished +from app.models.planet import Planet @pytest.fixture @@ -21,4 +22,17 @@ 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 one_planet(app): + # Arrange + planet = Planet( + name="Mercury", + description="Mercury is the smallest planet in our solar system.", + radius="1,516 mi" + ) + + db.session.add(planet) + db.session.commit() + return planet \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index 1124a9765..e4847f4a9 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -5,4 +5,20 @@ def test_get_all_planets_with_no_records(client): # Assert assert response.status_code == 200 - assert response_body == [] \ No newline at end of file + assert response_body == [] + +def test_get_one_planet(client, one_planet): + response = client.get(f"/planets/{one_planet.id}") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body["id"] == one_planet.id + assert response_body["name"] == one_planet.name + assert response_body["description"] == one_planet.description + assert response_body["radius"] == one_planet.radius + +def test_get_planet_with_no_data(client): + response = client.get(f"/planets/1") + response_body = response.get_json() + + assert response.status_code == 404 \ No newline at end of file From a91076271451e818d95a1916eaafffdeb2df84b0 Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Wed, 3 May 2023 15:26:11 -0400 Subject: [PATCH 13/18] Write test get planet, get all planets, create planet, get planet with no data --- app/routes.py | 6 +++--- tests/conftest.py | 22 +++++++++++++++++++++- tests/test_routes.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/app/routes.py b/app/routes.py index 6af23e8ba..4150e9ca3 100644 --- a/app/routes.py +++ b/app/routes.py @@ -50,7 +50,7 @@ def create_planet(): message = f"Planet {new_planet.name} successfully created." - return make_response(message, 201) + return make_response(jsonify(message), 201) @planet_bp.route("", methods=["GET"]) def get_all_planets(): @@ -103,7 +103,7 @@ def update_planet(id): planet_to_update.radius = planet_data["radius"] db.session.commit() - return make_response(f"Planet {planet_to_update.name} updated", 200) + return make_response(jsonify(f"Planet {planet_to_update.name} updated"), 200) @planet_bp.route("/", methods=["DELETE"]) def delete_planet(id): @@ -112,4 +112,4 @@ def delete_planet(id): db.session.delete(planet_to_delete) db.session.commit() - return make_response(f"Planet {planet_to_delete.name} deleted", 200) \ No newline at end of file + return make_response(jsonify(f"Planet {planet_to_delete.name} deleted"), 200) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 1152b36e8..7849a81ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,4 +35,24 @@ def one_planet(app): db.session.add(planet) db.session.commit() - return planet \ No newline at end of file + return planet + +@pytest.fixture +def multiple_planets(app): + # Arrange + planet1 = Planet( + name="Venus", + description="Venus is extremely hot.", + radius="3,760.4 mi" + ) + planet2 = Planet( + name="Earth", + description="Earth is the third planet from the Sun.", + radius="3,958.8 mi" + ) + + planets = [planet1, planet2] + + db.session.add_all(planets) + db.session.commit() + return planets \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index e4847f4a9..3fb4d5ee0 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -21,4 +21,31 @@ def test_get_planet_with_no_data(client): response = client.get(f"/planets/1") response_body = response.get_json() - assert response.status_code == 404 \ No newline at end of file + assert response.status_code == 404 + +def test_get_all_planets_with_valid_data(client, multiple_planets): + response = client.get(f"/planets") + response_body = response.get_json() + + assert response.status_code == 200 + assert len(response_body) == 2 + assert response_body[0]["id"] == multiple_planets[0].id + assert response_body[0]["name"] == multiple_planets[0].name + assert response_body[0]["description"] == multiple_planets[0].description + assert response_body[0]["radius"] == multiple_planets[0].radius + assert response_body[1]["id"] == multiple_planets[1].id + assert response_body[1]["name"] == multiple_planets[1].name + assert response_body[1]["description"] == multiple_planets[1].description + assert response_body[1]["radius"] == multiple_planets[1].radius + +def test_create_one_planet(client): + response = client.post("/planets", json={ + "name": "Yoshi", + "description": "Adorable and egg filled planet.", + "radius": "7,156.9 mi" + }) + + response_body = response.get_json() + + assert response.status_code == 201 + assert response_body == "Planet Yoshi successfully created." From d7d53b00281eb87fdd458b848cd9c023bd36a55c Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Thu, 4 May 2023 13:41:56 -0500 Subject: [PATCH 14/18] refactored code into dictionary creation helper function in class file --- app/models/planet.py | 18 +++++++++++++++++- app/routes.py | 31 ++++++++++--------------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 2e48c4063..3426cce40 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -4,4 +4,20 @@ class Planet(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String) description = db.Column(db.String) - radius = db.Column(db.String) \ No newline at end of file + radius = db.Column(db.String) + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "desription": self.description, + "radius": self.radius + } + + @classmethod + def from_dict(cls, data_dict): + return cls( + name = data_dict["name"], + description = data_dict["description"], + radius = data_dict["radius"] + ) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 4150e9ca3..b8bc30ffb 100644 --- a/app/routes.py +++ b/app/routes.py @@ -40,11 +40,13 @@ @planet_bp.route("", methods=["POST"]) def create_planet(): request_body = request.get_json() - new_planet = Planet( - name = request_body["name"], - description = request_body["description"], - radius = request_body["radius"] - ) + # new_planet = Planet( + # name = request_body["name"], + # description = request_body["description"], + # radius = request_body["radius"] + # ) + new_planet = Planet.from_dict(request_body) + db.session.add(new_planet) db.session.commit() @@ -55,15 +57,7 @@ def create_planet(): @planet_bp.route("", methods=["GET"]) def get_all_planets(): planets = Planet.query.all() - results = [] - - for planet in planets: - results.append(dict( - id=planet.id, - name=planet.name, - description=planet.description, - radius = planet.radius - )) + results = [planet.to_dict() for planet in planets] return jsonify(results) @@ -85,13 +79,8 @@ def validate_planet(id): @planet_bp.route("/", methods=["GET"]) def get_planet(id): planet = validate_planet(id) - planet_dict = dict( - id=planet.id, - name=planet.name, - description=planet.description, - radius=planet.radius - ) - return planet_dict + + return planet.to_dict() @planet_bp.route("/", methods=["PUT"]) def update_planet(id): From 4d88e625a79dfdb1532edaa1f41a5ab91866c5a7 Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Thu, 4 May 2023 18:39:57 -0400 Subject: [PATCH 15/18] Update validate function to include cls --- app/routes.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/app/routes.py b/app/routes.py index b8bc30ffb..746688185 100644 --- a/app/routes.py +++ b/app/routes.py @@ -40,19 +40,18 @@ @planet_bp.route("", methods=["POST"]) def create_planet(): request_body = request.get_json() - # new_planet = Planet( - # name = request_body["name"], - # description = request_body["description"], - # radius = request_body["radius"] - # ) - new_planet = Planet.from_dict(request_body) - - db.session.add(new_planet) - db.session.commit() + try: + new_planet = Planet.from_dict(request_body) + + db.session.add(new_planet) + db.session.commit() - message = f"Planet {new_planet.name} successfully created." + message = f"Planet {new_planet.name} successfully created." - return make_response(jsonify(message), 201) + return make_response(jsonify(message), 201) + except KeyError as e: + abort(make_response({"message": f"Missing required value {e}"}, 400)) + @planet_bp.route("", methods=["GET"]) def get_all_planets(): @@ -61,31 +60,31 @@ def get_all_planets(): return jsonify(results) -def validate_planet(id): +def validate_planet(cls, model_id): try: - id = int(id) + model_id = int(model_id) except: - message = f"Planet {id} is invalid" + message = f"{cls.__name__} {model_id} is invalid" abort(make_response({"message": message}, 400)) - planet = Planet.query.get(id) + model = cls.query.get(model_id) - if not planet: - message = f"Planet {id} is not found" + if not model: + message = f"{cls.__name__} {model_id} is not found" abort(make_response({"message": message}, 404)) - return planet + return model @planet_bp.route("/", methods=["GET"]) def get_planet(id): - planet = validate_planet(id) + planet = validate_planet(Planet, id) return planet.to_dict() @planet_bp.route("/", methods=["PUT"]) def update_planet(id): planet_data = request.get_json() - planet_to_update = validate_planet(id) + planet_to_update = validate_planet(Planet, id) planet_to_update.name = planet_data["name"] planet_to_update.description = planet_data["description"] @@ -96,7 +95,7 @@ def update_planet(id): @planet_bp.route("/", methods=["DELETE"]) def delete_planet(id): - planet_to_delete = validate_planet(id) + planet_to_delete = validate_planet(Planet, id) db.session.delete(planet_to_delete) db.session.commit() From 4f04580389f11e9390f6a78c7168b864c97d449c Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Thu, 4 May 2023 18:41:06 -0400 Subject: [PATCH 16/18] Update validate planet to validate model --- app/routes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/routes.py b/app/routes.py index 746688185..691494698 100644 --- a/app/routes.py +++ b/app/routes.py @@ -60,7 +60,7 @@ def get_all_planets(): return jsonify(results) -def validate_planet(cls, model_id): +def validate_model(cls, model_id): try: model_id = int(model_id) except: @@ -77,14 +77,14 @@ def validate_planet(cls, model_id): @planet_bp.route("/", methods=["GET"]) def get_planet(id): - planet = validate_planet(Planet, id) + planet = validate_model(Planet, id) return planet.to_dict() @planet_bp.route("/", methods=["PUT"]) def update_planet(id): planet_data = request.get_json() - planet_to_update = validate_planet(Planet, id) + planet_to_update = validate_model(Planet, id) planet_to_update.name = planet_data["name"] planet_to_update.description = planet_data["description"] @@ -95,7 +95,7 @@ def update_planet(id): @planet_bp.route("/", methods=["DELETE"]) def delete_planet(id): - planet_to_delete = validate_planet(Planet, id) + planet_to_delete = validate_model(Planet, id) db.session.delete(planet_to_delete) db.session.commit() From 0dabdcd6a1476c260e52a6ea745bdcd51921de0d Mon Sep 17 00:00:00 2001 From: Johanna Cabanban Date: Thu, 4 May 2023 19:02:51 -0400 Subject: [PATCH 17/18] Add query params to get all endpoint --- app/routes.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 691494698..04905eb6f 100644 --- a/app/routes.py +++ b/app/routes.py @@ -55,7 +55,15 @@ def create_planet(): @planet_bp.route("", methods=["GET"]) def get_all_planets(): - planets = Planet.query.all() + name_query = request.args.get("name") + radius_query = request.args.get("radius") + if name_query: + planets = Planet.query.filter_by(name=name_query) + elif radius_query: + planets = Planet.query.filter_by(radius=radius_query) + else: + planets = Planet.query.all() + results = [planet.to_dict() for planet in planets] return jsonify(results) From 200f17f19d2cb132180e1e3310166ebfb9e2f9e4 Mon Sep 17 00:00:00 2001 From: Yvett Jimenez Date: Thu, 4 May 2023 18:05:22 -0500 Subject: [PATCH 18/18] all waves completed, project completed hurray --- app/routes.py | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/app/routes.py b/app/routes.py index 04905eb6f..d8ec3f6f8 100644 --- a/app/routes.py +++ b/app/routes.py @@ -2,39 +2,6 @@ from app.models.planet import Planet from flask import Blueprint, jsonify, abort, make_response, request -# class Planet: -# def __init__(self, id, name, description, radius): -# self.id = id -# self.name = name -# self.description = description -# self.radius = radius - -# def make_planet_dict(self): -# return dict( -# id = self.id, -# name = self.name, -# description = self.description, -# radius = self.radius, -# ) - -# planets = [ -# Planet(1, "Mercury", "Mercury is the smallest planet in our solar system.", -# "1,516 mi"), -# Planet(2, "Venus", "In addition to being extremely hot, Venus is unusual"\ -# " because it spins in the opposite direction of Earth and most other planets.", "3,760.4 mi"), -# Planet(3, "Earth", "Earth is the third planet from the Sun and the only place"\ -# " known in the universe where life has originated and found habitability.", "3,958.8 mi"), -# Planet(4, "Mars", "Mars is the fourth planet from the Sun and the third largest"\ -# " and massive terrestrial object in the Solar System.", "2,106.1 mi"), -# Planet(5, "Jupiter", "Jupiter is the fifth planet from the Sun and the largest"\ -# " in the Solar System.", "43,441 mi"), -# Planet(6, "Saturn", "Saturn is the sixth planet from the Sun and the second-"\ -# " largest in the Solar System, after Jupiter.", "36,184 mi"), -# Planet(7, "Uranus", "Uranus is the seventh planet from the Sun and is named"\ -# " after Greek sky deity Uranus.", "15,759.2 mi"), -# Planet(8, "Neptune", "Neptune is the eighth planet from the Sun and the" \ -# " farthest known planet in the Solar System.", "15,299.4 mi")] - planet_bp = Blueprint("planets", __name__, url_prefix="/planets") @planet_bp.route("", methods=["POST"])