Skip to content

Commit

Permalink
Implement SQLAlchemy (closes #23)
Browse files Browse the repository at this point in the history
- Major database redesign
  • Loading branch information
bbugyi200 committed Feb 23, 2018
1 parent b56784b commit 1449a60
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 188 deletions.
27 changes: 21 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
apipkg==1.4
attrs==17.4.0
awsebcli==3.12.3
blessed==1.14.2
botocore==1.8.49
cement==2.8.2
certifi==2017.11.5
chardet==3.0.4
click==6.7
colorama==0.3.9
colorama==0.3.7
coverage==4.4.2
dockerpty==0.4.1
docopt==0.6.2
docutils==0.14
execnet==1.5.0
Flask==0.12.2
Flask-SQLAlchemy==2.3.2
Flask-Testing==0.7.1
green==2.12.0
gprof2dot==2017.9.19
idna==2.6
itsdangerous==0.24
Jinja2==2.10
jmespath==0.9.3
MarkupSafe==1.0
pathspec==0.5.5
pluggy==0.6.0
py==1.5.2
python-termstyle==0.1.10
requests==2.18.4
python-dateutil==2.6.1
PyYAML==3.12
requests==2.9.1
semantic-version==2.5.0
six==1.11.0
slackclient==1.1.0
Unidecode==1.0.22
SQLAlchemy==1.2.4
tabulate==0.7.5
termcolor==1.1.0
urllib3==1.22
websocket-client==0.46.0
wcwidth==0.1.7
websocket-client==0.47.0
Werkzeug==0.13
198 changes: 73 additions & 125 deletions slackru/DB.py
Original file line number Diff line number Diff line change
@@ -1,146 +1,94 @@
""" All SQLite database interactions should route through a DB instance """

import sqlite3
from datetime import datetime

from slackru import sqlalch_db as db


#####################
# Database Models #
#####################
class Mentor(db.Model):
__tablename__ = 'mentors'
userid = db.Column(db.String(64), primary_key=True)
fullname = db.Column(db.String(64))
username = db.Column(db.String(64))
phone_number = db.Column(db.String(12), unique=True)
keywords = db.Column(db.String(1000))
questions = db.relationship('Question', backref='mentor', lazy=True)
posts = db.relationship('Post', backref='mentor', lazy=True)


class Question(db.Model):
__tablename__ = "questions"
id = db.Column(db.Integer, primary_key=True)
question = db.Column(db.String(64))
answered = db.Column(db.Boolean)
username = db.Column(db.String(64))
userid = db.Column(db.String(64))
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
matchedMentors = db.Column(db.String(128))
assignedMentor = db.Column(db.String(64), db.ForeignKey('mentors.userid'), nullable=False)
posts = db.relationship('Post', backref='question', lazy=True)


class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
questionId = db.Column(db.Integer, db.ForeignKey('questions.id'), nullable=False)
userid = db.Column(db.String(64), db.ForeignKey('mentors.userid'), nullable=False)
channel = db.Column(db.String(64))
timestamp = db.Column(db.Integer)


db.create_all()


#######################
# Database Wrappers #
#######################
class BaseDB:
""" Base Database Class """
def __init__(self, dbpath: str):
self.dbpath = dbpath
self.conn = None
self.c = None

def open(self):
self.conn = sqlite3.connect(self.dbpath)

def make_dicts(cursor, row):
return dict((cursor.description[idx][0], value)
for idx, value in enumerate(row))

self.conn.row_factory = make_dicts
self.c = self.conn.cursor()
return self

def close(self):
self.conn.close()

def execAndCommit(self, *args):
self.c.execute(*args)
self.conn.commit()


class CreateDB(BaseDB):
""" Create and Drop Operations
This class also initializes the database.
"""
def __init__(self, dbpath: str):
super().__init__(dbpath)

self.open()

if self._isEmpty():
self.create_all()

def _isEmpty(self):
self.c.execute('SELECT name FROM sqlite_master WHERE type="table";')
return self.c.fetchall() == []

def create_all(self):
self.create_mentors()
self.create_shifts()
self.create_questions()
self.create_posts()

def drop_table(self, table_name):
self.c.execute("DROP TABLE IF EXISTS " + table_name)
self.conn.commit()

def drop_all(self):
self.drop_table('mentors')
self.drop_table('shifts')
self.drop_table('questions')
self.drop_table('posts')

def create_mentors(self):
self.execAndCommit("CREATE TABLE mentors "
"(userid TEXT PRIMARY KEY, "
"fullname TEXT, "
"username TEXT, "
"phone_number TEXT, "
"keywords VARCHAR(1000))")

def create_shifts(self):
self.execAndCommit("CREATE TABLE shifts "
"(userid TEXT, "
"start TEXT, "
"end TEXT, "
"FOREIGN KEY(userid) REFERENCES mentors(userid))")

def create_questions(self):
self.execAndCommit("CREATE TABLE questions "
"(id INTEGER PRIMARY KEY, "
"question TEXT, "
"answered INTEGER, "
"username TEXT, "
"userid TEXT, "
"timestamp INTEGER, "
"matchedMentors BLOB, "
"assignedMentor TEXT, "
"FOREIGN KEY(assignedMentor) REFERENCES mentors(userid))")

def create_posts(self):
self.execAndCommit("CREATE TABLE posts "
"(questionId INTEGER, "
"userid TEXT, "
"channel TEXT, "
"timestamp TEXT, "
"FOREIGN KEY(questionId) REFERENCES questions(id), "
"FOREIGN KEY(userid) REFERENCES mentors(userid))")
def __init__(self):
self.Mentor = Mentor
self.Post = Post
self.Question = Question

def execAndCommit(self, *objs):
for obj in objs:
db.session.add(obj)
db.session.commit()

def __getattr__(self, name):
method = getattr(db, name)
return method


class InsertDB(BaseDB):
""" Insert Operations """
def insertMentor(self, fullname: str, username: str, userid: str,
phone_number: str, keywords: str):
CMD = "INSERT OR REPLACE INTO mentors " \
"(fullname, username, userid, phone_number, keywords) " \
"VALUES (?, ?, ?, ?, ?)"
self.execAndCommit(CMD, [fullname, username, userid, phone_number, keywords])

def insertShift(self, userid: str, start: str, end: str):
CMD = "INSERT INTO shifts " \
"(userid, start, end) " \
"VALUES (?, ?, ?)"
self.execAndCommit(CMD, [userid, start, end])
mentor = Mentor(fullname=fullname, username=username, userid=userid,
phone_number=phone_number, keywords=keywords)
self.execAndCommit(mentor)

def insertQuestion(self, question: str, userid: str, matchedMentors: '[str]'):
CMD = "INSERT INTO questions " \
"(question, answered, userid, timestamp, matchedMentors, assignedmentor) " \
"VALUES (?, 0, ?, datetime('now', 'localtime'), ?, NULL)"
self.execAndCommit(CMD, [question, userid, matchedMentors])
return self.c.lastrowid
Q = Question(question=question, userid=userid, matchedMentors=matchedMentors,
assignedMentor='')
self.execAndCommit(Q)
return Question.query.order_by(Question.id).all()[-1].id

def insertPost(self, questionId: int, userid: str, channel: str, timestamp: str):
CMD = "INSERT INTO posts " \
"(questionId, userid, channel, timestamp) " \
"VALUES (?, ?, ?, ?)"
self.execAndCommit(CMD, [questionId, userid, channel, timestamp])
post = Post(questionId=questionId, userid=userid, channel=channel,
timestamp=timestamp)
self.execAndCommit(post)


class DB(CreateDB, InsertDB):
class DB(InsertDB):
""" DB Interface Class """
def runQuery(self, query: str, args=(), one=False):
cur = self.conn.execute(query, args)
rv = cur.fetchall()
cur.close()

return (rv[0] if rv else None) if one else rv

def markAnswered(self, userid: str, questionId: int):
CMD = "UPDATE questions " \
"SET answered=1, " \
"assignedMentor=? " \
"WHERE id=?"
Q = Question.query.get(questionId)
Q.answered = True
Q.assignedMentor = userid

self.execAndCommit(CMD, [userid, questionId])
self.execAndCommit(Q)
24 changes: 12 additions & 12 deletions slackru/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from flask import Flask, Blueprint

from slackru.DB import DB
from flask_sqlalchemy import SQLAlchemy

main = Blueprint('main', __name__)

sqlalch_db = None
db = None


def create_app() -> 'Flask(...)':
""" Initialize Flask App """
Expand All @@ -13,19 +15,17 @@ def create_app() -> 'Flask(...)':
app.config.from_object(config)
app.register_blueprint(main)

return app
global sqlalch_db, db
sqlalch_db = SQLAlchemy(app)

from slackru.DB import DB
db = DB()

return app

def get_db() -> DB:
""" Return a new database connection if one does not already exist.
Otherwise return the already opened database.
"""
from slackru.config import config
if not get_db.database:
get_db.database = DB(config.dbpath)
return get_db.database

def get_db() -> 'DB(...)':
return db

get_db.database = None

import slackru.views
12 changes: 5 additions & 7 deletions slackru/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
import os
import logging

basedir = os.path.abspath(os.path.dirname(__file__))


class Config:
""" Base Configuration Class """
botID = "U86U670N8"
TESTING = False
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'slackru.db')
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
SQLALCHEMY_TRACK_MODIFICATIONS = False

@classmethod
def setup(cls):
db_dir = os.path.dirname(cls.dbpath)
if not os.path.exists(db_dir):
os.makedirs(db_dir)

logLevel = {True: logging.DEBUG,
False: logging.ERROR}[cls.DEBUG]
logging.basicConfig(level=logLevel,
Expand All @@ -31,7 +31,6 @@ class DevelopmentConfig(Config):
DEBUG = True
slack_api_key = os.environ['TEST_SLACK_API_KEY']
serverurl = 'http://127.0.0.1:5000/'
dbpath = 'var/slackru-dev.db'


class TestingConfig(DevelopmentConfig):
Expand All @@ -46,7 +45,6 @@ class ProductionConfig(Config):
botID = "U9DFNJMHR"
slack_api_key = os.environ['SLACK_API_KEY']
serverurl = "http://slackru.bkdhwfwsv2.us-east-1.elasticbeanstalk.com/"
dbpath = "var/slackru.db"


config = {'development': DevelopmentConfig,
Expand Down
21 changes: 3 additions & 18 deletions slackru/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
""" Shared Testing Utilities """

import os
import inspect
from datetime import datetime, timedelta
import os
from unittest.mock import MagicMock

import flask
from flask_testing import TestCase

from slackru import get_db


######################
# Shared Test Data #
Expand Down Expand Up @@ -94,25 +91,13 @@ class TestBase(TestCase):
""" Base Unit Testing Class. Inherited by all other test classes. """
@classmethod
def setUpClass(cls):
cls.db = get_db()

# A hack to enable tests to run certain commands only once per SESSION
# instead of once per CLASS
if not os.getenv('SLACKRU_TEST_SESSION'):
os.environ['SLACKRU_TEST_SESSION'] = 'True'
from slackru.config import config
from slackru import create_app
cls.app = create_app()
flask.testing = True
config.setup()

cls.db.drop_all()
cls.db.create_all()

start_time = datetime.now().strftime('%Y-%m-%d %H:%M')
end_time = (datetime.now() + timedelta(days=365)).strftime('%Y-%m-%d %H:%M')
cls.db.insertMentor(data['mentor'][0], data['mentorname'][0], data['mentorid'][0], data['phone_number'][0], "Python")
cls.db.insertMentor(data['mentor'][1], data['mentorname'][1], data['mentorid'][1], data['phone_number'][1], "Java")
cls.db.insertShift(data['mentorid'][0], start_time, end_time)
cls.db.insertShift(data['mentorid'][1], start_time, end_time)

def create_app(self):
""" Used by Flask-Testing package to create app context """
Expand Down
Loading

0 comments on commit 1449a60

Please sign in to comment.