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

problems running caravel with mysql metadata db using mysql username and password #1070

Closed
dennisobrien opened this issue Sep 7, 2016 · 27 comments
Labels
!deprecated-label:bug Deprecated label - Use #bug instead

Comments

@dennisobrien
Copy link
Contributor

I'm running into two problems when trying to configure caravel to run with mysql as the metadata store.

  • The charts in the example dashboards show only the error message "Please define at least one metric for your table".
  • Some database interactions result in a stacktrace with the error message "OperationalError: (pymysql.err.OperationalError) (1045, u"Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)")".

I've tried to make this as reproducible as possible by running caravel and mysql as docker containers and using docker-compose to make the pieces work together. The code is in this repo:
https://github.com/dennisobrien/caravel-mysql-docker-example

I have tested this first with caravel 0.10.0 and then built from source from the tag 'airbnb_prod.0.10.0.2'.

These errors do not occur when we change the metadata store to sqlite (by commenting out the environment variable SQLALCHEMY_DATABASE_URI lines in docker-compose.yml).

Steps to reproduce:

  • Install docker and docker-compose.
  • Clone the repo above and cd into it.
  • To build caravel from source:
    • docker-compose up --force-recreate --build
    • Get a cup of coffee.
  • To install from a pypi release, uncomment and comment the sections in Dockerfile.
  • In a browser, open http://localhost:8088 and use the credentials in the fabmanager create-admin line in docker-entrypoint.sh.
  • Click on a dashboard and notice the error messages.
  • In 0.10.0:
    • click Sources -> Databases -> SQL
  • In later versions
    • click SQL Lab, the choose 'main' in the Database selector.
  • Notice the stacktrace in the server log.
2016-09-07 01:52:51,509:ERROR:root:(pymysql.err.OperationalError) (1045, u"Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)")
Traceback (most recent call last):
  File "/home/caravel/caravel/caravel/views.py", line 66, in wraps
    return f(self, *args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/Flask_AppBuilder-1.8.1-py2.7.egg/flask_appbuilder/security/decorators.py", line 52, in wraps
    return f(self, *args, **kwargs)
  File "/home/caravel/caravel/caravel/views.py", line 1118, in tables
    'tables': database.all_table_names(schema),
  File "/home/caravel/caravel/caravel/models.py", line 445, in all_table_names
    return sorted(self.inspector.get_table_names(schema))
  File "/home/caravel/caravel/caravel/models.py", line 442, in inspector
    return sqla.inspect(engine)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/inspection.py", line 63, in inspect
    ret = reg(subject)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/reflection.py", line 139, in _insp
    return Inspector.from_engine(bind)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/reflection.py", line 135, in from_engine
    return Inspector(bind)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/reflection.py", line 109, in __init__
    bind.connect().close()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 2018, in connect
    return self._connection_cls(self, **kwargs)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 72, in __init__
    if connection is not None else engine.raw_connection()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 2104, in raw_connection
    self.pool.unique_connection, _connection)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 2078, in _wrap_pool_connect
    e, dialect, self)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 1405, in _handle_dbapi_exception_noconnection
    exc_info
  File "build/bdist.linux-x86_64/egg/sqlalchemy/util/compat.py", line 202, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 2074, in _wrap_pool_connect
    return fn()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 318, in unique_connection
    return _ConnectionFairy._checkout(self)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 713, in _checkout
    fairy = _ConnectionRecord.checkout(pool)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 480, in checkout
    rec = pool._do_get()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 1060, in _do_get
    self._dec_overflow()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 1057, in _do_get
    return self._create_connection()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 323, in _create_connection
    return _ConnectionRecord(self)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 449, in __init__
    self.connection = self.__connect()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/pool.py", line 607, in __connect
    connection = self.__pool._invoke_creator(self)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/strategies.py", line 97, in connect
    return dialect.connect(*cargs, **cparams)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/default.py", line 385, in connect
    return self.dbapi.connect(*cargs, **cparams)
  File "/usr/local/lib/python2.7/site-packages/pymysql/__init__.py", line 90, in Connect
    return Connection(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/pymysql/connections.py", line 690, in __init__
    self.connect()
  File "/usr/local/lib/python2.7/site-packages/pymysql/connections.py", line 908, in connect
    self._request_authentication()
  File "/usr/local/lib/python2.7/site-packages/pymysql/connections.py", line 1116, in _request_authentication
    auth_packet = self._read_packet()
  File "/usr/local/lib/python2.7/site-packages/pymysql/connections.py", line 983, in _read_packet
    packet.check_error()
  File "/usr/local/lib/python2.7/site-packages/pymysql/connections.py", line 395, in check_error
    err.raise_mysql_exception(self._data)
  File "/usr/local/lib/python2.7/site-packages/pymysql/err.py", line 107, in raise_mysql_exception
    raise errorclass(errno, errval)
OperationalError: (pymysql.err.OperationalError) (1045, u"Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)")

There are a couple suspicious things here:

  • Why "using password: NO"? There is a password specified in SQLALCHEMY_DATABASE_URI.
  • Why an IP address? If the GRANT was done using the host name from SQLALCHEMY_DATABASE_URI this would be a name, not an IP.

I'm happy to try any debugging ideas others might have.

thanks,
Dennis

@xrmx xrmx added the question label Sep 7, 2016
@xrmx
Copy link
Contributor

xrmx commented Sep 7, 2016

It looks like you put a wrong sqlalchemy url. Please open a python shell and try to debug the issue by connecting with sqlalchemy to that url. Please reopen if it's not a configuration issue.

@xrmx xrmx closed this as completed Sep 7, 2016
@dennisobrien
Copy link
Contributor Author

dennisobrien commented Sep 7, 2016

There is a lot of evidence that the sqlalchemy url works.

  • Dashboards are listed on the welcome page and in the Dashboards tab.
  • Slices are listed in the Slices tab.
  • Database 'main' is listed in Sources -> Databases.
  • Sources -> Databases -> edit record for 'main' -> Test Connection: "localhost:8088 says: Seems OK!"
  • Tables are listed in Sources -> Tables

I have also tried this with just 'mysql://' as well as with 'postgresql+psycopg2://'.

Can you re-open this? I don't have the option to re-open.

If you would like more specific information I would be happy to provide it.

@bkyryliuk
Copy link
Member

@dennisobrien - could you please attach screen shot of the database configuration that is failing?

@dennisobrien
Copy link
Contributor Author

Hi @bkyryliuk

Here's the view at http://localhost:8088/databaseview/edit/1 after clicking "Test Connection". It shows the sqlalchemy uri I am using.
image

And after testing the connection, the Tables appear below:
image

If I click on SQL Lab (in the latest tag) and select 'main', I see the error OperationalError: (pymysql.err.OperationalError) (1045, u"Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)") In I could get this error by going to Sources -> Databases -> SQL, so this is not a new problem, the UI just moved.
image

Here's the view at http://localhost:8088/caravel/dashboard/births/ which just shows another problem, possibly related to the above.
image

The build flow goes like this:

  • Clear the mysql volume to make sure we are initializing from a clean state.
    • $ docker-compose down --volumes
  • Launch the stack with docker-compose. (The first build takes a long time since it is currently building from source. It's possible to comment/uncomment and pip install 0.10.0.)
    • $ docker-compose up --force-recreate --build
  • docker-compose.yml
    • Spin up mysql and caravel.
    • Wait until mysql is ready before starting caravel.
    • Also define the environment variable SQLALCHEMY_DATABASE_URI.
  • Dockerfile
    • Build caravel from source.
  • docker-entrypoint.sh
    • Initialize and start caravel.
  • caravel_config.py
    • Pick up the environment variable SQLALCHEMY_DATABASE_URI.

@bkyryliuk
Copy link
Member

@dennisobrien could you try to start the sqluri with mysql:// rather than mysql+pymysql://

screen shot 2016-09-07 at 11 25 49 am

@dennisobrien
Copy link
Contributor Author

@bkyryliuk I tried 'mysql://mysqladmin:FIXME_12345@db:3306/db' as well as 'mysql://mysqladmin:FIXME_12345@db/db' and see the same symptoms.

One difference from the screenshot you included is that I have caravel and mysql on different machines. In the case of this test using docker-compose, they are in running in different containers. In the case of AWS, caravel is running in a container in ECS and mysql is an RDS, and I'm seeing this exact problem. Originally I thought it was an AWS issue (or me bungling the RDS configuration) so I made this docker-compose set up to try to get a minimal reproducible case with mysql and caravel running on separate "machines".

I'll continue to experiment but please continue to send along any ideas you'd like me to try.

@dennisobrien
Copy link
Contributor Author

The output of SHOW GRANTS looks reasonable:

SHOW GRANTS;
"GRANT USAGE ON *.* TO 'mysqladmin'@'%' IDENTIFIED BY PASSWORD <secret>"
"GRANT ALL PRIVILEGES ON `db`.* TO 'mysqladmin'@'%'"

If I'm interpreting this correctly, the user 'mysqladmin' can access the database from any host (the '%' wildcard). So the issue with the OperationalError above is probably to do with "using password: NO" and not the IP address of the host. Is something removing the password from the sqlalchemy uri? I see places where the connection password is masked, but I doubt this is the issue.

I did a dump of the ab_* tables, once from caravel running with mysql and another running with sqlite, just to see if any of the contents were different after the examples were loaded. I only see two differences:

  1. on mysql ids are longs (e.g., 1L) while on sqlite they are just integers (e.g., 1)
  2. on sqlite strings are unicode (e.g., u'can_this_form_post') while on mysql they are strings (e.g., 'can_this_form_post').

mysql:

select * from ab_role
(1L, 'Admin')
(2L, 'Public')
(3L, 'Alpha')
(4L, 'Gamma')

sqlite:

select * from ab_role
(1, u'Admin')
(2, u'Public')
(3, u'Alpha')
(4, u'Gamma')

I'd be surprised if this caused a problem.

@bkyryliuk
Copy link
Member

@dennisobrien - are you familiar with ipython ?
If yes - can you connect to the database from the box running caravel using sql alchemy in ipython ?

@dennisobrien
Copy link
Contributor Author

@bkyryliuk - I am familiar with ipython. It wasn't included in the docker image so I'm adding that now. (But since I'm still building from source from the tag 'airbnb_prod.0.10.0.2' it takes a while.)

In the meantime, I connected to the container running caravel and launched python (not ipython) and can connect fine. There are some tables that are empty that I would not expect to be empty, but this might be related to the problems.

$ docker exec -it [container_id] bash
root@8a4734ac6dfc:/home/caravel# python
Python 2.7.11 (default, Jun  9 2016, 14:42:54) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from sqlalchemy import create_engine
>>> SQLALCHEMY_DATABASE_URI = 'mysql://mysqladmin:FIXME_12345@db/db'
>>> engine = create_engine(SQLALCHEMY_DATABASE_URI)
>>> import pandas as pd
>>> 
>>> pd.read_sql("select * from dashboard_user", engine)
Empty DataFrame
Columns: [id, user_id, dashboard_id]
Index: []
>>> 
>>> pd.read_sql("select * from dashboards", engine)    
           created_on          changed_on  id    dashboard_title  \
0 2016-09-08 00:38:06 2016-09-08 00:38:06   1  World's Bank Data   
1 2016-09-08 00:38:09 2016-09-08 00:38:09   2             Births   

                                       position_json created_by_fk  \
0  [\n    {\n        "size_y": 2, \n        "size...          None   
1  [\n    {\n        "size_y": 4, \n        "size...          None   

  changed_by_fk   css description          slug json_metadata  
0          None  None        None  world_health          None  
1          None  None        None        births          None  
>>> 
>>> pd.read_sql("select * from datasources", engine)
Empty DataFrame
Columns: [created_on, changed_on, id, datasource_name, is_featured, is_hidden, description, default_endpoint, user_id, cluster_name, created_by_fk, changed_by_fk, offset, cache_timeout]
Index: []
>>> 

I also added this debug script at the startup. It's using the sqlalchemy uri to read the tables.
https://github.com/dennisobrien/caravel-mysql-docker-example/blob/master/debug_db.py

Let me know what you'd like to try with ipython.

thanks,
Dennis

@dennisobrien
Copy link
Contributor Author

I just updated the mysql image to 5.7 (from 5.6) and I see some debug output that wasn't reported in the previous version.

  1. Dropped connections. This might be harmless and caused by code no closing connections. It might be the cause of the final migration not being recorded in 'alembic_version'.
caravel_1  | INFO  [alembic.runtime.migration] Running upgrade 27ae655e4247 -> 960c69cb1f5b, add dttm_format related fields in table_columns
db_1       | 2016-09-08T01:19:57.962460Z 4 [Note] Aborted connection 4 to db: 'db' user: 'mysqladmin' host: '172.18.0.3' (Got an error reading communication packets)
  1. Access denied. These errors aren't being reported by caravel but the are being logged from the mysql container.
caravel_1  | 2016-09-08 01:20:03,901:INFO:flask_appbuilder.base:Registering class CssTemplateModelView on menu CSS Templates
db_1       | 2016-09-08T01:20:04.028538Z 8 [Note] Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)
db_1       | 2016-09-08T01:20:17.766845Z 9 [Note] Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)
db_1       | 2016-09-08T01:20:20.943038Z 10 [Note] Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)
db_1       | 2016-09-08T01:20:21.956964Z 11 [Note] Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)
db_1       | 2016-09-08T01:20:34.887068Z 12 [Note] Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)
caravel_1  | /usr/local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/mysqldb.py:95: Warning: Incorrect date value: '2017-07-19 13:23:33' for column 'ds' at row 1
caravel_1  |   rowcount = cursor.executemany(statement, parameters)
caravel_1  | /usr/local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/mysqldb.py:95: Warning: Incorrect date value: '2016-11-23 00:48:29' for column 'ds' at row 2
caravel_1  |   rowcount = cursor.executemany(statement, parameters)
caravel_1  | /usr/local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/mysqldb.py:95: Warning: Incorrect date value: '2015-02-27 10:03:31' for column 'ds' at row 3
caravel_1  |   rowcount = cursor.executemany(statement, parameters)

@bkyryliuk
Copy link
Member

@dennisobrien - let's try to import caravel and see if the app has the access in to the mysql:

from caravel import db, models
s = db.session()
dbs = s.(models.Database).all()
dbs = s.query(models.Database).first()
dbs
# it should be main db
db_obj.get_df("SELECT * from tables", None)
# it should output the list of tables
# in case it doesn't work
# check what value is there, make sure that password is stored
db_obj.sqlalchemy_uri
# set correct uri
db_obj.sqlalchemy_uri='mysql://mysqladmin:FIXME_12345@db/db'
# check if it works
db_obj.get_df("SELECT * from tables", None)

Please post here the stacktrace if you'll have some problems.

@bkyryliuk bkyryliuk reopened this Sep 8, 2016
@bkyryliuk bkyryliuk self-assigned this Sep 8, 2016
@dennisobrien
Copy link
Contributor Author

@bkyryliuk -- Thanks for the debugging advice. Here's the output of the session. (Even though it reports version 0.10.0, this is build from the tag 'airbnb_prod.0.10.0.2'.)

root@564d0054a5e0:/home/caravel# ipython
Python 2.7.11 (default, Jun  9 2016, 14:42:54) 
Type "copyright", "credits" or "license" for more information.

IPython 4.2.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: from caravel import db, models, version
/usr/local/lib/python2.7/site-packages/Flask-0.11.1-py2.7.egg/flask/exthook.py:71: ExtDeprecationWarning: Importing flask.ext.sqlalchemy is deprecated, use flask_sqlalchemy instead.
  .format(x=modname), ExtDeprecationWarning
/usr/local/lib/python2.7/site-packages/Flask-0.11.1-py2.7.egg/flask/exthook.py:71: ExtDeprecationWarning: Importing flask.ext.sqlalchemy._compat is deprecated, use flask_sqlalchemy._compat instead.
  .format(x=modname), ExtDeprecationWarning
/usr/local/lib/python2.7/site-packages/Flask-0.11.1-py2.7.egg/flask/exthook.py:71: ExtDeprecationWarning: Importing flask.ext.script is deprecated, use flask_script instead.
  .format(x=modname), ExtDeprecationWarning
2016-09-08 16:24:44,062:INFO:root:Using SQLALCHEMY_DATABASE_URI: mysql://mysqladmin:FIXME_12345@db/db
/usr/local/lib/python2.7/site-packages/Flask_Cache-0.13.1-py2.7.egg/flask_cache/__init__.py:152: UserWarning: Flask-Cache: CACHE_TYPE is set to null, caching is effectively disabled.
  warnings.warn("Flask-Cache: CACHE_TYPE is set to null, "
/usr/local/lib/python2.7/site-packages/Flask-0.11.1-py2.7.egg/flask/exthook.py:71: ExtDeprecationWarning: Importing flask.ext.cache is deprecated, use flask_cache instead.
  .format(x=modname), ExtDeprecationWarning
2016-09-08 16:24:44,128:INFO:flask_appbuilder.base:Registering class MyIndexView on menu 
2016-09-08 16:24:44,131:INFO:flask_appbuilder.base:Registering class UtilView on menu 
2016-09-08 16:24:44,133:INFO:flask_appbuilder.base:Registering class LocaleView on menu 
2016-09-08 16:24:44,135:INFO:flask_appbuilder.base:Registering class ResetPasswordView on menu 
2016-09-08 16:24:44,144:INFO:flask_appbuilder.base:Registering class ResetMyPasswordView on menu 
2016-09-08 16:24:44,152:INFO:flask_appbuilder.base:Registering class UserInfoEditView on menu 
2016-09-08 16:24:44,162:INFO:flask_appbuilder.base:Registering class AuthDBView on menu 
2016-09-08 16:24:44,168:INFO:flask_appbuilder.base:Registering class UserDBModelView on menu List Users
2016-09-08 16:24:44,195:INFO:flask_appbuilder.base:Registering class RoleModelView on menu List Roles
2016-09-08 16:24:44,220:INFO:flask_appbuilder.base:Registering class UserStatsChartView on menu User's Statistics
2016-09-08 16:24:44,236:INFO:flask_appbuilder.base:Registering class PermissionModelView on menu Base Permissions
2016-09-08 16:24:44,258:INFO:flask_appbuilder.base:Registering class ViewMenuModelView on menu Views/Menus
2016-09-08 16:24:44,278:INFO:flask_appbuilder.base:Registering class PermissionViewModelView on menu Permission on Views/Menus
2016-09-08 16:24:44,770:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.tables".
2016-09-08 16:24:44,770:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.tables.TableExtension".
2016-09-08 16:24:44,772:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.fenced_code".
2016-09-08 16:24:44,773:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2016-09-08 16:24:44,773:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.codehilite".
2016-09-08 16:24:44,773:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2016-09-08 16:24:44,773:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.tables".
2016-09-08 16:24:44,774:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.tables.TableExtension".
2016-09-08 16:24:44,774:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.fenced_code".
2016-09-08 16:24:44,774:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2016-09-08 16:24:44,774:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.codehilite".
2016-09-08 16:24:44,774:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2016-09-08 16:24:44,775:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.tables".
2016-09-08 16:24:44,775:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.tables.TableExtension".
2016-09-08 16:24:44,775:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.fenced_code".
2016-09-08 16:24:44,775:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2016-09-08 16:24:44,775:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.codehilite".
2016-09-08 16:24:44,775:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2016-09-08 16:24:44,779:INFO:flask_appbuilder.base:Registering class TableColumnInlineView on menu 
2016-09-08 16:24:44,798:INFO:flask_appbuilder.base:Registering class DruidColumnInlineView on menu 
2016-09-08 16:24:44,823:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.tables".
2016-09-08 16:24:44,824:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.tables.TableExtension".
2016-09-08 16:24:44,824:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.fenced_code".
2016-09-08 16:24:44,824:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2016-09-08 16:24:44,824:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.codehilite".
2016-09-08 16:24:44,824:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2016-09-08 16:24:44,825:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.tables".
2016-09-08 16:24:44,825:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.tables.TableExtension".
2016-09-08 16:24:44,825:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.fenced_code".
2016-09-08 16:24:44,825:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2016-09-08 16:24:44,825:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.codehilite".
2016-09-08 16:24:44,825:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2016-09-08 16:24:44,827:INFO:flask_appbuilder.base:Registering class SqlMetricInlineView on menu 
2016-09-08 16:24:44,842:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.tables".
2016-09-08 16:24:44,843:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.tables.TableExtension".
2016-09-08 16:24:44,843:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.fenced_code".
2016-09-08 16:24:44,843:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2016-09-08 16:24:44,843:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.codehilite".
2016-09-08 16:24:44,843:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2016-09-08 16:24:44,846:INFO:flask_appbuilder.base:Registering class DruidMetricInlineView on menu 
2016-09-08 16:24:44,869:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.tables".
2016-09-08 16:24:44,869:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.tables.TableExtension".
2016-09-08 16:24:44,869:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.fenced_code".
2016-09-08 16:24:44,869:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2016-09-08 16:24:44,869:DEBUG:MARKDOWN:Successfuly imported extension module "markdown.extensions.codehilite".
2016-09-08 16:24:44,870:DEBUG:MARKDOWN:Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2016-09-08 16:24:44,871:WARNING:flask_appbuilder.models.filters:Filter type not supported for column: password
2016-09-08 16:24:44,873:INFO:flask_appbuilder.base:Registering class DatabaseView on menu Databases
2016-09-08 16:24:44,900:WARNING:flask_appbuilder.models.filters:Filter type not supported for column: password
2016-09-08 16:24:44,902:INFO:flask_appbuilder.base:Registering class DatabaseAsync on menu 
2016-09-08 16:24:44,923:WARNING:flask_appbuilder.models.filters:Filter type not supported for column: password
2016-09-08 16:24:44,925:INFO:flask_appbuilder.base:Registering class DatabaseTablesAsync on menu 
2016-09-08 16:24:44,951:INFO:flask_appbuilder.base:Registering class TableModelView on menu Tables
2016-09-08 16:24:44,979:INFO:flask_appbuilder.base:Registering class SliceModelView on menu Slices
2016-09-08 16:24:45,001:INFO:flask_appbuilder.base:Registering class SliceAsync on menu 
2016-09-08 16:24:45,019:INFO:flask_appbuilder.base:Registering class SliceAddView on menu 
2016-09-08 16:24:45,038:INFO:flask_appbuilder.base:Registering class DashboardModelView on menu Dashboards
2016-09-08 16:24:45,116:INFO:flask_appbuilder.base:Registering class DashboardModelViewAsync on menu 
2016-09-08 16:24:45,132:INFO:flask_appbuilder.base:Registering class LogModelView on menu Action Log
2016-09-08 16:24:45,157:INFO:flask_appbuilder.base:Registering class R on menu 
2016-09-08 16:24:45,161:INFO:flask_appbuilder.base:Registering class Caravel on menu 
2016-09-08 16:24:45,191:INFO:flask_appbuilder.base:Registering class CssTemplateModelView on menu CSS Templates

In [2]: version.VERSION_INFO
Out[2]: (0, 10, 0)

In [3]: version.VERSION_BUILD
Out[3]: 0

In [4]: s = db.session()              

In [5]: s
Out[5]: <flask_sqlalchemy.SignallingSession at 0x7f7cb36b3b90>

In [6]: s.query(models.Database).all()
Out[6]: [main]

In [7]: s.query(models.Database).first()
Out[7]: main

In [8]: db_obj = s.query(models.Database).first()

In [9]: db_obj
Out[9]: main

In [10]: db_obj.get_df("SELECT * from tables", None)
---------------------------------------------------------------------------
OperationalError                          Traceback (most recent call last)
<ipython-input-10-de04cd15e243> in <module>()
----> 1 db_obj.get_df("SELECT * from tables", None)

/home/caravel/caravel/caravel/models.pyc in get_df(self, sql, schema)
    409     def get_df(self, sql, schema):
    410         eng = self.get_sqla_engine(schema=schema)
--> 411         cur = eng.execute(sql, schema=schema)
    412         cols = [col[0] for col in cur.cursor.description]
    413         df = pd.DataFrame(cur.fetchall(), columns=cols)

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in execute(self, statement, *multiparams, **params)
   1988         """
   1989 
-> 1990         connection = self.contextual_connect(close_with_result=True)
   1991         return connection.execute(statement, *multiparams, **params)
   1992 

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in contextual_connect(self, close_with_result, **kwargs)
   2037         return self._connection_cls(
   2038             self,
-> 2039             self._wrap_pool_connect(self.pool.connect, None),
   2040             close_with_result=close_with_result,
   2041             **kwargs)

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _wrap_pool_connect(self, fn, connection)
   2076             if connection is None:
   2077                 Connection._handle_dbapi_exception_noconnection(
-> 2078                     e, dialect, self)
   2079             else:
   2080                 util.reraise(*sys.exc_info())

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _handle_dbapi_exception_noconnection(cls, e, dialect, engine)
   1403             util.raise_from_cause(
   1404                 sqlalchemy_exception,
-> 1405                 exc_info
   1406             )
   1407         else:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/util/compat.pyc in raise_from_cause(exception, exc_info)
    200     exc_type, exc_value, exc_tb = exc_info
    201     cause = exc_value if exc_value is not exception else None
--> 202     reraise(type(exception), exception, tb=exc_tb, cause=cause)
    203 
    204 if py3k:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _wrap_pool_connect(self, fn, connection)
   2072         dialect = self.dialect
   2073         try:
-> 2074             return fn()
   2075         except dialect.dbapi.Error as e:
   2076             if connection is None:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in connect(self)
    374         """
    375         if not self._use_threadlocal:
--> 376             return _ConnectionFairy._checkout(self)
    377 
    378         try:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _checkout(cls, pool, threadconns, fairy)
    711     def _checkout(cls, pool, threadconns=None, fairy=None):
    712         if not fairy:
--> 713             fairy = _ConnectionRecord.checkout(pool)
    714 
    715             fairy._pool = pool

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in checkout(cls, pool)
    478     @classmethod
    479     def checkout(cls, pool):
--> 480         rec = pool._do_get()
    481         try:
    482             dbapi_connection = rec.get_connection()

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _do_get(self)
   1058                 except:
   1059                     with util.safe_reraise():
-> 1060                         self._dec_overflow()
   1061             else:
   1062                 return self._do_get()

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/util/langhelpers.pyc in __exit__(self, type_, value, traceback)
     58             exc_type, exc_value, exc_tb = self._exc_info
     59             self._exc_info = None   # remove potential circular references
---> 60             compat.reraise(exc_type, exc_value, exc_tb)
     61         else:
     62             if not compat.py3k and self._exc_info and self._exc_info[1]:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _do_get(self)
   1055             if self._inc_overflow():
   1056                 try:
-> 1057                     return self._create_connection()
   1058                 except:
   1059                     with util.safe_reraise():

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _create_connection(self)
    321         """Called by subclasses to create a new ConnectionRecord."""
    322 
--> 323         return _ConnectionRecord(self)
    324 
    325     def _invalidate(self, connection, exception=None):

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in __init__(self, pool)
    447     def __init__(self, pool):
    448         self.__pool = pool
--> 449         self.connection = self.__connect()
    450         self.finalize_callback = deque()
    451 

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in __connect(self)
    605         try:
    606             self.starttime = time.time()
--> 607             connection = self.__pool._invoke_creator(self)
    608             self.__pool.logger.debug("Created new connection %r", connection)
    609             return connection

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/strategies.pyc in connect(connection_record)
     95                         if connection is not None:
     96                             return connection
---> 97                 return dialect.connect(*cargs, **cparams)
     98 
     99             creator = pop_kwarg('creator', connect)

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/default.pyc in connect(self, *cargs, **cparams)
    383 
    384     def connect(self, *cargs, **cparams):
--> 385         return self.dbapi.connect(*cargs, **cparams)
    386 
    387     def create_connect_args(self, url):

/usr/local/lib/python2.7/site-packages/MySQLdb/__init__.pyc in Connect(*args, **kwargs)
     79     """Factory function for connections.Connection."""
     80     from MySQLdb.connections import Connection
---> 81     return Connection(*args, **kwargs)
     82 
     83 connect = Connection = Connect

/usr/local/lib/python2.7/site-packages/MySQLdb/connections.pyc in __init__(self, *args, **kwargs)
    202         self.waiter = kwargs2.pop('waiter', None)
    203 
--> 204         super(Connection, self).__init__(*args, **kwargs2)
    205         self.cursorclass = cursorclass
    206         self.encoders = dict([ (k, v) for k, v in conv.items()

OperationalError: (_mysql_exceptions.OperationalError) (1045, "Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)")

Inspect sqlalchemy_uri and notice it already has the password intact:

In [11]: db_obj.sqlalchemy_uri
Out[11]: u'mysql://mysqladmin:FIXME_12345@db/db'

Set it anyway and follow the rest of your script:

In [12]: db_obj.sqlalchemy_uri = 'mysql://mysqladmin:FIXME_12345@db/db'

In [13]: db_obj.get_df("SELECT * from tables", None)
---------------------------------------------------------------------------
OperationalError                          Traceback (most recent call last)
<ipython-input-13-de04cd15e243> in <module>()
----> 1 db_obj.get_df("SELECT * from tables", None)

/home/caravel/caravel/caravel/models.pyc in get_df(self, sql, schema)
    409     def get_df(self, sql, schema):
    410         eng = self.get_sqla_engine(schema=schema)
--> 411         cur = eng.execute(sql, schema=schema)
    412         cols = [col[0] for col in cur.cursor.description]
    413         df = pd.DataFrame(cur.fetchall(), columns=cols)

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in execute(self, statement, *multiparams, **params)
   1988         """
   1989 
-> 1990         connection = self.contextual_connect(close_with_result=True)
   1991         return connection.execute(statement, *multiparams, **params)
   1992 

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in contextual_connect(self, close_with_result, **kwargs)
   2037         return self._connection_cls(
   2038             self,
-> 2039             self._wrap_pool_connect(self.pool.connect, None),
   2040             close_with_result=close_with_result,
   2041             **kwargs)

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _wrap_pool_connect(self, fn, connection)
   2076             if connection is None:
   2077                 Connection._handle_dbapi_exception_noconnection(
-> 2078                     e, dialect, self)
   2079             else:
   2080                 util.reraise(*sys.exc_info())

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _handle_dbapi_exception_noconnection(cls, e, dialect, engine)
   1403             util.raise_from_cause(
   1404                 sqlalchemy_exception,
-> 1405                 exc_info
   1406             )
   1407         else:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/util/compat.pyc in raise_from_cause(exception, exc_info)
    200     exc_type, exc_value, exc_tb = exc_info
    201     cause = exc_value if exc_value is not exception else None
--> 202     reraise(type(exception), exception, tb=exc_tb, cause=cause)
    203 
    204 if py3k:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _wrap_pool_connect(self, fn, connection)
   2072         dialect = self.dialect
   2073         try:
-> 2074             return fn()
   2075         except dialect.dbapi.Error as e:
   2076             if connection is None:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in connect(self)
    374         """
    375         if not self._use_threadlocal:
--> 376             return _ConnectionFairy._checkout(self)
    377 
    378         try:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _checkout(cls, pool, threadconns, fairy)
    711     def _checkout(cls, pool, threadconns=None, fairy=None):
    712         if not fairy:
--> 713             fairy = _ConnectionRecord.checkout(pool)
    714 
    715             fairy._pool = pool

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in checkout(cls, pool)
    478     @classmethod
    479     def checkout(cls, pool):
--> 480         rec = pool._do_get()
    481         try:
    482             dbapi_connection = rec.get_connection()

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _do_get(self)
   1058                 except:
   1059                     with util.safe_reraise():
-> 1060                         self._dec_overflow()
   1061             else:
   1062                 return self._do_get()

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/util/langhelpers.pyc in __exit__(self, type_, value, traceback)
     58             exc_type, exc_value, exc_tb = self._exc_info
     59             self._exc_info = None   # remove potential circular references
---> 60             compat.reraise(exc_type, exc_value, exc_tb)
     61         else:
     62             if not compat.py3k and self._exc_info and self._exc_info[1]:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _do_get(self)
   1055             if self._inc_overflow():
   1056                 try:
-> 1057                     return self._create_connection()
   1058                 except:
   1059                     with util.safe_reraise():

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _create_connection(self)
    321         """Called by subclasses to create a new ConnectionRecord."""
    322 
--> 323         return _ConnectionRecord(self)
    324 
    325     def _invalidate(self, connection, exception=None):

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in __init__(self, pool)
    447     def __init__(self, pool):
    448         self.__pool = pool
--> 449         self.connection = self.__connect()
    450         self.finalize_callback = deque()
    451 

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in __connect(self)
    605         try:
    606             self.starttime = time.time()
--> 607             connection = self.__pool._invoke_creator(self)
    608             self.__pool.logger.debug("Created new connection %r", connection)
    609             return connection

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/strategies.pyc in connect(connection_record)
     95                         if connection is not None:
     96                             return connection
---> 97                 return dialect.connect(*cargs, **cparams)
     98 
     99             creator = pop_kwarg('creator', connect)

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/default.pyc in connect(self, *cargs, **cparams)
    383 
    384     def connect(self, *cargs, **cparams):
--> 385         return self.dbapi.connect(*cargs, **cparams)
    386 
    387     def create_connect_args(self, url):

/usr/local/lib/python2.7/site-packages/MySQLdb/__init__.pyc in Connect(*args, **kwargs)
     79     """Factory function for connections.Connection."""
     80     from MySQLdb.connections import Connection
---> 81     return Connection(*args, **kwargs)
     82 
     83 connect = Connection = Connect

/usr/local/lib/python2.7/site-packages/MySQLdb/connections.pyc in __init__(self, *args, **kwargs)
    202         self.waiter = kwargs2.pop('waiter', None)
    203 
--> 204         super(Connection, self).__init__(*args, **kwargs2)
    205         self.cursorclass = cursorclass
    206         self.encoders = dict([ (k, v) for k, v in conv.items()

OperationalError: (_mysql_exceptions.OperationalError) (1045, "Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)")

In [14]:

And just to be sure, this is the version of caravel

root@564d0054a5e0:/home/caravel# pip freeze | grep caravel
-e git+https://github.com/airbnb/caravel.git@561828c2f890e179c4116f773cc7507103804dc6#egg=caravel

@dennisobrien
Copy link
Contributor Author

I see similar stacktrace in #726 which was closed as a dupe of #596

If I follow the steps testconn uses it works fine. But when I use get_sqla_engine I get the error. Is this the problem:
https://github.com/airbnb/caravel/blob/airbnb_prod.0.10.0.2/caravel/models.py#L398

        url = make_url(self.sqlalchemy_uri_decrypted)

But again, I would expect this to break for everyone using a SQLALCHEMY_DATABASE_URI with a username and password.

Here's an example using the code in testconn:

In [1]: from caravel import db, models
...

In [2]: from sqlalchemy import create_engine

In [3]: s = db.session() 

In [4]: db_obj = s.query(models.Database).first()

In [6]: db_obj.sqlalchemy_uri
Out[6]: u'mysql://mysqladmin:FIXME_12345@db/db'

In [7]: engine = create_engine(db_obj.sqlalchemy_uri)

In [8]: engine.connect()
Out[8]: <sqlalchemy.engine.base.Connection at 0x7f35d4ed0690>

In [9]: engine.table_names()
Out[9]: 
[u'ab_permission',
 u'ab_permission_view',
 u'ab_permission_view_role',
 u'ab_register_user',
 u'ab_role',
 u'ab_user',
 u'ab_user_role',
 u'ab_view_menu',
 u'alembic_version',
 u'birth_names',
 u'clusters',
 u'columns',
 u'css_templates',
 u'dashboard_slices',
 u'dashboard_user',
 u'dashboards',
 u'datasources',
 u'dbs',
 u'energy_usage',
 u'favstar',
 u'logs',
 u'long_lat',
 u'metrics',
 u'multiformat_time_series',
 u'query',
 u'random_time_series',
 u'slice_user',
 u'slices',
 u'sql_metrics',
 u'table_columns',
 u'tables',
 u'url',
 u'wb_health_population']

And an example of the problem using the engine returned by get_sqla_engine:

In [11]: engine = db_obj.get_sqla_engine()

In [12]: engine.connect()
---------------------------------------------------------------------------
OperationalError                          Traceback (most recent call last)
<ipython-input-12-e398aa840b8a> in <module>()
----> 1 engine.connect()

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in connect(self, **kwargs)
   2016         """
   2017 
-> 2018         return self._connection_cls(self, **kwargs)
   2019 
   2020     def contextual_connect(self, close_with_result=False, **kwargs):

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in __init__(self, engine, connection, close_with_result, _branch_from, _execution_options, _dispatch, _has_events)
     70         else:
     71             self.__connection = connection \
---> 72                 if connection is not None else engine.raw_connection()
     73             self.__transaction = None
     74             self.__savepoint_seq = 0

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in raw_connection(self, _connection)
   2102         """
   2103         return self._wrap_pool_connect(
-> 2104             self.pool.unique_connection, _connection)
   2105 
   2106 

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _wrap_pool_connect(self, fn, connection)
   2076             if connection is None:
   2077                 Connection._handle_dbapi_exception_noconnection(
-> 2078                     e, dialect, self)
   2079             else:
   2080                 util.reraise(*sys.exc_info())

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _handle_dbapi_exception_noconnection(cls, e, dialect, engine)
   1403             util.raise_from_cause(
   1404                 sqlalchemy_exception,
-> 1405                 exc_info
   1406             )
   1407         else:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/util/compat.pyc in raise_from_cause(exception, exc_info)
    200     exc_type, exc_value, exc_tb = exc_info
    201     cause = exc_value if exc_value is not exception else None
--> 202     reraise(type(exception), exception, tb=exc_tb, cause=cause)
    203 
    204 if py3k:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.pyc in _wrap_pool_connect(self, fn, connection)
   2072         dialect = self.dialect
   2073         try:
-> 2074             return fn()
   2075         except dialect.dbapi.Error as e:
   2076             if connection is None:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in unique_connection(self)
    316 
    317         """
--> 318         return _ConnectionFairy._checkout(self)
    319 
    320     def _create_connection(self):

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _checkout(cls, pool, threadconns, fairy)
    711     def _checkout(cls, pool, threadconns=None, fairy=None):
    712         if not fairy:
--> 713             fairy = _ConnectionRecord.checkout(pool)
    714 
    715             fairy._pool = pool

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in checkout(cls, pool)
    478     @classmethod
    479     def checkout(cls, pool):
--> 480         rec = pool._do_get()
    481         try:
    482             dbapi_connection = rec.get_connection()

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _do_get(self)
   1058                 except:
   1059                     with util.safe_reraise():
-> 1060                         self._dec_overflow()
   1061             else:
   1062                 return self._do_get()

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/util/langhelpers.pyc in __exit__(self, type_, value, traceback)
     58             exc_type, exc_value, exc_tb = self._exc_info
     59             self._exc_info = None   # remove potential circular references
---> 60             compat.reraise(exc_type, exc_value, exc_tb)
     61         else:
     62             if not compat.py3k and self._exc_info and self._exc_info[1]:

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _do_get(self)
   1055             if self._inc_overflow():
   1056                 try:
-> 1057                     return self._create_connection()
   1058                 except:
   1059                     with util.safe_reraise():

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in _create_connection(self)
    321         """Called by subclasses to create a new ConnectionRecord."""
    322 
--> 323         return _ConnectionRecord(self)
    324 
    325     def _invalidate(self, connection, exception=None):

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in __init__(self, pool)
    447     def __init__(self, pool):
    448         self.__pool = pool
--> 449         self.connection = self.__connect()
    450         self.finalize_callback = deque()
    451 

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/pool.pyc in __connect(self)
    605         try:
    606             self.starttime = time.time()
--> 607             connection = self.__pool._invoke_creator(self)
    608             self.__pool.logger.debug("Created new connection %r", connection)
    609             return connection

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/strategies.pyc in connect(connection_record)
     95                         if connection is not None:
     96                             return connection
---> 97                 return dialect.connect(*cargs, **cparams)
     98 
     99             creator = pop_kwarg('creator', connect)

/usr/local/lib/python2.7/site-packages/SQLAlchemy-1.0.13-py2.7-linux-x86_64.egg/sqlalchemy/engine/default.pyc in connect(self, *cargs, **cparams)
    383 
    384     def connect(self, *cargs, **cparams):
--> 385         return self.dbapi.connect(*cargs, **cparams)
    386 
    387     def create_connect_args(self, url):

/usr/local/lib/python2.7/site-packages/MySQLdb/__init__.pyc in Connect(*args, **kwargs)
     79     """Factory function for connections.Connection."""
     80     from MySQLdb.connections import Connection
---> 81     return Connection(*args, **kwargs)
     82 
     83 connect = Connection = Connect

/usr/local/lib/python2.7/site-packages/MySQLdb/connections.pyc in __init__(self, *args, **kwargs)
    202         self.waiter = kwargs2.pop('waiter', None)
    203 
--> 204         super(Connection, self).__init__(*args, **kwargs2)
    205         self.cursorclass = cursorclass
    206         self.encoders = dict([ (k, v) for k, v in conv.items()

OperationalError: (_mysql_exceptions.OperationalError) (1045, "Access denied for user 'mysqladmin'@'172.18.0.3' (using password: NO)")

@dennisobrien
Copy link
Contributor Author

Here's an ipython session that tries to mimic the functionality of caravel.views.Caravel.explore (the handler for the endpoint '/explore/<datasource_type>/<datasource_id>/'). In this case I was emulating the endpoint '/caravel/explore/table/3/?...'

root@f96e0b9325c8:/home/caravel# ipython                                                                                                                                        
Python 2.7.11 (default, Jun  9 2016, 14:42:54) 
Type "copyright", "credits" or "license" for more information.

IPython 4.2.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: from caravel import db, models
...

In [2]: db.session.query(models.SqlaTable).all()
Out[2]: 
[energy_usage,
 wb_health_population,
 birth_names,
 random_time_series,
 long_lat,
 multiformat_time_series]

In [3]: datasources = db.session.query(models.SqlaTable).all()

In [4]: datasources = sorted(datasources, key=lambda ds: ds.full_name)

In [5]: datasources
Out[5]: 
[birth_names,
 energy_usage,
 long_lat,
 multiformat_time_series,
 random_time_series,
 wb_health_population]

In [6]: datasource = [ds for ds in datasources if 3 == ds.id]

In [7]: datasource             
Out[7]: [birth_names]

In [8]: datasource = datasource[0] if datasource else None

In [9]: datasource
Out[9]: birth_names

In [10]: slice_id = 20  # from inspecting the url query string

In [11]: slc = db.session.query(models.Slice).filter_by(id=slice_id).first()

In [12]: slc
Out[12]: Trends

In [13]: # skip the section on permissions

In [14]: action = None  # not in the request

In [15]: viz_type = 'line'

In [16]: from caravel import viz

In [22]: from urlparse import parse_qs

In [23]: request_args_string = 'slice_name=Trends&row_limit=50000&metric=sum__num&show_legend=y&flt_op_1=in&viz_type=line&since=100+years+ago&json=false&until=now&datasource_id=1&metrics=sum__num&datasource_name=birth_names&granularity=ds&slice_id=20&datasource_type=table&rich_tooltip=y&compare_lag=10&limit=25&markup_type=markdown&compare_suffix=o10Y&where=&groupby=name'

In [25]: request_args = parse_qs(request_args_string)

In [27]: request_args
Out[27]: 
{'compare_lag': ['10'],
 'compare_suffix': ['o10Y'],
 'datasource_id': ['1'],
 'datasource_name': ['birth_names'],
 'datasource_type': ['table'],
 'flt_op_1': ['in'],
 'granularity': ['ds'],
 'groupby': ['name'],
 'json': ['false'],
 'limit': ['25'],
 'markup_type': ['markdown'],
 'metric': ['sum__num'],
 'metrics': ['sum__num'],
 'rich_tooltip': ['y'],
 'row_limit': ['50000'],
 'show_legend': ['y'],
 'since': ['100 years ago'],
 'slice_id': ['20'],
 'slice_name': ['Trends'],
 'until': ['now'],
 'viz_type': ['line']}

In [28]: obj = viz.viz_types[viz_type](datasource, form_data=request_args, slice_=slc)
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-28-79440994ad54> in <module>()
----> 1 obj = viz.viz_types[viz_type](datasource, form_data=request_args, slice_=slc)

/home/caravel/caravel/caravel/viz.pyc in __init__(self, datasource, form_data, slice_)
     62 
     63         # TODO refactor all form related logic out of here and into forms.py
---> 64         ff = FormFactory(self)
     65         form_class = ff.get_form()
     66         defaults = form_class().data.copy()

/home/caravel/caravel/caravel/forms.pyc in __init__(self, viz)
    125         datasource = viz.datasource
    126         if not datasource.metrics_combo:
--> 127             raise Exception("Please define at least one metric for your table")
    128         default_metric = datasource.metrics_combo[0][0]
    129 

Exception: Please define at least one metric for your table

In [29]: 

And I can verify that metrics is an empty list.

In [29]: datasource.metrics_combo
Out[29]: []

In [30]: datasource.metrics      
Out[30]: []

But if I query the db directly, I do see 'metrics' as a key in 'params'.

In [31]: from sqlalchemy import create_engine

In [32]: s = db.session() 

In [33]: db_obj = s.query(models.Database).first()

In [34]: db_obj.sqlalchemy_uri
Out[34]: u'mysql://mysqladmin:FIXME_12345@db/db'

In [35]: engine = create_engine(db_obj.sqlalchemy_uri)

In [36]: engine.connect()
Out[36]: <sqlalchemy.engine.base.Connection at 0x7f215fdc4550>

In [38]: import pandas as pd                                     

In [41]: pd.read_sql("SELECT * from slices where id=20", engine)
Out[41]: 
           created_on          changed_on  id slice_name druid_datasource_id  \
0 2016-09-08 23:24:12 2016-09-08 23:24:12  20     Trends                None   

   table_id datasource_type datasource_name viz_type  \
0         3           table            None     line   

                                              params created_by_fk  \
0  {\n    "compare_lag": "10", \n    "compare_suf...          None   

  changed_by_fk description cache_timeout                        perm  
0          None        None          None  [main].[birth_names](id:3)  

In [50]: params = pd.read_sql("SELECT * from slices where id=20", engine)['params'].tolist()[0]

In [52]: import json

In [53]: json.loads(params)
Out[53]: 
{u'compare_lag': u'10',
 u'compare_suffix': u'o10Y',
 u'datasource_id': u'1',
 u'datasource_name': u'birth_names',
 u'datasource_type': u'table',
 u'flt_op_1': u'in',
 u'granularity': u'ds',
 u'groupby': [u'name'],
 u'limit': u'25',
 u'markup_type': u'markdown',
 u'metric': u'sum__num',
 u'metrics': [u'sum__num'],
 u'rich_tooltip': u'y',
 u'row_limit': 50000,
 u'show_legend': u'y',
 u'since': u'100 years ago',
 u'until': u'now',
 u'viz_type': u'line',
 u'where': u''}

In [54]: 

@bkyryliuk
Copy link
Member

bkyryliuk commented Sep 9, 2016

That's pretty interesting, here is what I get:

>>> db_obj.sqlalchemy_uri
u'mysql://root:XXXXXXXXXX@localhost/caravel_db'
>>> db_obj.sqlalchemy_uri_decrypted
'mysql://root:root@localhost/caravel_db'

In the views file https://github.com/airbnb/caravel/blob/master/caravel/views.py#L486 when you save the database model this code is executed:

    def pre_add(self, db):
        conn = sqla.engine.url.make_url(db.sqlalchemy_uri)
        db.password = conn.password
        conn.password = "X" * 10 if conn.password else None
        db.sqlalchemy_uri = str(conn)  # hides the password
        utils.merge_perm(sm, 'database_access', db.perm)

I would expect to see mysql://root:XXXXXXXXXX@localhost/caravel_db at the sqlalchemy_uri.
That feature was added quite some time ago.
What is happening if you edit the Database and save updated connection string?

@dennisobrien
Copy link
Contributor Author

Hmm. I don't see the X's in my uri.

In [1]: from caravel import db, models
...

In [2]: s = db.session()

In [3]: db_obj = s.query(models.Database).first()

In [4]: db_obj.sqlalchemy_uri
Out[4]: u'mysql://mysqladmin:FIXME_12345@db/db'

In [5]: db_obj.sqlalchemy_uri_decrypted
Out[5]: 'mysql://mysqladmin@db/db'

I am able to edit the database info, save it, reload and verify that it persists. I just updated the "Extra" json to test.

{
    "metadata_params": {},
    "engine_params": {},
    "foo": "bar"
}

@bkyryliuk
Copy link
Member

In [5]: db_obj.sqlalchemy_uri_decrypted
Out[5]: 'mysql://mysqladmin@db/db'

Is the reason why you cannot access the db as db.password is not set.

It means that pre_add function https://github.com/airbnb/caravel/blob/master/caravel/views.py#L486 wasn't called somehow.
I cannot replicate the bug on my side.

Do you see any errors when you are saving the DB configuration in the UI ?

@dennisobrien
Copy link
Contributor Author

@bkyryliuk

I don't see any errors in the log around the time that DatabaseView is registered (and I assume when pre_add is called). There is a warning at the end of this section:

caravel_1  | 2016-09-10 00:25:28,102:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: can edit on DatabaseView
caravel_1  | 2016-09-10 00:25:28,122:INFO:flask_appbuilder.security.sqla.manager:Added Permission can edit on DatabaseView to role Admin
caravel_1  | 2016-09-10 00:25:28,137:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: can delete on DatabaseView
caravel_1  | 2016-09-10 00:25:28,155:INFO:flask_appbuilder.security.sqla.manager:Added Permission can delete on DatabaseView to role Admin
caravel_1  | 2016-09-10 00:25:28,173:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: can download on DatabaseView
caravel_1  | 2016-09-10 00:25:28,197:INFO:flask_appbuilder.security.sqla.manager:Added Permission can download on DatabaseView to role Admin
caravel_1  | 2016-09-10 00:25:28,214:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: can list on DatabaseView
caravel_1  | 2016-09-10 00:25:28,239:INFO:flask_appbuilder.security.sqla.manager:Added Permission can list on DatabaseView to role Admin
caravel_1  | 2016-09-10 00:25:28,258:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: can add on DatabaseView
caravel_1  | 2016-09-10 00:25:28,282:INFO:flask_appbuilder.security.sqla.manager:Added Permission can add on DatabaseView to role Admin
caravel_1  | 2016-09-10 00:25:28,299:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: can show on DatabaseView
caravel_1  | 2016-09-10 00:25:28,318:INFO:flask_appbuilder.security.sqla.manager:Added Permission can show on DatabaseView to role Admin
caravel_1  | 2016-09-10 00:25:28,349:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: muldelete on DatabaseView
caravel_1  | 2016-09-10 00:25:28,373:INFO:flask_appbuilder.security.sqla.manager:Added Permission muldelete on DatabaseView to role Admin
caravel_1  | 2016-09-10 00:25:28,401:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: menu access on Databases
caravel_1  | 2016-09-10 00:25:28,428:INFO:flask_appbuilder.security.sqla.manager:Added Permission menu access on Databases to role Admin
caravel_1  | 2016-09-10 00:25:28,455:INFO:flask_appbuilder.security.sqla.manager:Created Permission View: menu access on Sources
caravel_1  | 2016-09-10 00:25:28,476:INFO:flask_appbuilder.security.sqla.manager:Added Permission menu access on Sources to role Admin
caravel_1  | 2016-09-10 00:25:28,477:WARNING:flask_appbuilder.models.filters:Filter type not supported for column: password

And here's the record from the dbs table. Note that sqlalchemy_uri has the original value of SQLALCHEMY_DATABASE_URI and password is None. I'm guessing the password is supposed to be a hashed string, not None.

In [1]: from sqlalchemy import create_engine

In [2]: SQLALCHEMY_DATABASE_URI = 'mysql://mysqladmin:FIXME_12345@db/db'

In [3]: engine = create_engine(SQLALCHEMY_DATABASE_URI)

In [4]: import pandas as pd

In [7]: pd.read_sql("select * from dbs", engine).T
Out[7]: 
                                                                           0
created_on                                               2016-09-10 00:25:52
changed_on                                               2016-09-10 00:25:52
id                                                                         1
database_name                                                           main
sqlalchemy_uri                          mysql://mysqladmin:FIXME_12345@db/db
created_by_fk                                                           None
changed_by_fk                                                           None
password                                                                None
cache_timeout                                                           None
extra                      {\n    "metadata_params": {},\n    "engine_par...
select_as_create_table_as                                                  0

Also, does it matter that in the migration, no key is specified, while in the Database.password definition, it is provided?
https://github.com/airbnb/caravel/blob/master/caravel/migrations/versions/289ce07647b_add_encrypted_password_field.py#L23
https://github.com/airbnb/caravel/blob/airbnb_prod.0.10.0.2/caravel/models.py#L382

I'm not very familiar with Flask-AppBuilder and pre_add but it looks like it is only called for POST actions. That might explain why something like 'testconn' works ("POST /caravel/testconn HTTP/1.1" 200) and SQL Lab and others do not ("GET /caravel/tables/1/undefined HTTP/1.1" 500, "GET /databasetablesasync/api/read?_flt_0_id=1 HTTP/1.1" 500)?
https://github.com/dpgaspar/Flask-AppBuilder/blob/1.8.1/flask_appbuilder/baseviews.py#L910

@dennisobrien
Copy link
Contributor Author

@bkyryliuk You've already helped me immensely so this is not a request, but if you want a reproducible test case, and already have docker and docker-compose installed, you can clone this repo and do a one-liner to bring up this stack.
https://github.com/dennisobrien/caravel-mysql-docker-example

It's probably not as minimal as I could make it, but it is reproducible.

Thanks again for all your help!

@dennisobrien dennisobrien changed the title problems running caravel with mysql metadata db problems running caravel with mysql metadata db using mysql username and password Sep 12, 2016
@dennisobrien
Copy link
Contributor Author

@bkyryliuk - I just found that if I use the mysql 'root' user and configure it to have no password, these problems all go away.

  • All the example dashboards appear.
  • I am able to query the 'main' database in the SQL Labs section.

So I think this bug is specific to using a mysql (or postgres) username and password in the SQLALCHEMY_DATABASE_URI. From your comments a couple days ago, I'm guessing you are using the mysql user with no password and that's why you have not been able to reproduce this problem.

@RMKD
Copy link

RMKD commented Sep 12, 2016

I'm also seeing this issue with postgres passwords. commenting out the conn.password overwrite resolves the access problem (but of course leaves it visible in the view). is db.sqlalchemy_uri getting called somewhere without first reading db.password?

    def pre_add(self, db):
        conn = sqla.engine.url.make_url(db.sqlalchemy_uri)
        db.password = conn.password
        # conn.password = "X" * 10 if conn.password else None
        db.sqlalchemy_uri = str(conn)  # hides the password

@dennisobrien
Copy link
Contributor Author

@bkyryliuk - If you have any guidance on fixing this I'd be happy to submit a PR. At the moment, I'm confused on the intent of the code. caravel.models.Database has class attributes sqlalchemy_uri and password. At initialization utils.get_or_create_main_db sets dbobj.sqlalchemy_uri = config.get("SQLALCHEMY_DATABASE_URI") and at this point the "dbs" entry for "main" has the full values for sqlalchemy_uri and null for password.

Later in caravel.views.DatabaseView the method pre_add modifies db.sqlalchemy_uri to mask the password. But this is not called in the course of caravel init. (At least, I put a breakpoint there and it was never triggered.) Is the intent that the call to appbuilder.add_view(DatabaseView, ...) would trigger pre_add? In the Flask-AppBuilder source I only see pre_add called in RestCRUDView.api_create and in BaseCRUDView._add, both handlers of POST requests.

So calls to Database.sqlalchemy_uri_decrypted loses the password (since password is None) and calls to safe_sqlalchemy_uri returns the full string with the password.

Since a recent commit 6aadc6e#diff-397111749c5081b0f26dfa6ac13430dcL896 the command caravel load_examples fails with this stacktrace:

Loading examples into <SQLA engine=u'mysql://mysqladmin:FIXME_12345@127.0.0.1:3306/db'>
Creating default CSS templates
Loading energy related dataset
Creating table [wb_health_population] reference
2016-09-14 11:15:27,603:INFO:root:Creating database reference
2016-09-14 11:15:27,607:INFO:root:mysql://mysqladmin:FIXME_12345@127.0.0.1:3306/db
Traceback (most recent call last):
  File "/home/dobrien/anaconda3/envs/caraveldev/bin/caravel", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/home/dobrien/workspace/github/caravel/caravel/bin/caravel", line 152, in <module>
    manager.run()
  File "/home/dobrien/anaconda3/envs/caraveldev/lib/python2.7/site-packages/Flask_Script-2.0.5-py2.7.egg/flask_script/__init__.py", line 412, in run
    result = self.handle(sys.argv[0], sys.argv[1:])
  File "/home/dobrien/anaconda3/envs/caraveldev/lib/python2.7/site-packages/Flask_Script-2.0.5-py2.7.egg/flask_script/__init__.py", line 383, in handle
    res = handle(*args, **config)
  File "/home/dobrien/anaconda3/envs/caraveldev/lib/python2.7/site-packages/Flask_Script-2.0.5-py2.7.egg/flask_script/commands.py", line 216, in __call__
    return self.run(*args, **kwargs)
  File "/home/dobrien/workspace/github/caravel/caravel/bin/caravel", line 91, in load_examples
    data.load_energy()
  File "/home/dobrien/workspace/github/caravel/caravel/data/__init__.py", line 71, in load_energy
    tbl.fetch_metadata()
  File "/home/dobrien/workspace/github/caravel/caravel/models.py", line 906, in fetch_metadata
    "Table doesn't seem to exist in the specified database, "
Exception: Table doesn't seem to exist in the specified database, couldn't fetch column information

I think previous to this commit it was failing silently here. So this is a good thing.

Maybe the easiest fix is to move the password masking work from pre_add to get_or_create_main_db. I'm open to ideas/guidance and would be happy to put together a PR.

thanks,
Dennis

@xrmx
Copy link
Contributor

xrmx commented Sep 14, 2016

@dennisobrien as you already have a testcase in #1092, whatever will make it work would be an improvement on current state of affairs :)

@bkyryliuk
Copy link
Member

@dennisobrien - I'll have a chance to take a better look closer to the end of the week.
Thanks for the detailed stacktraces and scripts.

We are using caravel with mysql and passwords in our production. Looks like the issue is that somehow def pre_add(self, db): isn't executed in your case.

@dennisobrien
Copy link
Contributor Author

dennisobrien commented Sep 14, 2016

I added this commit 0debcee to PR #1092 which fixes this issue for me. The Travis tests are still running but hopefully will pass.

As I'm new to this codebase, I'd appreciate any suggestions for improvements.

thanks,
Dennis

@dennisobrien
Copy link
Contributor Author

I closed PR #1092 and opened a cleaner PR #1137

(This seemed easier than dealing with all the merge conflicts from recently pulled changes in master.)

@bkyryliuk
Copy link
Member

Thanks for the fix @dennisobrien !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
!deprecated-label:bug Deprecated label - Use #bug instead
Projects
None yet
Development

No branches or pull requests

4 participants