From 5bf5eecf0b2a7a5a65e229d555e96ec62ab6f7c2 Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Tue, 25 Apr 2023 16:24:14 -0400 Subject: [PATCH 01/20] completes wave 1&2 --- app/__init__.py | 9 ++++++++- app/routes.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 70b4cabfe..42bbe3d20 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +1,14 @@ 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__) - + from .routes import planets_bp + app.register_blueprint(planets_bp) + return app diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..458c75647 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,54 @@ -from flask import Blueprint +from flask import Blueprint, jsonify, abort, make_response +class Planet: + def __init__(self, id, name, description, color): + self.id =id + self.name = name + self.description = description + self.color = color + + def planet_to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "color": self.color + } + + +planets = [ + Planet(1,"big", "Pretty", "Purple"), + Planet(2,"bigg", "Round", "Orange"), + Planet(3,"bigger", "Lumpy", "Rainbow"), + Planet(4,"biggerthan","Wiggly", "Blue"), +] + +planets_bp = Blueprint("planets", __name__, url_prefix="/planets") + +@planets_bp.route("", methods = ["GET"]) +def handle_planets(): + result_list = [] + + for planet in planets: + result_list.append(planet.planet_to_dict()) + + return jsonify(result_list) + +def validate_planet(id): + try: + id = int(id) + except: + abort(make_response({"message":f"Planet {id} invaid"}, 400)) + + for planet in planets: + if planet.id == id: + return planet + + abort(make_response({"message":f"Planet {id} not found"}, 404)) + +@planets_bp.route("/", methods=["GET"]) +def handle_planet(id): + planet = validate_planet(id) + return planet.planet_to_dict() + + From abe7809292a5ff666f98d6e55e7b99ff7179fbf0 Mon Sep 17 00:00:00 2001 From: IzzyBravo Date: Tue, 25 Apr 2023 13:25:58 -0700 Subject: [PATCH 02/20] completed --- app/__init__.py | 8 ++++++ app/routes.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 70b4cabfe..b72e07834 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +1,15 @@ 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__) + from .routes import planets_bp + app.register_blueprint(planets_bp) + return app diff --git a/app/routes.py b/app/routes.py index 8e9dfe684..d96261cb5 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,2 +1,76 @@ -from flask import Blueprint +from flask import Blueprint,abort, make_response, jsonify, request +from app import db +class Planet: + def __init__(self,id, name, description,color): + self.id = id + self.name = name + self.description = description + self.color = color + + def planet_to_dict(self): + return { + "id":self.id, + "name": self.name, + "description":self.description, + "color":self.color + } + + +planets = [ + Planet(1,"big","pretty", "purple"), + Planet(2,"bigg","round","orange"), + Planet(3,"bigger","lumpy", "rainbow"), + Planet(4,"biggerthan","wiggly","blue"), + +] + +planets_bp = Blueprint("planets", __name__, url_prefix="/planets") + + +@planets_bp.route("", methods = ["GET"]) +def handle_planets(): + result_list = [] + + for planet in planets: + result_list.append(planet.planet_to_dict()) + + return jsonify(result_list) + +def validate_planet(id): + try: + id = int(id) + except: + abort(make_response({"message": f"planet{id} invalid planet"}, 400)) + + for planet in planets: + if planet.id == id: + return planet + + abort(make_response({"message":f"Planet{id} not found"},404)) + + +@planets_bp.route("/", methods=["GET"]) +def handle_planet(id): + planet = validate_planet(id) + return planet.planet_to_dict() + + +@planets_bp.route("/", methods=["PUT"]) +def update_planet(id): + planet = validate_planet(id) + + request_body = request.get_json() + + planet.description = request_body["description"] + + db.session.commit() + + return make_response(f"planet #{id} successfully updated") + + + + + + + \ No newline at end of file From 09d3931921991f63e2c12af393f2cb3782979224 Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Fri, 28 Apr 2023 13:47:51 -0400 Subject: [PATCH 03/20] creates POST endpoint --- app/__init__.py | 13 +++ app/models/__init__.py | 0 app/models/planet.py | 19 ++++ app/routes.py | 72 ++++++++------ migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ .../a6fa2b4e7d4b_adds_a_planet_model.py | 34 +++++++ 9 files changed, 275 insertions(+), 29 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/a6fa2b4e7d4b_adds_a_planet_model.py diff --git a/app/__init__.py b/app/__init__.py index 42bbe3d20..f1fdf0497 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,13 +2,26 @@ from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate +# postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development + db =SQLAlchemy() migrate = Migrate() def create_app(test_config=None): app = Flask(__name__) + + #DB Config + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development' + + db.init_app(app) + migrate.init_app(app, db) + from app.models.planet import Planet + from .routes import planets_bp app.register_blueprint(planets_bp) + + # from app.models. import return app 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..0f5e70380 --- /dev/null +++ b/app/models/planet.py @@ -0,0 +1,19 @@ +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) + color = db.Column(db.String) + # __tablename__ = "planets" + + def planet_to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "color": self.color } + + + def to_string(self): + return f"{self.id}: {self.name} Description {self.description} " \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 458c75647..df77d21b5 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,38 +1,52 @@ -from flask import Blueprint, jsonify, abort, make_response - -class Planet: - def __init__(self, id, name, description, color): - self.id =id - self.name = name - self.description = description - self.color = color +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, color): +# self.id =id +# self.name = name +# self.description = description +# self.color = color - def planet_to_dict(self): - return { - "id": self.id, - "name": self.name, - "description": self.description, - "color": self.color - } - - -planets = [ - Planet(1,"big", "Pretty", "Purple"), - Planet(2,"bigg", "Round", "Orange"), - Planet(3,"bigger", "Lumpy", "Rainbow"), - Planet(4,"biggerthan","Wiggly", "Blue"), -] +# def planet_to_dict(self): +# return { +# "id": self.id, +# "name": self.name, +# "description": self.description, +# "color": self.color +# } -planets_bp = Blueprint("planets", __name__, url_prefix="/planets") +# planets = [ +# Planet(1,"big", "Pretty", "Purple"), +# Planet(2,"bigg", "Round", "Orange"), +# Planet(3,"bigger", "Lumpy", "Rainbow"), +# Planet(4,"biggerthan","Wiggly", "Blue"), +# ] + +planets_bp = Blueprint("planets", __name__, url_prefix="/planets") @planets_bp.route("", methods = ["GET"]) -def handle_planets(): - result_list = [] - +def read_all_planets(): + planets = Planet.query.all() + planets_response = [] for planet in planets: - result_list.append(planet.planet_to_dict()) + planets_response.append(planet.planet_to_dict()) + return jsonify(planets_response) + +@planets_bp.route("", methods = ["POST"]) +def create_planets(): + request_body = request.get_json() + if "name" not in request_body or "description" not in request_body: + return make_response("Invalid Requesrt", 400) + new_planet = Planet( + name=request_body["name"], + description=request_body["description"], + color=request_body["color"]) + db.session.add(new_planet) + db.session.commit() - return jsonify(result_list) + return make_response(f"Planet {new_planet.name} successfully created", 201) def validate_planet(id): try: 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/a6fa2b4e7d4b_adds_a_planet_model.py b/migrations/versions/a6fa2b4e7d4b_adds_a_planet_model.py new file mode 100644 index 000000000..6fbff8c65 --- /dev/null +++ b/migrations/versions/a6fa2b4e7d4b_adds_a_planet_model.py @@ -0,0 +1,34 @@ +"""adds a planet model + +Revision ID: a6fa2b4e7d4b +Revises: +Create Date: 2023-04-25 18:01:36.589170 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a6fa2b4e7d4b' +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('color', 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 956f55f2c09313fab515592a8df21a636c740f11 Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Fri, 28 Apr 2023 19:35:03 -0400 Subject: [PATCH 04/20] handles single planet --- app/models/planet.py | 4 +++- app/routes.py | 34 +++++----------------------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 0f5e70380..41b963071 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -16,4 +16,6 @@ def planet_to_dict(self): def to_string(self): - return f"{self.id}: {self.name} Description {self.description} " \ No newline at end of file + return f"{self.id}: {self.name} Description {self.description} " + + \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index df77d21b5..b36b6050d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -2,29 +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, color): -# self.id =id -# self.name = name -# self.description = description -# self.color = color - -# def planet_to_dict(self): -# return { -# "id": self.id, -# "name": self.name, -# "description": self.description, -# "color": self.color -# } - - -# planets = [ -# Planet(1,"big", "Pretty", "Purple"), -# Planet(2,"bigg", "Round", "Orange"), -# Planet(3,"bigger", "Lumpy", "Rainbow"), -# Planet(4,"biggerthan","Wiggly", "Blue"), -# ] - planets_bp = Blueprint("planets", __name__, url_prefix="/planets") @planets_bp.route("", methods = ["GET"]) def read_all_planets(): @@ -54,15 +31,14 @@ def validate_planet(id): except: abort(make_response({"message":f"Planet {id} invaid"}, 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) + if not planet: + abort(make_response({"message":f"Planet {id} not found"}, 404)) + return planet @planets_bp.route("/", methods=["GET"]) def handle_planet(id): planet = validate_planet(id) + planet = Planet.query.get(id) return planet.planet_to_dict() From 0f8994c0226c9bf3186dbb1ed9d4a466197f25bd Mon Sep 17 00:00:00 2001 From: IzzyBravo Date: Mon, 1 May 2023 15:51:29 -0700 Subject: [PATCH 05/20] updated and deleted endpoints --- app/routes.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index b36b6050d..54b8acf3f 100644 --- a/app/routes.py +++ b/app/routes.py @@ -35,10 +35,38 @@ def validate_planet(id): if not planet: abort(make_response({"message":f"Planet {id} not found"}, 404)) return planet + @planets_bp.route("/", methods=["GET"]) -def handle_planet(id): +def read_one_planet(id): planet = validate_planet(id) planet = Planet.query.get(id) return planet.planet_to_dict() + +@planets_bp.route("/", methods=["PUT"]) +def update_planet(id): + planet = validate_planet(id) + + request_body = request.get_json() + + planet.name = request_body["name"] + planet.description=request_body["description"] + planet.color=request_body["color"] + + + db.session.commit() + + return make_response (f"Planet #{id} successfully updated") +@planets_bp.route("/", methods=["DELETE"]) +def delete_planet(id): + planet= validate_planet(id) + + db.session.delete(planet) + db.session.commit() + + return make_response (f"Planet #{id} successfully updated") + + + + From 1b26e3275e85cddaa2ec11447d29a307dc462cf7 Mon Sep 17 00:00:00 2001 From: IzzyBravo Date: Mon, 1 May 2023 16:59:39 -0700 Subject: [PATCH 06/20] refactored --- app/models/planet.py | 18 +++++++++++++++++- app/routes.py | 11 ++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 41b963071..b2d42916d 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -1,4 +1,5 @@ from app import db +from flask import make_response class Planet(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) @@ -7,13 +8,28 @@ class Planet(db.Model): color = db.Column(db.String) # __tablename__ = "planets" + + def update(id, planet_to_dict): + for key, value in planet_to_dict.items(): + setattr(id,key,value) + + def planet_to_dict(self): return { "id": self.id, "name": self.name, "description": self.description, "color": self.color } - + + +@classmethod +def create_new_planet(self, request_data): + if "name" not in request_data or "description" not in request_data: + return make_response("Invalid Requesrt", 400) + return self( + name=request_data["name"], + description=request_data["description"], + color=request_data.get("color")) def to_string(self): return f"{self.id}: {self.name} Description {self.description} " diff --git a/app/routes.py b/app/routes.py index 54b8acf3f..a0b2401c8 100644 --- a/app/routes.py +++ b/app/routes.py @@ -46,13 +46,14 @@ def read_one_planet(id): def update_planet(id): planet = validate_planet(id) - request_body = request.get_json() + planet.update(request.get_json()) - planet.name = request_body["name"] - planet.description=request_body["description"] - planet.color=request_body["color"] + #request_body = request.get_json() + + # planet.name = request_body["name"] + # planet.description=request_body["description"] + # planet.color=request_body["color"] - db.session.commit() return make_response (f"Planet #{id} successfully updated") From 13c0fbb3d144d31ade8ae7b75fffbb21f6fc073d Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Tue, 2 May 2023 18:42:12 -0400 Subject: [PATCH 07/20] adds query params --- app/models/planet.py | 16 +++++++++++++++- app/routes.py | 40 +++++++++++++++++++--------------------- app/tests/__init__.py | 0 app/tests/conftest.py | 25 +++++++++++++++++++++++++ app/tests/test_routes.py | 0 5 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 app/tests/__init__.py create mode 100644 app/tests/conftest.py create mode 100644 app/tests/test_routes.py diff --git a/app/models/planet.py b/app/models/planet.py index 41b963071..97c19063f 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -1,4 +1,5 @@ from app import db +from flask import make_response class Planet(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) @@ -13,7 +14,20 @@ def planet_to_dict(self): "name": self.name, "description": self.description, "color": self.color } - + + @classmethod + def create_new_planet(self, request_data): + if "name" not in request_data or "description" not in request_data: + return make_response("Invalid Requesrt", 400) + return self( + name=request_data["name"].title(), + description=request_data["description"], + color=request_data.get("color") + ) + + def update(id, planet_to_dict): + for key, value in planet_to_dict.items(): + setattr(id,key,value) def to_string(self): return f"{self.id}: {self.name} Description {self.description} " diff --git a/app/routes.py b/app/routes.py index 54b8acf3f..43d1ffdba 100644 --- a/app/routes.py +++ b/app/routes.py @@ -3,26 +3,31 @@ from flask import Blueprint, jsonify, abort, make_response, request planets_bp = Blueprint("planets", __name__, url_prefix="/planets") + @planets_bp.route("", methods = ["GET"]) def read_all_planets(): - planets = Planet.query.all() planets_response = [] - for planet in planets: - planets_response.append(planet.planet_to_dict()) + query_params = request.args.to_dict() + + if query_params: + query_params = {k.lower(): v.title() for k, v in query_params.items()} + planets = Planet.query.filter_by(**query_params).all() + else: + planets = Planet.query.all() + + planets_response = [planet.planet_to_dict() for planet in planets] return jsonify(planets_response) @planets_bp.route("", methods = ["POST"]) def create_planets(): request_body = request.get_json() - if "name" not in request_body or "description" not in request_body: - return make_response("Invalid Requesrt", 400) - new_planet = Planet( - name=request_body["name"], - description=request_body["description"], - color=request_body["color"]) + try: + new_planet = Planet.create_new_planet(request_body) + except ValueError: + return make_response("Invalid request", 400) db.session.add(new_planet) db.session.commit() - + # create var for make response return make_response(f"Planet {new_planet.name} successfully created", 201) def validate_planet(id): @@ -46,18 +51,12 @@ def read_one_planet(id): def update_planet(id): planet = validate_planet(id) - request_body = request.get_json() - - planet.name = request_body["name"] - planet.description=request_body["description"] - planet.color=request_body["color"] + planet.update(request.get_json()) - db.session.commit() - return make_response (f"Planet #{id} successfully updated") - - + return make_response(f"Planet #{id} successfully updated") + @planets_bp.route("/", methods=["DELETE"]) def delete_planet(id): planet= validate_planet(id) @@ -65,8 +64,7 @@ def delete_planet(id): db.session.delete(planet) db.session.commit() - return make_response (f"Planet #{id} successfully updated") - + return make_response (f"Planet #{id} successfully deleted", 200) diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/conftest.py b/app/tests/conftest.py new file mode 100644 index 000000000..ac8a4193b --- /dev/null +++ b/app/tests/conftest.py @@ -0,0 +1,25 @@ +import pytest +from app import create_app +from app import 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/app/tests/test_routes.py b/app/tests/test_routes.py new file mode 100644 index 000000000..e69de29bb From 2d68184b03935a6bf962c3c15da5c6928fad1aa4 Mon Sep 17 00:00:00 2001 From: IzzyBravo Date: Tue, 2 May 2023 15:42:15 -0700 Subject: [PATCH 08/20] testing --- app/__init__.py | 10 ++++++++-- app/models/planet.py | 16 ++++++++-------- app/routes.py | 11 +++++------ app/tests/__init__.py | 0 app/tests/conftest.py | 32 ++++++++++++++++++++++++++++++++ app/tests/test_routes.py | 0 6 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 app/tests/__init__.py create mode 100644 app/tests/conftest.py create mode 100644 app/tests/test_routes.py diff --git a/app/__init__.py b/app/__init__.py index f1fdf0497..c3e2c1488 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,11 +1,14 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate +from dotenv import load_dotenv +import os # postgresql+psycopg2://postgres:postgres@localhost:5432/solar_system_development db =SQLAlchemy() migrate = Migrate() +load_dotenv() def create_app(test_config=None): @@ -13,8 +16,11 @@ def create_app(test_config=None): #DB Config 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['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_TEST_DATABASE_URI') + db.init_app(app) migrate.init_app(app, db) from app.models.planet import Planet diff --git a/app/models/planet.py b/app/models/planet.py index b2d42916d..d579d9cb8 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -22,14 +22,14 @@ def planet_to_dict(self): "color": self.color } -@classmethod -def create_new_planet(self, request_data): - if "name" not in request_data or "description" not in request_data: - return make_response("Invalid Requesrt", 400) - return self( - name=request_data["name"], - description=request_data["description"], - color=request_data.get("color")) + @classmethod + def create_new_planet(self, request_data): + if "name" not in request_data or "description" not in request_data: + return make_response("Invalid Requesrt", 400) + return self( + name=request_data["name"], + description=request_data["description"], + color=request_data.get("color")) def to_string(self): return f"{self.id}: {self.name} Description {self.description} " diff --git a/app/routes.py b/app/routes.py index a0b2401c8..72f17982c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -11,15 +11,14 @@ def read_all_planets(): planets_response.append(planet.planet_to_dict()) return jsonify(planets_response) + @planets_bp.route("", methods = ["POST"]) def create_planets(): request_body = request.get_json() - if "name" not in request_body or "description" not in request_body: - return make_response("Invalid Requesrt", 400) - new_planet = Planet( - name=request_body["name"], - description=request_body["description"], - color=request_body["color"]) + try: + new_planet = Planet.create_new_planet(request_body) + except ValueError: + return make_response("Invalid request", 400) db.session.add(new_planet) db.session.commit() diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/conftest.py b/app/tests/conftest.py new file mode 100644 index 000000000..dcab6abd1 --- /dev/null +++ b/app/tests/conftest.py @@ -0,0 +1,32 @@ +import pytest +from app import create_app +from app import db +from flask.signals import request_finished +from app.models.planet import Planet + +#instance of our app that is listening for our test databse +@pytest.fixture +def app(): + app = create_app({"TESTING":True}) + + @request_finished.connect_via(app) + def expire_session(sender, response, **extra): + db.session.remove() + + #our database is clean,its empty of all data + with app.app_context(): + db.create_all() + yield app + + # once test is complete clears out any data that it created + #so we can work with clean database + with app.app_context(): + db.drop_all() + +@pytest.fixture +def client(app): + #app that references another fixture + # holds the reference to the test interface + return app.test_client() + + diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py new file mode 100644 index 000000000..e69de29bb From e8eb4e630d0ca3db98c5553038707611722b491a Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Wed, 3 May 2023 14:16:54 -0400 Subject: [PATCH 09/20] added pytests --- app/routes.py | 10 ++++---- app/tests/conftest.py | 49 ++++++++++++++++++---------------------- app/tests/test_routes.py | 46 +++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 32 deletions(-) diff --git a/app/routes.py b/app/routes.py index 6c37c4eb7..4370d1621 100644 --- a/app/routes.py +++ b/app/routes.py @@ -29,17 +29,17 @@ def create_planets(): db.session.add(new_planet) db.session.commit() # create var for make response - return make_response(f"Planet {new_planet.name} successfully created", 201) + return make_response(jsonify(f"Planet {new_planet.name} successfully created"), 201) def validate_planet(id): try: id = int(id) except: - abort(make_response({"message":f"Planet {id} invaid"}, 400)) + abort(make_response(jsonify({"message":f"Planet {id} invaid"}), 400)) planet = Planet.query.get(id) if not planet: - abort(make_response({"message":f"Planet {id} not found"}, 404)) + abort(make_response(jsonify({"message":f"Planet {id} not found"}), 404)) return planet @planets_bp.route("/", methods=["GET"]) @@ -56,7 +56,7 @@ def update_planet(id): db.session.commit() - return make_response(f"Planet #{id} successfully updated") + return make_response(jsonify(f"Planet #{id} successfully updated")) @planets_bp.route("/", methods=["DELETE"]) def delete_planet(id): @@ -65,7 +65,7 @@ def delete_planet(id): db.session.delete(planet) db.session.commit() - return make_response (f"Planet #{id} successfully deleted", 200) + return make_response(jsonify(f"Planet #{id} successfully deleted"), 200) diff --git a/app/tests/conftest.py b/app/tests/conftest.py index 33f7da3e1..46e8bc759 100644 --- a/app/tests/conftest.py +++ b/app/tests/conftest.py @@ -4,50 +4,45 @@ from flask.signals import request_finished from app.models.planet import Planet -#instance of our app that is listening for our test databse +# instance of our app that is listening for our test db @pytest.fixture def app(): - app = create_app({"TESTING":True}) - + app = create_app({"TESTING": True}) + # helps with the accuracy of tests + # clears out temperary data @request_finished.connect_via(app) def expire_session(sender, response, **extra): db.session.remove() - #our database is clean,its empty of all data + # our database is clean,its empty of all data with app.app_context(): + # generate a new clean, start to db + # an empty db that we can test on db.create_all() + + # tells fixture to return an instance of app context + # sending it to test when test is called yield app # once test is complete clears out any data that it created - #so we can work with clean database + # so we can work with clean database with app.app_context(): db.drop_all() @pytest.fixture def client(app): - #app that references another fixture + # app that references another fixture # holds the reference to the test interface return app.test_client() - -import pytest -from app import create_app -from app import 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() - +def two_saved_planets(app): + # Arrange + ocean_planet = Planet(name="Ocean Planet", + description="Smells fishy", + color="Silver") + minion_planet = Planet(name="Mark", + description="Miniony", + color="Yellow") + db.session.add_all([ocean_planet, minion_planet]) + db.session.commit() \ No newline at end of file diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index e69de29bb..0aa6cc92a 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -0,0 +1,46 @@ +# get all planets and return no records +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 == [] + +# get one planet by id +def test_get_planet_by_id(client, two_saved_planets): + # Act + response1 = client.get('/planets/1') + response2 = client.get('/planets/2') + response_body1 = response1.get_json() + response_body2 = response2.get_json() + + #Assert + assert response1.status_code == 200 + assert response_body1 == { + "id": 1, + "name": "Ocean Planet", + "description": "Smells fishy", + "color": "Silver" + } + assert response_body2 == { + "id": 2, + "name": "Mark", + "description": "Miniony", + "color": "Yellow" + } + +def test_create_one_book(client): + # Act + response = client.post("/planets", json={ + "name": "New Planet", + "description": "Fresh out the box" + }) + response_body = response.get_json() + # alternative if the return statement does not use jsonify + # response_body = response.get_data(as_text=True) + + #Assert + assert response.status_code == 201 + assert response_body == "Planet New Planet successfully created" From 24a26551d6a5a37fc39a4f8a25f4042889cd6bfd Mon Sep 17 00:00:00 2001 From: IzzyBravo Date: Wed, 3 May 2023 11:17:24 -0700 Subject: [PATCH 10/20] deleted files --- app/__init__.py | 2 ++ app/tests/__init__.py | 0 app/tests/conftest.py | 32 -------------------------------- app/tests/test_routes.py | 0 4 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 app/tests/__init__.py delete mode 100644 app/tests/conftest.py delete mode 100644 app/tests/test_routes.py diff --git a/app/__init__.py b/app/__init__.py index c3e2c1488..d2b2f7631 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -16,9 +16,11 @@ def create_app(test_config=None): #DB Config app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + if not test_config: app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_DATABASE_URI') else: + app.config['TESTING'] = True app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_TEST_DATABASE_URI') db.init_app(app) diff --git a/app/tests/__init__.py b/app/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/tests/conftest.py b/app/tests/conftest.py deleted file mode 100644 index dcab6abd1..000000000 --- a/app/tests/conftest.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest -from app import create_app -from app import db -from flask.signals import request_finished -from app.models.planet import Planet - -#instance of our app that is listening for our test databse -@pytest.fixture -def app(): - app = create_app({"TESTING":True}) - - @request_finished.connect_via(app) - def expire_session(sender, response, **extra): - db.session.remove() - - #our database is clean,its empty of all data - with app.app_context(): - db.create_all() - yield app - - # once test is complete clears out any data that it created - #so we can work with clean database - with app.app_context(): - db.drop_all() - -@pytest.fixture -def client(app): - #app that references another fixture - # holds the reference to the test interface - return app.test_client() - - diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py deleted file mode 100644 index e69de29bb..000000000 From 5adc317cae8e167b6afdba7ae8922a351f2963b2 Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Wed, 3 May 2023 15:55:48 -0400 Subject: [PATCH 11/20] adds test for empty database --- app/tests/conftest.py | 7 ++++++- app/tests/test_routes.py | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/tests/conftest.py b/app/tests/conftest.py index 46e8bc759..ad8af8faf 100644 --- a/app/tests/conftest.py +++ b/app/tests/conftest.py @@ -45,4 +45,9 @@ def two_saved_planets(app): description="Miniony", color="Yellow") db.session.add_all([ocean_planet, minion_planet]) - db.session.commit() \ No newline at end of file + db.session.commit() + +# @pytest.fixture +# def empty_list(app): +# empty_list = [] +# return empty_list \ No newline at end of file diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index 0aa6cc92a..8b9d42714 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -44,3 +44,10 @@ def test_create_one_book(client): #Assert assert response.status_code == 201 assert response_body == "Planet New Planet successfully created" + +def test_planets_with_no_data_return_empty_list(client): + response = client.get("/planets/1") + response_body = response.get_json() + + assert response_body == {"message": "Planet 1 not found"} + assert response.status_code == 404 From a50347cea91d74745ec865631f3b4587c0b4e0e2 Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Wed, 3 May 2023 16:13:18 -0400 Subject: [PATCH 12/20] corrects test name --- app/tests/test_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index 8b9d42714..7b04b6c65 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -45,7 +45,7 @@ def test_create_one_book(client): assert response.status_code == 201 assert response_body == "Planet New Planet successfully created" -def test_planets_with_no_data_return_empty_list(client): +def test_planets_with_no_data_returns_404_status_code(client): response = client.get("/planets/1") response_body = response.get_json() From dc1e077fecdf4881aaae12769000036f44f5649d Mon Sep 17 00:00:00 2001 From: IzzyBravo Date: Wed, 3 May 2023 13:13:27 -0700 Subject: [PATCH 13/20] some updates --- app/tests/__init__.py | 0 app/tests/test_routes.py | 8 +------- 2 files changed, 1 insertion(+), 7 deletions(-) create mode 100644 app/tests/__init__.py diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index 8b9d42714..98763cba6 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -1,3 +1,4 @@ + # get all planets and return no records def test_get_all_planets_with_no_records(client): # Act @@ -44,10 +45,3 @@ def test_create_one_book(client): #Assert assert response.status_code == 201 assert response_body == "Planet New Planet successfully created" - -def test_planets_with_no_data_return_empty_list(client): - response = client.get("/planets/1") - response_body = response.get_json() - - assert response_body == {"message": "Planet 1 not found"} - assert response.status_code == 404 From f79cc7a8afac3359f3769d031c67c63aff632c72 Mon Sep 17 00:00:00 2001 From: IzzyBravo Date: Wed, 3 May 2023 13:24:45 -0700 Subject: [PATCH 14/20] update return message --- app/routes.py | 11 +++++++---- app/tests/test_routes.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/routes.py b/app/routes.py index 4370d1621..a02f22154 100644 --- a/app/routes.py +++ b/app/routes.py @@ -29,7 +29,9 @@ def create_planets(): db.session.add(new_planet) db.session.commit() # create var for make response - return make_response(jsonify(f"Planet {new_planet.name} successfully created"), 201) + + message = f"Planet {new_planet.name} successfully created" + return make_response(jsonify(message), 201) def validate_planet(id): try: @@ -55,8 +57,8 @@ def update_planet(id): planet.update(request.get_json()) db.session.commit() - - return make_response(jsonify(f"Planet #{id} successfully updated")) + message = f"Planet #{id} successfully updated" + return make_response(jsonify(message)) @planets_bp.route("/", methods=["DELETE"]) def delete_planet(id): @@ -65,7 +67,8 @@ def delete_planet(id): db.session.delete(planet) db.session.commit() - return make_response(jsonify(f"Planet #{id} successfully deleted"), 200) + message = f"Planet #{id} successfully deleted" + return make_response(jsonify(message), 200) diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index 5c272c6b1..6a3aa78e4 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -32,7 +32,7 @@ def test_get_planet_by_id(client, two_saved_planets): "color": "Yellow" } -def test_create_one_book(client): +def test_create_one_planet(client): # Act response = client.post("/planets", json={ "name": "New Planet", From d936ce5a311fc1ba047a0eabbd54c431b97e9ae5 Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Wed, 3 May 2023 21:10:33 -0400 Subject: [PATCH 15/20] adds pytests for update and delete --- app/tests/conftest.py | 4 ---- app/tests/test_routes.py | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/tests/conftest.py b/app/tests/conftest.py index ad8af8faf..252c88927 100644 --- a/app/tests/conftest.py +++ b/app/tests/conftest.py @@ -47,7 +47,3 @@ def two_saved_planets(app): db.session.add_all([ocean_planet, minion_planet]) db.session.commit() -# @pytest.fixture -# def empty_list(app): -# empty_list = [] -# return empty_list \ No newline at end of file diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index 6a3aa78e4..12bfb9fad 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -52,3 +52,26 @@ def test_planets_with_no_data_returns_404_status_code(client): assert response_body == {"message": "Planet 1 not found"} assert response.status_code == 404 + +def test_update_planet_successfully(client,two_saved_planets): + planet_id = 1 + updated_planet_data = { + "name": "Updated planet name", + "description": "I'm updated", + "color": "Fusha" + } + response = client.put(f"planets/1", json=updated_planet_data) + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == f"Planet #{planet_id} successfully updated" +def test_delete_planet_successfully(client, two_saved_planets): + planet_id = 1 + response = client.delete(f"planets/{planet_id}") + response_body = response.get_json() + + assert response.status_code == 200 + assert response_body == f"Planet #{planet_id} successfully deleted" + + response = client.get(f"/planets/{planet_id}") + assert response.status_code == 404 \ No newline at end of file From 474ee69df11993cc59bc1f4b026956cdc702f11f Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Thu, 4 May 2023 14:52:16 -0400 Subject: [PATCH 16/20] adds generic validate function --- app/models/planet.py | 4 +-- app/routes.py | 63 +++++++++++++++++++++++++++------------- app/tests/test_routes.py | 9 +++++- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 19fbc0b6f..146514fa9 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -22,10 +22,10 @@ def planet_to_dict(self): "color": self.color } @classmethod - def create_new_planet(self, request_data): + def create_new_planet(cls, request_data): if "name" not in request_data or "description" not in request_data: return make_response("Invalid Requesrt", 400) - return self( + return cls( name=request_data["name"].title(), description=request_data["description"], color=request_data.get("color") diff --git a/app/routes.py b/app/routes.py index a02f22154..42f06ad43 100644 --- a/app/routes.py +++ b/app/routes.py @@ -24,35 +24,45 @@ def create_planets(): request_body = request.get_json() try: new_planet = Planet.create_new_planet(request_body) - except ValueError: - return make_response("Invalid request", 400) - db.session.add(new_planet) - db.session.commit() - # create var for make response + db.session.add(new_planet) + db.session.commit() - message = f"Planet {new_planet.name} successfully created" - return make_response(jsonify(message), 201) + message = f"Planet {new_planet.name} successfully created" + return make_response(jsonify(message), 201) -def validate_planet(id): - try: - id = int(id) - except: - abort(make_response(jsonify({"message":f"Planet {id} invaid"}), 400)) + except KeyError as e: + abort(make_response("Invalid request. Missing required value: {e}"), 400) + +# def validate_model(cls, id): +# try: +# id = int(id) +# except: +# abort(make_response(jsonify({"message":f"{cls.__name__} {id} invaid"}), 400)) - planet = Planet.query.get(id) - if not planet: - abort(make_response(jsonify({"message":f"Planet {id} not found"}), 404)) - return planet +# model = cls.query.get(id) +# if not model: +# abort(make_response(jsonify({"message":f"{cls.__name__} {id} not found"}), 404)) +# return model +# def validate_model(id): +# try: +# id = int(id) +# except: +# abort(make_response(jsonify({"message":f"Planet {id} invaid"}), 400)) + +# planet = Planet.query.get(id) +# if not planet: +# abort(make_response(jsonify({"message":f"Planet {id} not found"}), 404)) +# return planet @planets_bp.route("/", methods=["GET"]) def read_one_planet(id): - planet = validate_planet(id) + planet = validate_model(Planet, id) planet = Planet.query.get(id) - return planet.planet_to_dict() + return jsonify(planet.planet_to_dict()), 200 @planets_bp.route("/", methods=["PUT"]) def update_planet(id): - planet = validate_planet(id) + planet = validate_model(Planet, id) planet.update(request.get_json()) @@ -62,13 +72,26 @@ def update_planet(id): @planets_bp.route("/", methods=["DELETE"]) def delete_planet(id): - planet= validate_planet(id) + planet= validate_model(Planet, id) db.session.delete(planet) db.session.commit() message = f"Planet #{id} successfully deleted" return make_response(jsonify(message), 200) +def validate_model(cls, id): + try: + id = int(id) + except: + message = f"{cls.__name__} {id} is invalid" + abort(make_response({"message": message}, 400)) + + model = cls.query.get(id) + + if not model: + message = f"{cls.__name__} {id} not found" + abort(make_response({"message": message}, 404)) + return model diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index 12bfb9fad..7fe694653 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -74,4 +74,11 @@ def test_delete_planet_successfully(client, two_saved_planets): assert response_body == f"Planet #{planet_id} successfully deleted" response = client.get(f"/planets/{planet_id}") - assert response.status_code == 404 \ No newline at end of file + assert response.status_code == 404 + +def test_deleting_non_existing_planet_returns_planet_not_found(client): + response = client.delete(f"planets/1") + response_body = response.get_json() + + assert response.status_code == 404 + assert response_body == {"message": "Planet 1 not found"} \ No newline at end of file From ed82db76e3cd387480fe0e109a227033b4aea345 Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Thu, 4 May 2023 15:22:55 -0400 Subject: [PATCH 17/20] final push --- app/models/planet.py | 11 +++++------ app/routes.py | 21 --------------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/app/models/planet.py b/app/models/planet.py index 146514fa9..8ab2d12f3 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -21,6 +21,10 @@ def planet_to_dict(self): "description": self.description, "color": self.color } + def __str__(self): + return f'An object of type {self.__class__.__name__} with id {self.id}.' + + @classmethod def create_new_planet(cls, request_data): if "name" not in request_data or "description" not in request_data: @@ -31,11 +35,6 @@ def create_new_planet(cls, request_data): color=request_data.get("color") ) - def update(id, planet_to_dict): - for key, value in planet_to_dict.items(): - setattr(id,key,value) - def to_string(self): - return f"{self.id}: {self.name} Description {self.description} " - + \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 42f06ad43..73587226f 100644 --- a/app/routes.py +++ b/app/routes.py @@ -33,27 +33,6 @@ def create_planets(): except KeyError as e: abort(make_response("Invalid request. Missing required value: {e}"), 400) -# def validate_model(cls, id): -# try: -# id = int(id) -# except: -# abort(make_response(jsonify({"message":f"{cls.__name__} {id} invaid"}), 400)) - -# model = cls.query.get(id) -# if not model: -# abort(make_response(jsonify({"message":f"{cls.__name__} {id} not found"}), 404)) -# return model -# def validate_model(id): -# try: -# id = int(id) -# except: -# abort(make_response(jsonify({"message":f"Planet {id} invaid"}), 400)) - -# planet = Planet.query.get(id) -# if not planet: -# abort(make_response(jsonify({"message":f"Planet {id} not found"}), 404)) -# return planet - @planets_bp.route("/", methods=["GET"]) def read_one_planet(id): planet = validate_model(Planet, id) From b80f5c5b5226e0672d2ace72ad3896dc21b71dff Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Tue, 9 May 2023 11:23:09 -0400 Subject: [PATCH 18/20] added routes folder and refactors --- app/__init__.py | 2 +- app/models/planet.py | 9 +++++---- app/routes/__init__.py | 0 app/{routes.py => routes/planet_routes.py} | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 app/routes/__init__.py rename app/{routes.py => routes/planet_routes.py} (96%) diff --git a/app/__init__.py b/app/__init__.py index d2b2f7631..640e0e9f0 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -27,7 +27,7 @@ def create_app(test_config=None): migrate.init_app(app, db) from app.models.planet import Planet - from .routes import planets_bp + from app.routes.planet_routes import planets_bp app.register_blueprint(planets_bp) # from app.models. import diff --git a/app/models/planet.py b/app/models/planet.py index 8ab2d12f3..851c24b7c 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -9,9 +9,11 @@ class Planet(db.Model): # __tablename__ = "planets" - def update(id, planet_to_dict): + def update(self, planet_to_dict): for key, value in planet_to_dict.items(): - setattr(id,key,value) + if key == "name": + value = value.title() + setattr(self,key,value) def planet_to_dict(self): @@ -23,12 +25,11 @@ def planet_to_dict(self): def __str__(self): return f'An object of type {self.__class__.__name__} with id {self.id}.' - @classmethod def create_new_planet(cls, request_data): if "name" not in request_data or "description" not in request_data: - return make_response("Invalid Requesrt", 400) + return make_response("Invalid Request. Missing required fields: name or description", 400) return cls( name=request_data["name"].title(), description=request_data["description"], diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/routes.py b/app/routes/planet_routes.py similarity index 96% rename from app/routes.py rename to app/routes/planet_routes.py index 73587226f..899edc802 100644 --- a/app/routes.py +++ b/app/routes/planet_routes.py @@ -31,7 +31,8 @@ def create_planets(): return make_response(jsonify(message), 201) except KeyError as e: - abort(make_response("Invalid request. Missing required value: {e}"), 400) + abort(make_response(f"Invalid request. Missing required value: {e}"), 400) + @planets_bp.route("/", methods=["GET"]) def read_one_planet(id): From 5534ed1aeaadf931e58cca4143ceb92162d4b5da Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Tue, 9 May 2023 13:08:22 -0400 Subject: [PATCH 19/20] fixes moon GET route --- app/__init__.py | 3 +++ app/models/moon.py | 29 +++++++++++++++++++++++++ app/models/planet.py | 8 +++++-- app/routes/moon_routes.py | 45 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 app/models/moon.py create mode 100644 app/routes/moon_routes.py diff --git a/app/__init__.py b/app/__init__.py index 640e0e9f0..106425a33 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -26,9 +26,12 @@ def create_app(test_config=None): db.init_app(app) migrate.init_app(app, db) from app.models.planet import Planet + from app.models.moon import Moon from app.routes.planet_routes import planets_bp + from app.routes.moon_routes import moons_bp app.register_blueprint(planets_bp) + app.register_blueprint(moons_bp) # from app.models. import diff --git a/app/models/moon.py b/app/models/moon.py new file mode 100644 index 000000000..3d2059dd5 --- /dev/null +++ b/app/models/moon.py @@ -0,0 +1,29 @@ +from app import db +from flask import make_response + +class Moon(db.Model): + moon_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + name = db.Column(db.String) + planets = db.relationship("Planet", back_populates="moon") + + def moon_to_dict(self): + return { + "moon_id": self.moon_id, + "name": self.name + } + def __str__(self): + return f'An object of type {self.__class__.__name__} with id {self.moon_id}.' + + @classmethod + def create_new_planet(cls, request_data): + if "name" not in request_data or request_data is None: + return make_response("Invalid Request. Missing required fields: name or request data", 400) + return cls( + name=request_data["name"].title() + ) + + def update(self, moon_to_dict): + for key, value in moon_to_dict.items(): + if key == "name": + value = value.title() + setattr(self,key,value) \ No newline at end of file diff --git a/app/models/planet.py b/app/models/planet.py index 851c24b7c..d61315d3c 100644 --- a/app/models/planet.py +++ b/app/models/planet.py @@ -6,6 +6,8 @@ class Planet(db.Model): name = db.Column(db.String) description = db.Column(db.String) color = db.Column(db.String) + moon_id = db.Column(db.Integer, db.ForeignKey('moon.moon_id')) + moon = db.relationship("Moon", back_populates="planets") # __tablename__ = "planets" @@ -21,7 +23,8 @@ def planet_to_dict(self): "id": self.id, "name": self.name, "description": self.description, - "color": self.color } + "color": self.color, + "moon_id": self.moon_id } def __str__(self): return f'An object of type {self.__class__.__name__} with id {self.id}.' @@ -33,7 +36,8 @@ def create_new_planet(cls, request_data): return cls( name=request_data["name"].title(), description=request_data["description"], - color=request_data.get("color") + color=request_data.get("color"), + moon_id=request_data.get("moon_id") ) diff --git a/app/routes/moon_routes.py b/app/routes/moon_routes.py new file mode 100644 index 000000000..8a342a898 --- /dev/null +++ b/app/routes/moon_routes.py @@ -0,0 +1,45 @@ +from app import db +from app.models.moon import Moon +from app.models.planet import Planet +from app.routes.planet_routes import validate_model +from flask import Blueprint, jsonify, abort, make_response, request + +moons_bp = Blueprint("moons", __name__, url_prefix="/moons") + + +@moons_bp.route("", methods = ["GET"]) +def read_all_moons(): + moons_response = [] + query_params = request.args.to_dict() + + if query_params: + query_params = {k.lower(): v.title() for k, v in query_params.items()} + moons = Moon.query.filter_by(**query_params).all() + else: + moons = Moon.query.all() + + moons_response = [moon.moon_to_dict() for moon in moons] + return jsonify(moons_response) + +@moons_bp.route("", methods=["POST"]) +def create_moon(): + # Retrieve the request body + request_body = request.get_json() + + # Validate the request body + if not request_body: + return make_response(jsonify({"error": "Request body must be provided"}), 400) + if "name" not in request_body: + return make_response(jsonify({"error": "Name field is required"}), 400) + + # Create a new Moon instance + new_moon = Moon( + name=request_body["name"] + ) + + # Add the new Moon instance to the database session + db.session.add(new_moon) + db.session.commit() + + # Return a JSON response indicating success + return make_response(jsonify({"message": f"New moon '{new_moon.name}' created"}), 201) \ No newline at end of file From 2134c2b60c77377fd5d6ab0adfdd0ea7003e5d5a Mon Sep 17 00:00:00 2001 From: Say Ryder Date: Tue, 9 May 2023 17:18:59 -0400 Subject: [PATCH 20/20] implements add_moon_to_planet function --- app/routes/planet_routes.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/app/routes/planet_routes.py b/app/routes/planet_routes.py index 899edc802..843966337 100644 --- a/app/routes/planet_routes.py +++ b/app/routes/planet_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 planets_bp = Blueprint("planets", __name__, url_prefix="/planets") @@ -74,4 +75,37 @@ def validate_model(cls, id): return model +@planets_bp.route("//moons", methods=["POST"]) +def create_moon(id): + planet = validate_model(Planet, id) + request_body = request.get_json() + new_moon = Moon( + name=request_body["name"] + ) + + db.session.add(new_moon) + db.session.commit() + add_moon_to_planet(new_moon.moon_id, planet) + + return make_response(jsonify(f"Moon {new_moon.name} successfully created"), 201) + +def add_moon_to_planet(moon_id, planet): + planet.moon_id = moon_id + db.session.commit() +@planets_bp.route("//moons", methods=["GET"]) +def read_moons(id): + planet = validate_model(Planet, id) + moons_response = [] + for id in str(planet.id): + moons_response.append( + { + "moon_id": int(id), + } + ) + return jsonify(moons_response) +# def handle_moon_from_planet(): + + + +