Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b1c8a92
created structure new files models/plant and routes/routes and class…
vladarap88 Oct 18, 2024
1f4faf4
changed dditional attribute to distance from sun and created a plane…
vladarap88 Oct 19, 2024
2a30a29
implemented endpoint to get planet_lists; refactored file names due t…
aleidavi Oct 20, 2024
c219d7f
worked together with live share to create endpoints of one planet and…
vladarap88 Oct 21, 2024
8184fbb
completed wave 3 of slar-system-api, will implement migration with pa…
aleidavi Oct 28, 2024
62f63a3
added the configurations for create_app() inside __init__.py file
aleidavi Oct 28, 2024
a26e770
started migration to create mogration files with Alembic
aleidavi Oct 28, 2024
984e0dd
completed migration of model for Planet
aleidavi Oct 28, 2024
7ab88f0
added two endpoints to planet_routes.py
aleidavi Oct 29, 2024
83d91c1
debugged create_planet() endpoint
aleidavi Oct 29, 2024
11d0ec9
added read/get one planet
vladarap88 Oct 29, 2024
4acea17
added update one planet
vladarap88 Oct 29, 2024
0b1eff0
added delete one planet
vladarap88 Oct 29, 2024
48ae495
changde to constant attribute distance
vladarap88 Oct 29, 2024
87195b4
fixed validate_planet()
vladarap88 Oct 29, 2024
79a070e
adding my local changes in order to pull afterwards
aleidavi Oct 30, 2024
a86a6d0
Merge branch 'main' of https://github.com/vladarap88/solar-system-api
aleidavi Oct 30, 2024
5fe3dea
resolved merge conflicts - adding updated planet_routes.py file
aleidavi Oct 30, 2024
fab9221
added a seed.py file to assist in creating the api database data for …
aleidavi Oct 30, 2024
4c6c569
Added to_dict() method under the planet.py file for Planet class and …
aleidavi Oct 30, 2024
d0832a2
changed seed.py and deleted migrations file and import decimal into d…
vladarap88 Oct 31, 2024
696dd56
added migrate and changed seed.py the distance from sun
vladarap88 Oct 31, 2024
8d377e0
upgrade migrations file
vladarap88 Oct 31, 2024
1d04545
fixed query param to discription & distance
vladarap88 Oct 31, 2024
6668986
updated distance from sun in seed.py
vladarap88 Oct 31, 2024
0c2712d
implementing large integer in seed.py but didn't work
vladarap88 Oct 31, 2024
23335e3
completing part of wave 6 requirements: added tests directory, create…
aleidavi Nov 1, 2024
c13d35f
added test
vladarap88 Nov 1, 2024
9b1a3f3
Added tests
vladarap88 Nov 1, 2024
7d245f4
fix formatting
vladarap88 Nov 1, 2024
712a776
removed large blocks of commented code
aleidavi Nov 1, 2024
f62681b
added a response body assert statement to test_planet_routes.py
aleidavi Nov 1, 2024
b1fcd3e
changed response patterns to utilize the to_dict() method
aleidavi Nov 3, 2024
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
19 changes: 17 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
from flask import Flask
from .routes.db import db, migrate
from .models import planet
from .routes.planet_routes import planets_bp
import os


def create_app(test_config=None):
def create_app(config=None):
app = Flask(__name__)

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_DATABASE_URI')

if config:
app.config.update(config)

db.init_app(app)
migrate.init_app(app, db)

# Register Blueprints here
app.register_blueprint(planets_bp)

return app
Empty file added app/models/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions app/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
pass
16 changes: 16 additions & 0 deletions app/models/planet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from sqlalchemy.orm import Mapped, mapped_column
from app.routes.db import db

class Planet(db.Model):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str]
description: Mapped[str]
distance_from_sun: Mapped[int]

def to_dict(self):
return dict(
id=self.id,
name=self.name,
description=self.description,
distance_from_sun=self.distance_from_sun,
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also have a from_dict class method that we could use to construct our plant instances.

2 changes: 0 additions & 2 deletions app/routes.py

This file was deleted.

Empty file added app/routes/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions app/routes/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from ..models.base import Base

db = SQLAlchemy(model_class=Base)
migrate = Migrate()

94 changes: 94 additions & 0 deletions app/routes/planet_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from flask import Blueprint, abort, make_response, request, Response
from app.routes.db import db
from app.models.planet import Planet


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention is to name this bp, inside of app/__init__.py we could then import this under an alias to prevent name conflicts like so:

from app.routes.planet_routes.py import bp as planets_bp



@planets_bp.post("")
def create_planet():

request_body = request.get_json()
name = request_body["name"]
description = request_body["description"]
distance_from_sun = request_body["distance_from_sun"]

new_planet = Planet(
name=name, description=description, distance_from_sun=distance_from_sun
)
db.session.add(new_planet)
db.session.commit()

response = new_planet.to_dict()

return response, 201
Comment on lines +12 to +25

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move all of this logic into a function like create_model that we created in class.


@planets_bp.get("")
def get_all_planets():
query = db.select(Planet)
name_param = request.args.get("name")

if name_param:
query = query.where(Planet.name == name_param)

description_param = request.args.get("description")
if description_param:
query = query.where(Planet.description.ilike(f"%{description_param}%"))

distance_from_sun_param = request.args.get("distance_from_sun")
if distance_from_sun_param:
query = query.where(Planet.distance_from_sun.ilike(f"%{distance_from_sun_param}%"))

query = query.order_by(Planet.id)

planets = db.session.scalars(query)

planets_response = [planet.to_dict() for planet in planets]
return planets_response
Comment on lines +32 to +48

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like create_model we could also move this logic into a helper function like get_models_with_filters. How could the helper function look different to serve your needs here?


@planets_bp.get("/<planet_id>")
def get_one_planet(planet_id):
planet = validate_planet(planet_id)

response = planet.to_dict()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would be okay with returning this on a single line since we aren't doing anything extra here.


return response


@planets_bp.put("/<planet_id>")
def update_planet(planet_id):
planet = validate_planet(planet_id)
request_body = request.get_json()

planet.name = request_body["name"]
planet.description = request_body["description"]
planet.distance_from_sun = request_body["distance_from_sun"]
db.session.commit()
Comment on lines +62 to +67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this into a helper function? How would it look based off what we learned during refactoring?


return Response(status=204, mimetype="application/json")


@planets_bp.delete("/<planet_id>")
def delete_planet(planet_id):
planet = validate_planet(planet_id)
db.session.delete(planet)
db.session.commit()
return Response(status=204, mimetype="application/json")


def validate_planet(planet_id):
try:
planet_id = int(planet_id)
except:
response = {"message": f"planet {planet_id} invalid"}
abort(make_response(response, 400))

query = db.select(Planet).where(Planet.id == planet_id)
planet = db.session.scalar(query)

if not planet:
response = {"message": f"planet {planet_id} not found"}
abort(make_response(response, 404))

return planet
Comment on lines +80 to +94

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where could this function live to better organize our code?

1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Single-database configuration for Flask.
50 changes: 50 additions & 0 deletions migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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,flask_migrate

[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

[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate

[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
113 changes: 113 additions & 0 deletions migrations/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import logging
from logging.config import fileConfig

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')


def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions['migrate'].db.get_engine()
except (TypeError, AttributeError):
# this works with Flask-SQLAlchemy>=3
return current_app.extensions['migrate'].db.engine


def get_engine_url():
try:
return get_engine().url.render_as_string(hide_password=False).replace(
'%', '%%')
except AttributeError:
return str(get_engine().url).replace('%', '%%')


# 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', get_engine_url())
target_db = current_app.extensions['migrate'].db

# 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 get_metadata():
if hasattr(target_db, 'metadatas'):
return target_db.metadatas[None]
return target_db.metadata


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=get_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.')

conf_args = current_app.extensions['migrate'].configure_args
if conf_args.get("process_revision_directives") is None:
conf_args["process_revision_directives"] = process_revision_directives

connectable = get_engine()

with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=get_metadata(),
**conf_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"}
34 changes: 34 additions & 0 deletions migrations/versions/578fcb6c6536_adds_planet_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""adds Planet model

Revision ID: 578fcb6c6536
Revises:
Create Date: 2024-10-28 16:24:28.316505

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '578fcb6c6536'
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=False),
sa.Column('description', sa.String(), nullable=False),
sa.Column('distance_from_sun', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###


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