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

Refactor: models #148

Merged
merged 10 commits into from
Sep 15, 2022
13 changes: 7 additions & 6 deletions data/server.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
A1234567;Garcia;['type1']
B2345678;Hernandez;['type2']
C3456789;Smith;[]
D4567890;Jones;['type1', 'type2']
a93ef111829829ea5038398ba6583dc2e1a2f2c309e24aee535f725dbe1f1250;2c3aaefea8267c66822f6edc0e42d9b7384695f9c0407eabda141770aab8901e;['type1']
095feaba889222ee1bc97aaf3bb89c90c21238fb556fb48b5cc54760461ea18ce814d016e70b1929d05190edbec1032e07921228fcb61ad3417aaff0abd9d082;123c86e1f2ac255ba31f1ad742defe23d194269669d2aac0d2572e20e9378e395976f84db305caeba1f91e7996463031d4c49365a7a9f4c7dc404873ad330974;['type1']
A1234567;Garcia;type1
B2345678;Hernandez;type2
C3456789;Smith;
D4567890;Jones;type1
D4567890;Jones;type2
a93ef111829829ea5038398ba6583dc2e1a2f2c309e24aee535f725dbe1f1250;2c3aaefea8267c66822f6edc0e42d9b7384695f9c0407eabda141770aab8901e;type1
095feaba889222ee1bc97aaf3bb89c90c21238fb556fb48b5cc54760461ea18ce814d016e70b1929d05190edbec1032e07921228fcb61ad3417aaff0abd9d082;123c86e1f2ac255ba31f1ad742defe23d194269669d2aac0d2572e20e9378e395976f84db305caeba1f91e7996463031d4c49365a7a9f4c7dc404873ad330974;type1
19 changes: 17 additions & 2 deletions eligibility_server/db/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
from eligibility_server.db import db


user_eligibility = db.Table(
"user_eligibility",
db.Column("user_id", db.Integer, db.ForeignKey("user.id"), primary_key=True),
db.Column("eligibility_id", db.Integer, db.ForeignKey("eligibility.id"), primary_key=True),
)


class Eligibility(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)

def __repr__(self):
return f"<Eligibility {self.name}>"


class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
sub = db.Column(db.String, unique=True, nullable=False)
name = db.Column(db.String, unique=True, nullable=False)
types = db.Column(db.String, unique=False, nullable=False)
types = db.relationship("Eligibility", secondary=user_eligibility, lazy="subquery", backref=db.backref("users", lazy=True))

def __repr__(self):
return "<User %r>" % self.sub
return f"<User {self.sub}>"
31 changes: 22 additions & 9 deletions eligibility_server/db/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from flask_sqlalchemy import inspect

from eligibility_server.db import db
from eligibility_server.db.models import User
from eligibility_server.db.models import User, Eligibility
from eligibility_server.settings import Configuration


Expand Down Expand Up @@ -50,7 +50,7 @@ def import_users():
with open(file_path) as file:
data = json.load(file)["users"]
for user in data:
save_users(user, data[user][0], str(data[user][1]))
save_users(user, data[user][0], data[user][1])
Copy link
Member

Choose a reason for hiding this comment

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

I like how this is cleaner than the previous str() version.

Copy link
Member

@thekaveman thekaveman Sep 15, 2022

Choose a reason for hiding this comment

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

I do think it's a little funky to pass user and then use user as a key for data - not super obvious what is going on here? data is a dict, might be clearer to iterate over data.items() to deal with the key/value tuple.

Also this JSON file is... the oldest remnant of the old school approach from our toy. Not that important to maintain / make clearer right now, probably just drop it entirely in the future?

ALSO I've updated this section for the remote data anyway! So no worries.

elif file_format == "csv":
with open(file_path, newline=config.csv_newline, encoding="utf-8") as file:
data = csv.reader(
Expand All @@ -60,24 +60,32 @@ def import_users():
quotechar=config.csv_quotechar,
)
for user in data:
save_users(user[0], user[1], user[2])
# lists are expected to be a comma-separated value and quoted if the CSV delimiter is a comma
types = [type.replace(config.csv_quotechar, "") for type in user[2].split(",") if type]
save_users(user[0], user[1], types)
else:
click.echo(f"Warning: File format is not supported: {file_format}")

click.echo(f"Users added: {User.query.count()}")
click.echo(f"Eligibility types added: {Eligibility.query.count()}")


def save_users(sub: str, name: str, types: str):
def save_users(sub: str, name: str, types):
"""
Add users to the database User table

@param sub - User's sub
@param name - User's name
@param types - Types of eligibilities, in a stringified list
@param types - Types of eligibilities, in the form of a list of strings
"""

item = User(sub=sub, name=name, types=types)
db.session.add(item)
user = User.query.filter_by(sub=sub, name=name).first() or User(sub=sub, name=name)
eligibility_types = [Eligibility.query.filter_by(name=type).first() or Eligibility(name=type) for type in types]
user.types.extend(eligibility_types)

db.session.add(user)
db.session.add_all(eligibility_types)

db.session.commit()


Expand All @@ -90,9 +98,14 @@ def drop_db_command():
try:
click.echo(f"Users to be deleted: {User.query.count()}")
User.query.delete()
db.session.commit()

click.echo(f"Eligibility types to be deleted: {Eligibility.query.count()}")
Eligibility.query.delete()

except Exception as e:
click.echo("Failed to query for Users", e)
click.echo(f"Failed to query models. Exception: {e}", err=True)

db.session.commit()

db.drop_all()
click.echo("Database dropped.")
Expand Down
2 changes: 1 addition & 1 deletion eligibility_server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
CSV_DELIMITER = ";"
CSV_NEWLINE = ""
CSV_QUOTING = 3
CSV_QUOTECHAR = ""
CSV_QUOTECHAR = '"'
thekaveman marked this conversation as resolved.
Show resolved Hide resolved


class Configuration:
Expand Down
3 changes: 1 addition & 2 deletions eligibility_server/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
Eligibility Verification route
"""

import ast
import datetime
import json
import logging
Expand Down Expand Up @@ -140,7 +139,7 @@ def _check_user(self, sub, name, types):

existing_user = User.query.filter_by(sub=sub, name=name).first()
if existing_user:
existing_user_types = ast.literal_eval(existing_user.types)
existing_user_types = [type.name for type in existing_user.types]
else:
existing_user_types = []

Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ def flask():
@pytest.fixture
def client():
return app.test_client()


@pytest.fixture()
def runner():
return app.test_cli_runner()
Empty file added tests/db/__init__.py
Empty file.
41 changes: 41 additions & 0 deletions tests/db/test_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest

from flask_sqlalchemy import inspect
from eligibility_server.db import db
from eligibility_server.db.models import Eligibility, User


@pytest.mark.usefixtures("flask")
def test_init_db_command(runner):
"""Assumes that IMPORT_FILE_PATH is data/server.csv."""
runner.invoke(args="drop-db")

result = runner.invoke(args="init-db")
thekaveman marked this conversation as resolved.
Show resolved Hide resolved

assert result.exit_code == 0

assert User.query.count() == 6
assert Eligibility.query.count() == 2

user_with_one_eligibility = User.query.filter_by(sub="A1234567", name="Garcia").first()
type1_eligibility = Eligibility.query.filter_by(name="type1").first()
assert user_with_one_eligibility.types == [type1_eligibility]

user_with_no_eligibility = User.query.filter_by(sub="C3456789", name="Smith").first()
assert user_with_no_eligibility.types == []

user_with_multiple_eligibilities = User.query.filter_by(sub="D4567890", name="Jones").first()
type2_eligibility = Eligibility.query.filter_by(name="type2").first()
assert user_with_multiple_eligibilities.types == [type1_eligibility, type2_eligibility]


@pytest.mark.usefixtures("flask")
def test_drop_db_command(runner):
result = runner.invoke(args="drop-db")

assert result.exit_code == 0

inspector = inspect(db.engine)
assert inspector.get_table_names() == []

runner.invoke(args="init-db")
angela-tran marked this conversation as resolved.
Show resolved Hide resolved