Skip to content
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

Sapphire - Ericka M. #34

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8fbd584
implemented init and routes
erickalucia Apr 20, 2023
3f04f96
implemented helper function validate planet and created new route cal…
erickalucia Apr 21, 2023
870045b
configured sqlalchemy and migrate
sophiat8832 Apr 27, 2023
1c4680d
created Planet model, added new migration
sophiat8832 Apr 27, 2023
5ff2070
implemented create_planet for POST method
sophiat8832 Apr 27, 2023
4e7e714
implemented get_planets with GET method for /planets route
sophiat8832 Apr 27, 2023
ac2e36e
implemented GET route for one planet
erickalucia May 1, 2023
8d21b30
implemented PUT method to update one planet
erickalucia May 1, 2023
b177034
implemented DELETE route for single planet
erickalucia May 1, 2023
940022a
Added query param name for GET routes /planets
sophiat8832 May 2, 2023
a9e5015
refactored create app for test database
erickalucia May 3, 2023
32e704e
fixed typo in create app
erickalucia May 3, 2023
6c9eff8
created tests folder with _init__.py conftest.py and test_routes.py
erickalucia May 3, 2023
c53075c
created test for empty database returns 200
erickalucia May 3, 2023
b671ac7
created test for GET one planet
erickalucia May 3, 2023
160d25a
added test for empty db returns 404
erickalucia May 3, 2023
255c902
created test for GET/planets returns 200
erickalucia May 3, 2023
41dab81
completed test for POST new planet
erickalucia May 3, 2023
a7e7ffc
Refactor: added to_dict function
erickalucia May 5, 2023
3284626
Refactor: implemented from_dict class method
erickalucia May 5, 2023
19a0a6c
Refactor: added get_valid_item_by_id function
erickalucia May 5, 2023
d06b5f4
added gunicorn
erickalucia May 9, 2023
ea6a797
added render database uri
erickalucia May 9, 2023
ae52daf
edited config SQLALchemy uri to render
erickalucia May 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from dotenv import load_dotenv
import os


def create_app(test_config=None):
db = SQLAlchemy()
migrate = Migrate()
load_dotenv()

def create_app(testing=None):
app = Flask(__name__)

if testing is None:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("RENDER_DATABASE_URI")
else:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get('SQLALCHEMY_TEST_DATABASE_URI')

app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
migrate.init_app(app, db)


from app.models.planet import Planet
from flask import Blueprint
from .routes import planets_bp


app.register_blueprint(planets_bp)

return app



26 changes: 26 additions & 0 deletions app/models/planet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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)
distance_from_sun = db.Column(db.Integer)


def to_dict(self):
return {
"id": self.id,
"name": self.name,
"description": self.description,
"distance from sun": self.distance_from_sun
}

@classmethod
def from_dict(cls, planet_details):
new_planet = cls(
name=planet_details["name"],
description=planet_details["description"],
distance_from_sun=planet_details["distance from sun"]
)
return new_planet

113 changes: 112 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,113 @@
from flask import Blueprint
from flask import Blueprint, jsonify, abort, make_response, request
from app import db
from app.models.planet import Planet

# class Planet():
# def __init__(self, id, name, description, distance_from_sun):
# self.id = id
# self.name = name
# self.description = description
# self.distance_from_sun = distance_from_sun

# planet_list = [
# Planet(1, "Mercury", "blue", 0.39),
# Planet(2, "Venus", "yellow", 0.72),
# Planet(3, "Earth", "blue", 1),
# Planet(4, "Mars", "orange", 1.52),
# Planet(5, "Jupiter", "orange", 5.2),
# Planet(6, "Saturn", "yellow", 9.54),
# Planet(7, "Uranus", "blue", 19.2),
# Planet(8, "Neptune", "blue", 30.06)
# ]

def get_valid_item_by_id(model, id):
try:
id = int(id)
except:
abort(make_response({'msg': f"Invalid id '{id}'"}, 400))

item = model.query.get(id)

return item if item else abort(make_response({'msg': f"No {model.__name__} with id {id}"}, 404))



planets_bp = Blueprint("planets", __name__, url_prefix="/planets")

@planets_bp.route("", methods=['POST'])
def create_planet():
request_body = request.get_json()
new_planet = Planet.from_dict(request_body)

db.session.add(new_planet)
db.session.commit()

return ({
"id": new_planet.id,
"name": new_planet.name,
"description": new_planet.description,
"distance from sun": new_planet.distance_from_sun,
"msg": "Successfully created"
}, 201)


@planets_bp.route("", methods=['GET'])
def get_planets():
name_query = request.args.get("name")
if name_query:
planets = Planet.query.filter_by(name=name_query)
else:
planets = Planet.query.all()

planets_response = []
for planet in planets:
planets_response.append(planet.to_dict())

return jsonify(planets_response), 200


@planets_bp.route("/<planet_id>", methods=["GET"])
def get_one_planet(planet_id):

planet = get_valid_item_by_id(Planet, planet_id)

return ({
"id": planet.id,
"name": planet.name,
"description": planet.description,
"distance from sun": planet.distance_from_sun
}), 200


@planets_bp.route("/<planet_id>", methods=["PUT"])
def update_one_planet(planet_id):
# Get the data from the request body
request_body = request.get_json()

planet_to_update = get_valid_item_by_id(Planet, planet_id)

planet_to_update.name = request_body["name"]
planet_to_update.description = request_body["description"]
planet_to_update.distance_from_sun = request_body["distance from sun"]

db.session.commit()

return ({
"id": planet_to_update.id,
"name": planet_to_update.name,
"description": planet_to_update.description,
"distance from sun": planet_to_update.distance_from_sun
}), 200



@planets_bp.route("/<planet_id>", methods=["DELETE"])
def delete_one_planet(planet_id):
planet_to_delete = get_valid_item_by_id(Planet, planet_id)

db.session.delete(planet_to_delete)
db.session.commit()

return f"Planet {planet_to_delete.name} is deleted!", 200


1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
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
96 changes: 96 additions & 0 deletions migrations/env.py
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()
24 changes: 24 additions & 0 deletions migrations/script.py.mako
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"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""created models folder and added Planet model in planet.py

Revision ID: beefc1e65a76
Revises:
Create Date: 2023-04-27 14:14:25.328130

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'beefc1e65a76'
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('distance_from_sun', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('planet')
# ### end Alembic commands ###
Loading