From 3793da09c5a65c5b90ad742fc41a027f29a2c366 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Fri, 24 Jul 2015 13:56:01 +0200 Subject: [PATCH] read_sql/to_sql can accept database URI as con parameter (:issue:`10214`) read_sql/to_sql can accept database URI as con parameter (issue 10666) read_sql/to_sql can accept database URI as con parameter (issue 10666) read_sql/to_sql can accept database URI as con parameter (issue 10666) read_sql/to_sql can accept database URI as con parameter (issue 10666) --- doc/source/whatsnew/v0.17.0.txt | 2 ++ pandas/io/sql.py | 30 ++++++++++++++++++++++++++---- pandas/io/tests/test_sql.py | 17 +++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index face3a1002bae..ff6759730da8f 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -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`) diff --git a/pandas/io/sql.py b/pandas/io/sql.py index ef8360f0ff459..6cc4b73ed7bbe 100644 --- a/pandas/io/sql.py +++ b/pandas/io/sql.py @@ -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 @@ -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.") @@ -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. @@ -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. @@ -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. @@ -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): """ @@ -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: diff --git a/pandas/io/tests/test_sql.py b/pandas/io/tests/test_sql.py index d95babff2653b..18dd13f9b896e 100644 --- a/pandas/io/tests/test_sql.py +++ b/pandas/io/tests/test_sql.py @@ -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): """