-
Notifications
You must be signed in to change notification settings - Fork 79
Tiger - Tanil&Stacy #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8be97cf
d735bbf
23c530a
09f95ed
29d074f
fe94c5a
938efbd
78007c1
9b534cd
18736af
75dc241
7c3ab34
f91c8fb
00ccb0d
b2af6dd
5560f30
b424e88
c3197ab
dab2c1c
51a0e9e
be96ae4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,30 @@ | ||
| 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 | ||
|
|
||
| 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) | ||
|
|
||
| return app |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| from app import db | ||
| from flask import make_response, abort | ||
|
|
||
|
|
||
| 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) | ||
|
|
||
| def to_dict(self): | ||
| return({ | ||
| "id": self.id, | ||
| "name": self.name, | ||
| "color": self.color, | ||
| "description": self.description | ||
| }) | ||
| @classmethod | ||
| def from_json(cls, req_body): | ||
| return cls( | ||
| 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: | ||
| raise error | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,151 @@ | ||
| from flask import Blueprint | ||
| 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") | ||
|
|
||
| # HELPER FUNCTION | ||
| def validate_id(class_obj, id): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice helper method |
||
| try: | ||
| object_id = int(id) | ||
| except: | ||
| abort(make_response({"message":f"{class_obj} {id} is invalid"}, 400)) | ||
|
|
||
| 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 query_result | ||
|
|
||
| # CREATE RESOURCE | ||
| @planets_bp.route("", methods = ["POST"]) | ||
| def create_planet(): | ||
| request_body = request.get_json() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing some validation on the request body here would be nice, just to check if it has the required fields. |
||
|
|
||
| new_planet = Planet.from_json(request_body) | ||
|
|
||
| db.session.add(new_planet) | ||
| db.session.commit() | ||
|
|
||
| return make_response(f"Planet {new_planet.name} has been created", 201) | ||
|
|
||
|
|
||
| # GET ALL RESOURCES | ||
| @planets_bp.route("", methods = ["GET"]) | ||
| def get_all_planets(): | ||
| results_list = [] | ||
|
|
||
| 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 | ||
|
|
||
|
|
||
| # GET ALL RESOURCES | ||
| @planets_bp.route("/<planet_id>", methods = ["GET"]) | ||
| def get_one_planet(planet_id): | ||
| planet = validate_id(Planet, planet_id) | ||
|
|
||
| return jsonify(planet.to_dict()) | ||
|
|
||
|
|
||
| # UPDATE RESOURCE | ||
| @planets_bp.route("/<planet_id>", methods = ["PUT"]) | ||
| def update_planet(planet_id): | ||
| planet = validate_id(Planet, planet_id) | ||
|
|
||
| request_body = request.get_json() | ||
|
|
||
| try: | ||
| planet.update(request_body) | ||
| except KeyError as error: | ||
| return make_response({'message': f"Missing attribute: {error}"}, 400) | ||
|
Comment on lines
+69
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is good validation, something similar could be done in the post request. It should also be tested |
||
|
|
||
| db.session.commit() | ||
|
|
||
| return make_response(f"planet {planet_id} successfully updated") | ||
|
|
||
|
|
||
| # DELETE RESOURCE | ||
| @planets_bp.route("/<planet_id>", methods = ["DELETE"]) | ||
| def delete_planet(planet_id): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note that this is untested. |
||
| planet = validate_id(Planet, 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): | ||
| # # 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.") | ||
|
|
||
|
|
||
| # # ] | ||
|
|
||
| # # 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) | ||
|
|
||
|
|
||
|
|
||
|
|
||
| # @planets_bp.route("/<id>", 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 | ||
| # } | ||
| # ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Generic single-database configuration. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great helper methods