Skip to content

read_sql/to_sql can accept database URI as con parameter #10666

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

Merged
merged 1 commit into from
Jul 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.17.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ New features
Other enhancements
^^^^^^^^^^^^^^^^^^

- `read_sql` and `to_sql` can accept database URI as con parameter (:issue:`10214`)

- Enable `read_hdf` to be used without specifying a key when the HDF file contains a single dataset (:issue:`10443`)

- ``DatetimeIndex`` can be instantiated using strings contains ``NaT`` (:issue:`7599`)
Expand Down
30 changes: 26 additions & 4 deletions pandas/io/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def read_sql_table(table_name, con, schema=None, index_col=None,
----------
table_name : string
Name of SQL table in database
con : SQLAlchemy connectable
con : SQLAlchemy connectable (or database string URI)
Sqlite DBAPI connection mode not supported
schema : string, default None
Name of SQL schema in database to query (if database flavor
Expand Down Expand Up @@ -328,6 +328,8 @@ def read_sql_table(table_name, con, schema=None, index_col=None,
read_sql

"""

con = _engine_builder(con)
if not _is_sqlalchemy_connectable(con):
raise NotImplementedError("read_sql_table only supported for "
"SQLAlchemy connectable.")
Expand Down Expand Up @@ -362,7 +364,8 @@ def read_sql_query(sql, con, index_col=None, coerce_float=True, params=None,
----------
sql : string
SQL query to be executed
con : SQLAlchemy connectable(engine/connection) or sqlite3 DBAPI2 connection
con : SQLAlchemy connectable(engine/connection) or database string URI
or sqlite3 DBAPI2 connection
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object, only sqlite3 is supported.
Expand Down Expand Up @@ -420,7 +423,8 @@ def read_sql(sql, con, index_col=None, coerce_float=True, params=None,
----------
sql : string
SQL query to be executed or database table name.
con : SQLAlchemy connectable(engine/connection) or DBAPI2 connection (fallback mode)
con : SQLAlchemy connectable(engine/connection) or database string URI
or DBAPI2 connection (fallback mode)
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object, only sqlite3 is supported.
Expand Down Expand Up @@ -504,7 +508,8 @@ def to_sql(frame, name, con, flavor='sqlite', schema=None, if_exists='fail',
frame : DataFrame
name : string
Name of SQL table
con : SQLAlchemy connectable(engine/connection) or sqlite3 DBAPI2 connection
con : SQLAlchemy connectable(engine/connection) or database string URI
or sqlite3 DBAPI2 connection
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object, only sqlite3 is supported.
Expand Down Expand Up @@ -584,6 +589,22 @@ def has_table(table_name, con, flavor='sqlite', schema=None):
"MySQL will be further supported with SQLAlchemy connectables.")


def _engine_builder(con):
"""
Returns a SQLAlchemy engine from a URI (if con is a string)
else it just return con without modifying it
"""
if isinstance(con, string_types):
try:
import sqlalchemy
con = sqlalchemy.create_engine(con)
return con

except ImportError:
_SQLALCHEMY_INSTALLED = False

return con

def pandasSQL_builder(con, flavor=None, schema=None, meta=None,
is_cursor=False):
"""
Expand All @@ -592,6 +613,7 @@ def pandasSQL_builder(con, flavor=None, schema=None, meta=None,
"""
# When support for DBAPI connections is removed,
# is_cursor should not be necessary.
con = _engine_builder(con)
if _is_sqlalchemy_connectable(con):
return SQLDatabase(con, schema=schema, meta=meta)
else:
Expand Down
17 changes: 17 additions & 0 deletions pandas/io/tests/test_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,23 @@ def test_sqlalchemy_type_mapping(self):
table = sql.SQLTable("test_type", db, frame=df)
self.assertTrue(isinstance(table.table.c['time'].type, sqltypes.DateTime))

def test_to_sql_read_sql_with_database_uri(self):

# Test read_sql and .to_sql method with a database URI (GH10654)
test_frame1 = self.test_frame1
#db_uri = 'sqlite:///:memory:' # raises sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "iris": syntax error [SQL: 'iris']
with tm.ensure_clean() as name:
db_uri = 'sqlite:///' + name
table = 'iris'
test_frame1.to_sql(table, db_uri, if_exists='replace', index=False)
test_frame2 = sql.read_sql(table, db_uri)
test_frame3 = sql.read_sql_table(table, db_uri)
query = 'SELECT * FROM iris'
test_frame4 = sql.read_sql_query(query, db_uri)
tm.assert_frame_equal(test_frame1, test_frame2)
tm.assert_frame_equal(test_frame1, test_frame3)
tm.assert_frame_equal(test_frame1, test_frame4)


class _EngineToConnMixin(object):
"""
Expand Down