From 8be97cffa0e09d32577a53c4d34614f9fdd58d6d Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Sun, 23 Oct 2022 20:06:52 -0400 Subject: [PATCH 01/17] wave 1 --- app/__init__.py | 3 +++ app/routes.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 70b4cabfe..90e1cd4d2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,4 +4,7 @@ def create_app(test_config=None): app = Flask(__name__) + from.routes import planets_bp + app.register_blueprint(planets_bp) + return app diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..dccec9adf 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,29 @@ -from flask import Blueprint +from flask import Blueprint, jsonify +class Planet: + def __init__(self, id, name, color, description): + self.id = id + self.name = name + self.color = color + self.description = description + +planets = [ + Planet(1, "Saturn", "yellowish-brown", "Saturn is the sixth planet from the Sun and the second-largest planet in our solar system."), + Planet(2, "Mars", "rusty red", "Mars is the fourth planet from the Sun – a dusty, cold, desert world with a very thin atmosphere."), + Planet(3, "Jupiter", "beige", "Jupiter is covered in swirling cloud stripes. It has big storms like the Great Red Spot, which has been going for hundreds of years. ") +] + +planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") + +@planets_bp.route("", methods = ["GET"]) +def get_all_planets(): + planets_response = [] + for planet in planets: + planets_response.append( + {"id": planet.id, + "name": planet.name, + "color": planet.color, + "description": planet.description + } + ) + return jsonify(planets_response) \ No newline at end of file From d735bbf356830f462c5d2313ee14f5bedb164ccb Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Sun, 23 Oct 2022 20:19:46 -0400 Subject: [PATCH 02/17] add some planets --- app/routes.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index dccec9adf..9bea9918d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -10,7 +10,15 @@ def __init__(self, id, name, color, description): planets = [ Planet(1, "Saturn", "yellowish-brown", "Saturn is the sixth planet from the Sun and the second-largest planet in our solar system."), Planet(2, "Mars", "rusty red", "Mars is the fourth planet from the Sun – a dusty, cold, desert world with a very thin atmosphere."), - Planet(3, "Jupiter", "beige", "Jupiter is covered in swirling cloud stripes. It has big storms like the Great Red Spot, which has been going for hundreds of years. ") + Planet(3, "Jupiter", "beige", "Jupiter is covered in swirling cloud stripes. It has big storms like the Great Red Spot, which has been going for hundreds of years. "), + Planet(4, "Earth", "blue and green", "Earth is a rocky, terrestrial planet. It has a solid and active surface with mountains, valleys, canyons, plains and so much more."), + Planet(5, "Venus", "beige", "It’s one of the four inner, terrestrial (or rocky) planets, and it’s often called Earth’s twin because it’s similar in size and density."), + Planet(6, "Uranus", "blue", "Uranus is the seventh planet from the Sun, and has the third-largest diameter in our solar system."), + Planet(7, "Neptune", "blue", "Dark, cold, and whipped by supersonic winds, ice giant Neptune is the eighth and most distant planet in our solar system"), + Planet(8, "Pluto", "off-white and light blue", "Pluto is a dwarf planet in the Kuiper Belt, a donut-shaped region of icy bodies beyond the orbit of Neptune."), + Planet(9, "Mercury", "dark gray", "Mercury—the smallest planet in our solar system and closest to the Sun—is only slightly larger than Earth's Moon.") + + ] planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") From 23c530acd5f38a501ece292fa95c8718faf694b9 Mon Sep 17 00:00:00 2001 From: Tanil Date: Mon, 24 Oct 2022 15:16:13 -0400 Subject: [PATCH 03/17] wave 1 and 2 complete --- app/routes.py | 42 ++++++++++++++++++++++++++++++++++++++---- server.py | 0 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 server.py diff --git a/app/routes.py b/app/routes.py index 9bea9918d..614b4feb0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,5 @@ -from flask import Blueprint, jsonify +from flask import Blueprint, jsonify, abort, make_response + class Planet: def __init__(self, id, name, color, description): @@ -28,10 +29,43 @@ def get_all_planets(): planets_response = [] for planet in planets: planets_response.append( - {"id": planet.id, + {"id": planet.id, "name": planet.name, "color": planet.color, "description": planet.description - } + } ) - return jsonify(planets_response) \ No newline at end of file + return jsonify(planets_response) + + +def validate_planet_id(id): + try: + planet_id = int(id) + except: + abort(make_response({"message":f"Planet {id} is invalid"}, 400)) + + for planet in planets: + if planet.id == planet_id: + return planet + + abort(make_response({"message":f"Planet {id} is not found"}, 404)) + +@planets_bp.route("/", methods = ["GET"]) +def get_one_planet(id): + # try: + # planet_id = int(id) + # except: + # abort(make_response({"message":f"Planet {id} is invalid"}, 400)) + + # planet = int(id) + # for planet in planets: + # if planet.id == planet_id: + planet = validate_planet_id(id) + return ( + {"id": planet.id, + "name": planet.name, + "color": planet.color, + "description": planet.description + } + ) + diff --git a/server.py b/server.py new file mode 100644 index 000000000..e69de29bb From 09f95edf14535e3c0a8f5d2d217dbe42aee4e3be Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Fri, 28 Oct 2022 15:10:37 -0400 Subject: [PATCH 04/17] add code to wave 3 --- app/__init__.py | 2 ++ app/models/__init__.py | 0 app/models/planet.py | 0 3 files changed, 2 insertions(+) create mode 100644 app/models/__init__.py create mode 100644 app/models/planet.py diff --git a/app/__init__.py b/app/__init__.py index 90e1cd4d2..6a7d2c172 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,6 @@ from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate def create_app(test_config=None): 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..e69de29bb From 29d074fc398b6d031d2f58817059f5b40ebb5a21 Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Fri, 28 Oct 2022 15:37:22 -0400 Subject: [PATCH 05/17] routes for wave 3 --- app/routes.py | 132 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/app/routes.py b/app/routes.py index 614b4feb0..da5521433 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,57 +1,93 @@ -from flask import Blueprint, jsonify, abort, make_response +from flask import Blueprint, jsonify, abort, make_response, request +from app.models.planet import Planet +from app import db +planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") -class Planet: - def __init__(self, id, name, color, description): - self.id = id - self.name = name - self.color = color - self.description = description - -planets = [ - Planet(1, "Saturn", "yellowish-brown", "Saturn is the sixth planet from the Sun and the second-largest planet in our solar system."), - Planet(2, "Mars", "rusty red", "Mars is the fourth planet from the Sun – a dusty, cold, desert world with a very thin atmosphere."), - Planet(3, "Jupiter", "beige", "Jupiter is covered in swirling cloud stripes. It has big storms like the Great Red Spot, which has been going for hundreds of years. "), - Planet(4, "Earth", "blue and green", "Earth is a rocky, terrestrial planet. It has a solid and active surface with mountains, valleys, canyons, plains and so much more."), - Planet(5, "Venus", "beige", "It’s one of the four inner, terrestrial (or rocky) planets, and it’s often called Earth’s twin because it’s similar in size and density."), - Planet(6, "Uranus", "blue", "Uranus is the seventh planet from the Sun, and has the third-largest diameter in our solar system."), - Planet(7, "Neptune", "blue", "Dark, cold, and whipped by supersonic winds, ice giant Neptune is the eighth and most distant planet in our solar system"), - Planet(8, "Pluto", "off-white and light blue", "Pluto is a dwarf planet in the Kuiper Belt, a donut-shaped region of icy bodies beyond the orbit of Neptune."), - Planet(9, "Mercury", "dark gray", "Mercury—the smallest planet in our solar system and closest to the Sun—is only slightly larger than Earth's Moon.") - +# CREATE RESOURCE +@planets_bp("", methods = ["POST"]) +def create_planet(): + request_body = request.get_json() + new_planet = Planet( + name = request_body['name'], + color = request_body['color'], + description = request_body['descripton'] + ) + db.session.add(new_planet) + db.session.commit() -] + return make_response(f"Planet {new_planet.name} has been created", 201) -planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") -@planets_bp.route("", methods = ["GET"]) +# GET ALL RESOURCES +@planets_bp("", methods = ["GET"]) def get_all_planets(): - planets_response = [] - for planet in planets: - planets_response.append( - {"id": planet.id, + results_list = [] + all_planets = Planet.query.all() + + for planet in all_planets: + results_list.append( + {"id": planet.id, "name": planet.name, "color": planet.color, "description": planet.description - } - ) - return jsonify(planets_response) + }) + + return jsonify(results_list), 200 + + +# # class Planet: +# # def __init__(self, id, name, color, description): +# # self.id = id +# # self.name = name +# # self.color = color +# # self.description = description -def validate_planet_id(id): - try: - planet_id = int(id) - except: - abort(make_response({"message":f"Planet {id} is invalid"}, 400)) +# # planets = [ +# # Planet(1, "Saturn", "yellowish-brown", "Saturn is the sixth planet from the Sun and the second-largest planet in our solar system."), +# # Planet(2, "Mars", "rusty red", "Mars is the fourth planet from the Sun – a dusty, cold, desert world with a very thin atmosphere."), +# # Planet(3, "Jupiter", "beige", "Jupiter is covered in swirling cloud stripes. It has big storms like the Great Red Spot, which has been going for hundreds of years. "), +# # Planet(4, "Earth", "blue and green", "Earth is a rocky, terrestrial planet. It has a solid and active surface with mountains, valleys, canyons, plains and so much more."), +# # Planet(5, "Venus", "beige", "It’s one of the four inner, terrestrial (or rocky) planets, and it’s often called Earth’s twin because it’s similar in size and density."), +# # Planet(6, "Uranus", "blue", "Uranus is the seventh planet from the Sun, and has the third-largest diameter in our solar system."), +# # Planet(7, "Neptune", "blue", "Dark, cold, and whipped by supersonic winds, ice giant Neptune is the eighth and most distant planet in our solar system"), +# # Planet(8, "Pluto", "off-white and light blue", "Pluto is a dwarf planet in the Kuiper Belt, a donut-shaped region of icy bodies beyond the orbit of Neptune."), +# # Planet(9, "Mercury", "dark gray", "Mercury—the smallest planet in our solar system and closest to the Sun—is only slightly larger than Earth's Moon.") + + +# # ] + +# # planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") + +# # @planets_bp.route("", methods = ["GET"]) +# # def get_all_planets(): +# # planets_response = [] +# # for planet in planets: +# # planets_response.append( +# # {"id": planet.id, +# # "name": planet.name, +# # "color": planet.color, +# # "description": planet.description +# } +# ) +# return jsonify(planets_response) + + +# def validate_planet_id(id): +# try: +# planet_id = int(id) +# except: +# abort(make_response({"message":f"Planet {id} is invalid"}, 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 {id} is not found"}, 404)) +# abort(make_response({"message":f"Planet {id} is not found"}, 404)) -@planets_bp.route("/", methods = ["GET"]) -def get_one_planet(id): +# @planets_bp.route("/", methods = ["GET"]) +# def get_one_planet(id): # try: # planet_id = int(id) # except: @@ -60,12 +96,12 @@ def get_one_planet(id): # planet = int(id) # for planet in planets: # if planet.id == planet_id: - planet = validate_planet_id(id) - return ( - {"id": planet.id, - "name": planet.name, - "color": planet.color, - "description": planet.description - } - ) + # planet = validate_planet_id(id) + # return ( + # {"id": planet.id, + # "name": planet.name, + # "color": planet.color, + # "description": planet.description + # } + # ) From fe94c5af9f153e7117a696ab91e51fedbe6dc3ed Mon Sep 17 00:00:00 2001 From: Tanil Date: Fri, 28 Oct 2022 15:39:52 -0400 Subject: [PATCH 06/17] wave 3 --- app/__init__.py | 10 +++- app/models/planet.py | 12 +++++ app/routes.py | 8 ---- migrations/README | 1 + migrations/alembic.ini | 45 ++++++++++++++++++ migrations/env.py | 96 +++++++++++++++++++++++++++++++++++++++ migrations/script.py.mako | 24 ++++++++++ 7 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako diff --git a/app/__init__.py b/app/__init__.py index 6a7d2c172..05f6cb7e4 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,11 +1,17 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate +import sqlalchemy - +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_systems' + db.init_app(app) + migrate.init_app(app,db) + from.models.planet import Planet from.routes import planets_bp app.register_blueprint(planets_bp) diff --git a/app/models/planet.py b/app/models/planet.py index e69de29bb..81c07a0a2 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -0,0 +1,12 @@ +from app import db + +class Planet (db.Model): + id = db.Column(db.Integer, primary_key = True, autoincrement = True) + name = db.Column(db.String) + color = db.Column(db.String) + description = db.Column(db.String) + + + + + \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 614b4feb0..bcdb8ebab 100644 --- a/app/routes.py +++ b/app/routes.py @@ -52,14 +52,6 @@ def validate_planet_id(id): @planets_bp.route("/", methods = ["GET"]) def get_one_planet(id): - # try: - # planet_id = int(id) - # except: - # abort(make_response({"message":f"Planet {id} is invalid"}, 400)) - - # planet = int(id) - # for planet in planets: - # if planet.id == planet_id: planet = validate_planet_id(id) return ( {"id": 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"} From 78007c1121e904da7ccbc0ab126c9e95a70953ab Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Fri, 28 Oct 2022 19:26:28 -0400 Subject: [PATCH 07/17] fix wave 3 --- app/routes.py | 2 +- migrations/versions/ad0e864a9208_test.py | 34 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/ad0e864a9208_test.py diff --git a/app/routes.py b/app/routes.py index 110c40409..9119c4a5d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -11,7 +11,7 @@ def create_planet(): new_planet = Planet( name = request_body['name'], color = request_body['color'], - description = request_body['descripton'] + description = request_body['description'] ) db.session.add(new_planet) db.session.commit() diff --git a/migrations/versions/ad0e864a9208_test.py b/migrations/versions/ad0e864a9208_test.py new file mode 100644 index 000000000..a0fad1e9b --- /dev/null +++ b/migrations/versions/ad0e864a9208_test.py @@ -0,0 +1,34 @@ +"""test + +Revision ID: ad0e864a9208 +Revises: +Create Date: 2022-10-28 19:20:31.083820 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ad0e864a9208' +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('color', sa.String(), nullable=True), + sa.Column('description', 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 9b534cd37c7392dc1b644b6ac9eae0968ff0c97f Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Tue, 1 Nov 2022 13:54:20 -0400 Subject: [PATCH 08/17] add update and delete endpoint --- app/routes.py | 69 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/app/routes.py b/app/routes.py index 9119c4a5d..148ef73ac 100644 --- a/app/routes.py +++ b/app/routes.py @@ -4,6 +4,19 @@ planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") +# HELPER FUNCTION +def validate_planet_id(id): + try: + planet_id = int(id) + except: + abort(make_response({"message":f"Planet {id} is invalid"}, 400)) + + planet = Planet.query.get(planet_id) + if not planet: + abort(make_response({"message":f"Planet {id} is not found"}, 404)) + + return planet + # CREATE RESOURCE @planets_bp.route("", methods = ["POST"]) def create_planet(): @@ -36,6 +49,50 @@ def get_all_planets(): return jsonify(results_list), 200 +# GET ALL RESOURCES +@planets_bp.route("/", methods = ["GET"]) +def get_one_planet(planet_id): + planet = validate_planet_id(planet_id) + + return ( + {"id": planet.id, + "name": planet.name, + "color": planet.color, + "description": planet.description + } + ) + + +# UPDATE RESOURCE +@planets_bp.route("/", methods = ["PUT"]) +def update_planet(planet_id): + planet = validate_planet_id(planet_id) + + request_body = request.get_json() + + planet.name = request_body["name"] + planet.color = request_body["color"] + planet.description = request_body["description"] + + db.session.commit() + + return make_response(f"planet {planet_id} successfully updated") + + +# DELETE RESOURCE +@planets_bp.route("/", methods = ["DELETE"]) +def delete_planet(planet_id): + planet = validate_planet_id(planet_id) + + db.session.delete(planet) + db.session.commit() + + return make_response(f"planet #{planet_id} successfully deleted") + + + + + # # class Planet: # # def __init__(self, id, name, color, description): @@ -74,17 +131,7 @@ def get_all_planets(): # return jsonify(planets_response) -# def validate_planet_id(id): -# try: -# planet_id = int(id) -# except: -# abort(make_response({"message":f"Planet {id} is invalid"}, 400)) - -# for planet in planets: -# if planet.id == planet_id: -# return planet - -# abort(make_response({"message":f"Planet {id} is not found"}, 404)) + # @planets_bp.route("/", methods = ["GET"]) # def get_one_planet(id): From 18736affad81d85cf7f8b32a8751b3db66d7eee9 Mon Sep 17 00:00:00 2001 From: Tanil Date: Wed, 2 Nov 2022 13:57:16 -0400 Subject: [PATCH 09/17] refactors wave 5 --- app/routes.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/app/routes.py b/app/routes.py index 148ef73ac..acccac962 100644 --- a/app/routes.py +++ b/app/routes.py @@ -36,16 +36,20 @@ def create_planet(): @planets_bp.route("", methods = ["GET"]) def get_all_planets(): results_list = [] - all_planets = Planet.query.all() - - for planet in all_planets: - results_list.append( - {"id": planet.id, - "name": planet.name, - "color": planet.color, - "description": planet.description - }) + color_param= request.args.get("color") + description_param= request.args.get("description") + + if color_param: + planets = Planet.query.filter_by(color=color_param) + elif description_param: + planets = Planet.query.filter_by(description=description_param) + else: + planets = Planet.query.all() + + for planet in planets: + results_list.append(planet.to_dict()) + return jsonify(results_list), 200 @@ -54,13 +58,7 @@ def get_all_planets(): def get_one_planet(planet_id): planet = validate_planet_id(planet_id) - return ( - {"id": planet.id, - "name": planet.name, - "color": planet.color, - "description": planet.description - } - ) + return jsonify(planet.to_dict()) # UPDATE RESOURCE From 75dc241c6fc5b251cb7de5bb860c02a6502d885b Mon Sep 17 00:00:00 2001 From: Tanil Date: Wed, 2 Nov 2022 14:05:37 -0400 Subject: [PATCH 10/17] refactor wave 5 again --- app/models/planet.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/planet.py b/app/models/planet.py index 81c07a0a2..7fe8c266e 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -6,7 +6,18 @@ class Planet (db.Model): color = db.Column(db.String) description = db.Column(db.String) - + def to_dict(self): + planet_as_dict = {} + planet_as_dict["id"] = self.id + planet_as_dict["name"] = self.name + planet_as_dict["color"] = self.color + planet_as_dict["description"] = self.description + # return({ + # "id": self.id, + # "name": self.name, + # "color": self.color, + # "description": self.description + # }) \ No newline at end of file From 7c3ab345f77ccfa3d1dbbf42cf6c0149da678372 Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Wed, 2 Nov 2022 14:52:37 -0400 Subject: [PATCH 11/17] refactor --- app/models/planet.py | 37 ++++++++++++++++++++++++------------- app/routes.py | 28 +++++++++++++--------------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 7fe8c266e..6f3bb8411 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -1,4 +1,7 @@ +from os import abort from app import db +from flask import make_response + class Planet (db.Model): id = db.Column(db.Integer, primary_key = True, autoincrement = True) @@ -7,17 +10,25 @@ class Planet (db.Model): description = db.Column(db.String) def to_dict(self): - planet_as_dict = {} - planet_as_dict["id"] = self.id - planet_as_dict["name"] = self.name - planet_as_dict["color"] = self.color - planet_as_dict["description"] = self.description - # return({ - # "id": self.id, - # "name": self.name, - # "color": self.color, - # "description": self.description - # }) - - + return({ + "id": self.id, + "name": self.name, + "color": self.color, + "description": self.description + }) + @classmethod + def from_json(cls, req_body): + return Planet( + name = req_body["name"], + color = req_body["color"], + description = req_body["description"]) + + def update(self, req_body): + try: + self.name = req_body["name"] + self.color = req_body["color"] + self.description = req_body["description"] + except KeyError as error: + return (make_response({'message': "Missing Key", "error": error})) + \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index acccac962..249078efa 100644 --- a/app/routes.py +++ b/app/routes.py @@ -5,27 +5,25 @@ planets_bp = Blueprint("planets_bp", __name__, url_prefix = "/planets") # HELPER FUNCTION -def validate_planet_id(id): +def validate_id(class_obj, id): try: - planet_id = int(id) + object_id = int(id) except: - abort(make_response({"message":f"Planet {id} is invalid"}, 400)) + abort(make_response({"message":f"{class_obj} {id} is invalid"}, 400)) - planet = Planet.query.get(planet_id) - if not planet: - abort(make_response({"message":f"Planet {id} is not found"}, 404)) + query_result = class_obj.query.get(object_id) + if not query_result: + abort(make_response({"message":f"{class_obj} {id} is not found"}, 404)) - return planet + return query_result # CREATE RESOURCE @planets_bp.route("", methods = ["POST"]) def create_planet(): request_body = request.get_json() - new_planet = Planet( - name = request_body['name'], - color = request_body['color'], - description = request_body['description'] - ) + + new_planet = Planet.from_json(request_body) + db.session.add(new_planet) db.session.commit() @@ -56,7 +54,7 @@ def get_all_planets(): # GET ALL RESOURCES @planets_bp.route("/", methods = ["GET"]) def get_one_planet(planet_id): - planet = validate_planet_id(planet_id) + planet = validate_id(Planet, planet_id) return jsonify(planet.to_dict()) @@ -64,7 +62,7 @@ def get_one_planet(planet_id): # UPDATE RESOURCE @planets_bp.route("/", methods = ["PUT"]) def update_planet(planet_id): - planet = validate_planet_id(planet_id) + planet = validate_id(Planet, planet_id) request_body = request.get_json() @@ -80,7 +78,7 @@ def update_planet(planet_id): # DELETE RESOURCE @planets_bp.route("/", methods = ["DELETE"]) def delete_planet(planet_id): - planet = validate_planet_id(planet_id) + planet = validate_id(Planet, planet_id) db.session.delete(planet) db.session.commit() From f91c8fbb45a9a28eb9fc9cc0f7901d04cd79d5f9 Mon Sep 17 00:00:00 2001 From: Tanil Date: Wed, 2 Nov 2022 14:53:15 -0400 Subject: [PATCH 12/17] minor changes --- app/models/planet.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 7fe8c266e..d9a782299 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -7,17 +7,12 @@ class Planet (db.Model): description = db.Column(db.String) def to_dict(self): - planet_as_dict = {} - planet_as_dict["id"] = self.id - planet_as_dict["name"] = self.name - planet_as_dict["color"] = self.color - planet_as_dict["description"] = self.description - # return({ - # "id": self.id, - # "name": self.name, - # "color": self.color, - # "description": self.description - # }) + return({ + "id": self.id, + "name": self.name, + "color": self.color, + "description": self.description + }) \ No newline at end of file From 00ccb0d8272706a5e99de009aba6bc53548be3fd Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Wed, 2 Nov 2022 15:59:07 -0400 Subject: [PATCH 13/17] update --- app/models/planet.py | 4 ++-- app/routes.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 6f3bb8411..98ea9d74d 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -18,7 +18,7 @@ def to_dict(self): }) @classmethod def from_json(cls, req_body): - return Planet( + return cls( name = req_body["name"], color = req_body["color"], description = req_body["description"]) @@ -29,6 +29,6 @@ def update(self, req_body): self.color = req_body["color"] self.description = req_body["description"] except KeyError as error: - return (make_response({'message': "Missing Key", "error": error})) + abort(make_response({'message': f"Missing attribute: {error}"})) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 249078efa..7422c19d1 100644 --- a/app/routes.py +++ b/app/routes.py @@ -66,9 +66,7 @@ def update_planet(planet_id): request_body = request.get_json() - planet.name = request_body["name"] - planet.color = request_body["color"] - planet.description = request_body["description"] + planet.update(request_body) db.session.commit() From b424e888fa14e6f7ec19c16f1fc42df9cc7a65d8 Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Thu, 3 Nov 2022 14:03:32 -0400 Subject: [PATCH 14/17] refactor update --- app/models/planet.py | 3 ++- app/routes.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 98ea9d74d..67980a2ad 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -29,6 +29,7 @@ def update(self, req_body): self.color = req_body["color"] self.description = req_body["description"] except KeyError as error: - abort(make_response({'message': f"Missing attribute: {error}"})) + raise error + \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 7422c19d1..05444812b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -66,7 +66,10 @@ def update_planet(planet_id): request_body = request.get_json() - planet.update(request_body) + try: + planet.update(request_body) + except KeyError as error: + return make_response({'message': f"Missing attribute: {error}"}, 400) db.session.commit() From dab2c1c8b0be70e62c8a26f9cf98fdca497b151b Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Thu, 3 Nov 2022 20:57:30 -0400 Subject: [PATCH 15/17] tests set up --- app/__init__.py | 14 +++++++++++++- tests/__init__.py | 0 tests/conftest.py | 35 +++++++++++++++++++++++++++++++++++ tests/test_routes.py | 9 +++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_routes.py diff --git a/app/__init__.py b/app/__init__.py index 05f6cb7e4..997b22a2d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,16 +1,28 @@ +from dotenv import load_dotenv from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate import sqlalchemy +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_systems' + + if test_config is None: + app.config['SQLALCHEMY_DATABASE_URI']= os.environ.get("SQLALCHEMY_DATABASE_URI") + else: + app.config["TESTING"] = True + app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") + db.init_app(app) migrate.init_app(app,db) + from.models.planet import Planet from.routes import planets_bp app.register_blueprint(planets_bp) 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..346c7d061 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,35 @@ +import pytest +from app import create_app, db +from app.models.planet import Planet +from flask.signals import request_finished + +@pytest.fixture +def app(): + app = create_app({"TESTING": True}) + + @request_finished.connect_via(app) + def expire_session(send, response, **extra): + db.session.remove() + + with app.app_context(): + db.create_all() + yield app + + with app.app_context(): + db.drop_all() + +@pytest.fixture +def client(app): + return app.test_client() + +@pytest.fixture +def one_planet(app): + planet = Planet( + name = "Uranus", + color = "blue", + description = "Uranus is the seventh planet from the Sun, and has the third-largest diameter in our solar system." + ) + db.session.add(planet) + db.session.commit() + db.session.refresh(planet, ["id"]) + return planet \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py new file mode 100644 index 000000000..028109e95 --- /dev/null +++ b/tests/test_routes.py @@ -0,0 +1,9 @@ +from app.models.planet import Planet + +def test_get_all_planets_returns_empty_list_when_db_is_empty(client): + #act + response = client.get("/planets") + + #assert + assert response.status_code == 200 + assert response.get_json() == [] \ No newline at end of file From 51a0e9e2e6beca4c000bbe8fbd05fc85338cc3c3 Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Thu, 3 Nov 2022 23:03:57 -0400 Subject: [PATCH 16/17] add testing --- tests/conftest.py | 3 +- tests/test_routes.py | 92 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 346c7d061..446942b02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,4 +32,5 @@ def one_planet(app): db.session.add(planet) db.session.commit() db.session.refresh(planet, ["id"]) - return planet \ No newline at end of file + return planet + diff --git a/tests/test_routes.py b/tests/test_routes.py index 028109e95..2dfa186c5 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,9 +1,99 @@ from app.models.planet import Planet +def create_mock_data(client): + a = { + "name": "foo1", + "color": "bar1", + "description": "foobar1" + } + + b = { + "name": "foo2", + "color": "bar2", + "description": "foobar2" + } + + c = { + "name": "foo3", + "color": "bar3", + "description": "foobar3" + } + + client.post("/planets", json=a) + client.post("/planets", json=b) + client.post("/planets", json=c) + def test_get_all_planets_returns_empty_list_when_db_is_empty(client): #act response = client.get("/planets") #assert assert response.status_code == 200 - assert response.get_json() == [] \ No newline at end of file + assert response.get_json() == [] + +def test_get_all_planets(client, three_planets): + #act + # create_mock_data(client) + response = client.get("/planets") + response_body = response.get_json() + + #assert + assert response.status_code == 200 + assert len(response_body) == 3 + assert response_body[0]["name"] == "foo1" + assert response_body[1]["name"] == "foo2" + assert response_body[2]["name"] == "foo3" + assert response_body[0]["color"] == "bar1" + assert response_body[1]["color"] == "bar2" + assert response_body[2]["color"] == "bar3" + assert response_body[0]["description"] == "foobar1" + assert response_body[1]["description"] == "foobar2" + assert response_body[2]["description"] == "foobar3" + +def test_get_one_planet_returns_seeded_planet(client, one_planet): + #act + response = client.get(f"/planets/{one_planet.id}") + response_body = response.get_json() + + #assert + assert response.status_code == 200 + assert response_body["id"] == one_planet.id + assert response_body["name"] == one_planet.name + assert response_body["color"] == one_planet.color + assert response_body["description"] == one_planet.description + + +def test_get_one_planet_returns_not_found_planet(client, one_planet): + #act + response = client.get(f"/planets/10") + response_body = response.get_json() + + #assert + assert response.status_code == 404 + + + +def test_create_planet_can_create_planet_in_empty_db(client): + # arrange + EXPECTED_PLANET = { + "name": "Neptune", + "color": "blue", + "description": "Dark, cold, and whipped by supersonic winds, ice giant Neptune is the eighth and most distant planet in our solar system" + } + + EXPECTED_ID = 1 + + # act + response = client.post("/planets", json=EXPECTED_PLANET) + response_body = response.get_data(as_text=True) + + + actual_planet = Planet.query.get(EXPECTED_ID) + + # assert + assert response.status_code == 201 + assert response_body == f"Planet {EXPECTED_PLANET['name']} has been created" + assert actual_planet.id == EXPECTED_ID + assert actual_planet.name == EXPECTED_PLANET["name"] + assert actual_planet.color == EXPECTED_PLANET["color"] + assert actual_planet.description == EXPECTED_PLANET["description"] From be96ae4c884eb88615e4933f46e95e4bde314caf Mon Sep 17 00:00:00 2001 From: Siyi Ji Date: Fri, 4 Nov 2022 09:10:55 -0400 Subject: [PATCH 17/17] fix test_get_all_planets --- tests/test_routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_routes.py b/tests/test_routes.py index 2dfa186c5..7ba8208cc 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -31,9 +31,9 @@ def test_get_all_planets_returns_empty_list_when_db_is_empty(client): assert response.status_code == 200 assert response.get_json() == [] -def test_get_all_planets(client, three_planets): +def test_get_all_planets(client): #act - # create_mock_data(client) + create_mock_data(client) response = client.get("/planets") response_body = response.get_json()