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

Db connection issue #100

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open

Db connection issue #100

wants to merge 15 commits into from

Conversation

tweddielin
Copy link
Collaborator

  • Solve previous DB connection switch issue by using app.config['SQLALCHEMY_DATABASE_URI'] and app.config['SQLALCHEMY_BINDS']
  • Refactor query.py based on the change

webapp/query.py Outdated

def get_model_parameters(model_id):

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

methods=['GET', 'POST'])
def get_model_detail(model_id):
df = query.get_model_parameters(model_id)
output=df

Choose a reason for hiding this comment

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

missing whitespace around operator

@@ -395,7 +392,16 @@ def get_metric_over_time(model_id):
except Exception as e:
return jsonify({"sorry": "Sorry, no results! Please try again."}), 500


@app.route(

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1


# Default DB
app.config['DB_NAME'] = "cmpd"
from webapp import db

Choose a reason for hiding this comment

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

'webapp.db' imported but unused

Copy link
Member

@jesteria jesteria left a comment

Choose a reason for hiding this comment

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

I know you're not really done with this. I was just curious about what was going on in Tyra-land 😁


app = Flask(__name__,instance_relative_config=True)
app.secret_key = "tyra american top models"
app.config['SQLALCHEMY_DATABASE_URI'] = db_dict['sfpd']['url']
Copy link
Member

Choose a reason for hiding this comment

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

I'm not really sure, but is it possible to omit this configuration setting? It seems like the project doesn't really have a "default" or "main" database connection, (unless we are in fact using SFPD that way?).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good call.

@@ -3,9 +3,15 @@
from flask_sqlalchemy import SQLAlchemy
import flask_login
import json
from config import db_dict
Copy link
Member

Choose a reason for hiding this comment

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

This is me getting out of scope, but I'm seeing a lot of code in our projects to map environment and/or file configuration variables to database URIs. The standard I've used in the past is simply to pass database URIs around, in a single setting variable. For frameworks which don't have native support for this URI, there are often simple libraries to add support; and, it's cool that Flask-SQLAlchemy supports these natively.

DATABASE_URI=postgresql://USER@HOST:PORT/DATABASE?PARAMS

Copy link
Collaborator Author

@tweddielin tweddielin Dec 14, 2017

Choose a reason for hiding this comment

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

Yeah I absolutely agree with you that the config thing is kinda messy or redundant. I don't like that personally too. Maybe we should change it in other PR.

Copy link
Member

Choose a reason for hiding this comment

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

👍

@@ -43,7 +41,6 @@ def testing():
@app.route('/db_choose/<string:project>', methods=['GET', 'POST'])
def db_choose(project):
try:
app.config['SQLALCHEMY_DATABASE_URI'] = db_dict[project]['url']
app.config['DB_NAME'] = project
Copy link
Member

Choose a reason for hiding this comment

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

I don't have a ton of experience with Flask, and so I'm curious – is this app.config dictionary not process-global? Is db_choose meant for something other than for the user to select the database for their session?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I end up using flask-session now.

session['engine'] = project

webapp/query.py Outdated
return model_comments


def get_model_groups(query_arg):
engine = db.get_engine(app, app.config['DB_NAME'])
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if it might not make more sense for the caller (the controller) to pass the database engine to the query method, rather than ask the query method to look up the appropriate database in this shared configuration? At least, that would make these query methods more easily testable. And it makes sense for the controller to be tying the parts together.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, I change to pass the database engine to the query method.
In controller.py:

engine = db.get_engine(app, session['engine'])
query_arg = {'model_id': model_id,
             'evaluation_start_time': evaluation_start_time}
prediction_df = query.get_model_prediction(query_arg, engine)

webapp/query.py Outdated
df = pd.read_sql(
query,
con=engine)
print(df)
Copy link
Member

Choose a reason for hiding this comment

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

Stray print?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

terminated!

webapp/query.py Outdated
FROM results.models
WHERE model_id={model_id};
""".format(
model_id=model_id)
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we're not too worried about SQL injection attacks here, but I'd suggest passing expression parameters to the database driver, nonetheless. At the least it might make this a little cleaner.

return pd.read_sql("""\
    SELECT model_parameters
    FROM results.models
    WHERE model_id=%s
    """,
    engine,
    params=(model_id,),
)

[docs]

Copy link
Collaborator Author

@tweddielin tweddielin Dec 14, 2017

Choose a reason for hiding this comment

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

Yeah for model_id and as_of_date we should pass it to database driver ( I don't know why I miss this one and I'll fix it), but for column_name or schema_name it won't work and have to just interpolate.

webapp/query.py Outdated
query = """
SELECT feature_list FROM results.model_groups WHERE model_group_id={}
""".format(model_group_id)
df = pd.read_sql(
query,
con=db.engine)
con=engine)

Choose a reason for hiding this comment

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

undefined name 'engine'

webapp/query.py Outdated
)
output = df_importance
return output

def get_feature_importance(query_arg):
def get_feature_importance(query_arg, engine):

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

webapp/query.py Outdated
@@ -1,12 +1,11 @@
import pandas as pd
from webapp import db
from webapp import app

Choose a reason for hiding this comment

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

'webapp.app' imported but unused

@@ -1,12 +1,26 @@
from flask import Flask
from flask import Flask, session
from flask.ext.session import Session, SqlAlchemySessionInterface

Choose a reason for hiding this comment

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

'flask.ext.session.SqlAlchemySessionInterface' imported but unused

@@ -1,12 +1,26 @@
from flask import Flask
from flask import Flask, session

Choose a reason for hiding this comment

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

'flask.session' imported but unused

@codecov-io
Copy link

codecov-io commented Dec 14, 2017

Codecov Report

Merging #100 into master will decrease coverage by 0.59%.
The diff coverage is 85.24%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master     #100     +/-   ##
=========================================
- Coverage   71.77%   71.17%   -0.6%     
=========================================
  Files           4        4             
  Lines         418      444     +26     
=========================================
+ Hits          300      316     +16     
- Misses        118      128     +10
Impacted Files Coverage Δ
webapp/__init__.py 100% <100%> (ø) ⬆️
webapp/controller.py 64.38% <81.08%> (-0.22%) ⬇️
webapp/query.py 84.37% <87.5%> (-3%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c1921de...a83226c. Read the comment docs.

Copy link
Member

@jesteria jesteria left a comment

Choose a reason for hiding this comment

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

🆒 – just a couple comments

app.secret_key = "tyra american top models"
app.config['SQLALCHEMY_DATABASE_URI'] = db_dict['cmpd']['url']
Copy link
Member

Choose a reason for hiding this comment

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

Did you intend to keep this setting? It looks like you've just switched defaulting to SFPD with CMPD.

Copy link
Member

Choose a reason for hiding this comment

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

Oh and in case this setting is required, that is that Flask requires a default database connection, that's OK, of course; though, I might suggest we make it something that doesn't actually resolve, to avoid unintentionally defaulting.

Moreover, we might want a database user/schema for Tyra, e.g. for session data, which this could (at some point) point to.

Copy link
Member

Choose a reason for hiding this comment

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

(bump)

app.secret_key = "tyra american top models"
app.config['SQLALCHEMY_DATABASE_URI'] = db_dict['cmpd']['url']

app.config['SQLALCHEMY_BINDS'] = {key: db_dict[key]['url'] for key in db_dict.keys()}
Copy link
Member

Choose a reason for hiding this comment

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

Same thing, but if you like 😸

{key: value['url'] for (key, value) in db_dict.items()}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good point.

db = SQLAlchemy(app)

app.config['SESSION_TYPE'] = 'filesystem'
Copy link
Member

Choose a reason for hiding this comment

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

Where do we currently deploy Tyra? Just want to check that it has a filesystem appropriate for writing session data.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it's on an ec2 machine.

@@ -11,8 +11,6 @@
import os
from config import db_dict

# Default DB
app.config['DB_NAME'] = "cmpd"
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to set/get a default database in the session?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nope. removed!

# try:
# engine = db.get_engine(app, app.config['DB_NAME'])
# except:
# engine = db.engine
Copy link
Member

Choose a reason for hiding this comment

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

stale code?

Copy link
Member

Choose a reason for hiding this comment

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

(bump)

@@ -21,6 +21,8 @@

def test_individual_importances():
with rig_test_client(data) as test_app:
with test_app.session_transaction() as session:
Copy link
Contributor

Choose a reason for hiding this comment

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

do we just want to add an optional engine to rig_test_client and have it handle this?

assert expected == response_data

def test_get_feature_dist_train():
with rig_test_client(data) as test_app:
with test_app.session_transaction() as session:
session['engine'] = 'cmpd'
Copy link
Member

Choose a reason for hiding this comment

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

what exactly is happening in these cases where you only set the session engine in the context-managed block? usually a context manager is doing something in __enter__ and something else in __exit__. does this affect the remainder of the test? is it different from:

session = test_app.session_transaction()
session['engine'] = 'cmpd'

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It will give me this error if I don't use the context-managed block with session.
TypeError: '_GeneratorContextManager' object does not support item assignment

Copy link
Member

Choose a reason for hiding this comment

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

That makes sense 😄 I would've guessed that it had an interface that wasn't based around the context manager, but I'm not sure it matters, anyway.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, in fact, I would guess that it's writing the session to the backend upon __exit__ 😉

Copy link
Member

@jesteria jesteria left a comment

Choose a reason for hiding this comment

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

Looks good to me. Just a couple comments, hopefully helpful.

assert expected == response_data

def test_get_feature_dist_train():
with rig_test_client(data) as test_app:
with test_app.session_transaction() as session:
session['engine'] = 'cmpd'
Copy link
Member

Choose a reason for hiding this comment

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

Ah, in fact, I would guess that it's writing the session to the backend upon __exit__ 😉

app.secret_key = "tyra american top models"
app.config['SQLALCHEMY_DATABASE_URI'] = db_dict['cmpd']['url']
Copy link
Member

Choose a reason for hiding this comment

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

(bump)

# try:
# engine = db.get_engine(app, app.config['DB_NAME'])
# except:
# engine = db.engine
Copy link
Member

Choose a reason for hiding this comment

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

(bump)

try:
return jsonify(results=output)
except Exception as e:
return jsonify({"sorry": "Sorry, no results! Please try again."}), 500
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible for jsonify to fail here? For that matter, is there no middleware or other layer which catches Python exceptions and returns response 500, (such as in flask or gunicorn)? It seems as though there's always a chance of an unanticipated, and therefore uncaught, exception being raised; but, here, we're not catching a particular case and returning a useful message, so much as (re?)-implementing a default exception handler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants