Skip to content

Commit 80597dc

Browse files
Merge pull request #10105 from graingert/sqlalchemy-connectable
support both sqlalchemy engines and connections
2 parents bac025f + 7327f6b commit 80597dc

File tree

5 files changed

+180
-53
lines changed

5 files changed

+180
-53
lines changed

ci/requirements-2.7.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ boto=2.36.0
1717
bottleneck=0.8.0
1818
psycopg2=2.5.2
1919
patsy
20-
pymysql=0.6.1
20+
pymysql=0.6.3
2121
html5lib=1.0b2
2222
beautiful-soup=4.2.1
2323
httplib2=0.8

doc/source/io.rst

+8-1
Original file line numberDiff line numberDiff line change
@@ -3555,9 +3555,16 @@ below and the SQLAlchemy `documentation <http://docs.sqlalchemy.org/en/rel_0_9/c
35553555
.. ipython:: python
35563556
35573557
from sqlalchemy import create_engine
3558-
# Create your connection.
3558+
# Create your engine.
35593559
engine = create_engine('sqlite:///:memory:')
35603560
3561+
If you want to manage your own connections you can pass one of those instead:
3562+
3563+
.. ipython:: python
3564+
3565+
with engine.connect() as conn, conn.begin():
3566+
data = pd.read_sql_table('data', conn)
3567+
35613568
Writing DataFrames
35623569
~~~~~~~~~~~~~~~~~~
35633570

doc/source/whatsnew/v0.17.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Check the :ref:`API Changes <whatsnew_0170.api>` and :ref:`deprecations <whatsne
2626
New features
2727
~~~~~~~~~~~~
2828

29+
- SQL io functions now accept a SQLAlchemy connectable. (:issue:`7877`)
2930

3031
.. _whatsnew_0170.enhancements.other:
3132

pandas/io/sql.py

+49-34
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class DatabaseError(IOError):
3838
_SQLALCHEMY_INSTALLED = None
3939

4040

41-
def _is_sqlalchemy_engine(con):
41+
def _is_sqlalchemy_connectable(con):
4242
global _SQLALCHEMY_INSTALLED
4343
if _SQLALCHEMY_INSTALLED is None:
4444
try:
@@ -62,7 +62,7 @@ def compile_big_int_sqlite(type_, compiler, **kw):
6262

6363
if _SQLALCHEMY_INSTALLED:
6464
import sqlalchemy
65-
return isinstance(con, sqlalchemy.engine.Engine)
65+
return isinstance(con, sqlalchemy.engine.Connectable)
6666
else:
6767
return False
6868

@@ -139,7 +139,7 @@ def execute(sql, con, cur=None, params=None):
139139
----------
140140
sql : string
141141
Query to be executed
142-
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
142+
con : SQLAlchemy connectable(engine/connection) or sqlite3 DBAPI2 connection
143143
Using SQLAlchemy makes it possible to use any DB supported by that
144144
library.
145145
If a DBAPI2 object, only sqlite3 is supported.
@@ -282,14 +282,14 @@ def read_sql_table(table_name, con, schema=None, index_col=None,
282282
chunksize=None):
283283
"""Read SQL database table into a DataFrame.
284284
285-
Given a table name and an SQLAlchemy engine, returns a DataFrame.
285+
Given a table name and an SQLAlchemy connectable, returns a DataFrame.
286286
This function does not support DBAPI connections.
287287
288288
Parameters
289289
----------
290290
table_name : string
291291
Name of SQL table in database
292-
con : SQLAlchemy engine
292+
con : SQLAlchemy connectable
293293
Sqlite DBAPI connection mode not supported
294294
schema : string, default None
295295
Name of SQL schema in database to query (if database flavor
@@ -328,9 +328,9 @@ def read_sql_table(table_name, con, schema=None, index_col=None,
328328
read_sql
329329
330330
"""
331-
if not _is_sqlalchemy_engine(con):
331+
if not _is_sqlalchemy_connectable(con):
332332
raise NotImplementedError("read_sql_table only supported for "
333-
"SQLAlchemy engines.")
333+
"SQLAlchemy connectable.")
334334
import sqlalchemy
335335
from sqlalchemy.schema import MetaData
336336
meta = MetaData(con, schema=schema)
@@ -362,7 +362,7 @@ def read_sql_query(sql, con, index_col=None, coerce_float=True, params=None,
362362
----------
363363
sql : string
364364
SQL query to be executed
365-
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
365+
con : SQLAlchemy connectable(engine/connection) or sqlite3 DBAPI2 connection
366366
Using SQLAlchemy makes it possible to use any DB supported by that
367367
library.
368368
If a DBAPI2 object, only sqlite3 is supported.
@@ -420,7 +420,7 @@ def read_sql(sql, con, index_col=None, coerce_float=True, params=None,
420420
----------
421421
sql : string
422422
SQL query to be executed or database table name.
423-
con : SQLAlchemy engine or DBAPI2 connection (fallback mode)
423+
con : SQLAlchemy connectable(engine/connection) or DBAPI2 connection (fallback mode)
424424
Using SQLAlchemy makes it possible to use any DB supported by that
425425
library.
426426
If a DBAPI2 object, only sqlite3 is supported.
@@ -504,14 +504,14 @@ def to_sql(frame, name, con, flavor='sqlite', schema=None, if_exists='fail',
504504
frame : DataFrame
505505
name : string
506506
Name of SQL table
507-
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
507+
con : SQLAlchemy connectable(engine/connection) or sqlite3 DBAPI2 connection
508508
Using SQLAlchemy makes it possible to use any DB supported by that
509509
library.
510510
If a DBAPI2 object, only sqlite3 is supported.
511511
flavor : {'sqlite', 'mysql'}, default 'sqlite'
512-
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
512+
The flavor of SQL to use. Ignored when using SQLAlchemy connectable.
513513
'mysql' is deprecated and will be removed in future versions, but it
514-
will be further supported through SQLAlchemy engines.
514+
will be further supported through SQLAlchemy connectables.
515515
schema : string, default None
516516
Name of SQL schema in database to write to (if database flavor
517517
supports this). If None, use default schema (default).
@@ -557,14 +557,14 @@ def has_table(table_name, con, flavor='sqlite', schema=None):
557557
----------
558558
table_name: string
559559
Name of SQL table
560-
con: SQLAlchemy engine or sqlite3 DBAPI2 connection
560+
con: SQLAlchemy connectable(engine/connection) or sqlite3 DBAPI2 connection
561561
Using SQLAlchemy makes it possible to use any DB supported by that
562562
library.
563563
If a DBAPI2 object, only sqlite3 is supported.
564564
flavor: {'sqlite', 'mysql'}, default 'sqlite'
565-
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
565+
The flavor of SQL to use. Ignored when using SQLAlchemy connectable.
566566
'mysql' is deprecated and will be removed in future versions, but it
567-
will be further supported through SQLAlchemy engines.
567+
will be further supported through SQLAlchemy connectables.
568568
schema : string, default None
569569
Name of SQL schema in database to write to (if database flavor supports
570570
this). If None, use default schema (default).
@@ -581,7 +581,7 @@ def has_table(table_name, con, flavor='sqlite', schema=None):
581581

582582
_MYSQL_WARNING = ("The 'mysql' flavor with DBAPI connection is deprecated "
583583
"and will be removed in future versions. "
584-
"MySQL will be further supported with SQLAlchemy engines.")
584+
"MySQL will be further supported with SQLAlchemy connectables.")
585585

586586

587587
def pandasSQL_builder(con, flavor=None, schema=None, meta=None,
@@ -592,7 +592,7 @@ def pandasSQL_builder(con, flavor=None, schema=None, meta=None,
592592
"""
593593
# When support for DBAPI connections is removed,
594594
# is_cursor should not be necessary.
595-
if _is_sqlalchemy_engine(con):
595+
if _is_sqlalchemy_connectable(con):
596596
return SQLDatabase(con, schema=schema, meta=meta)
597597
else:
598598
if flavor == 'mysql':
@@ -637,7 +637,7 @@ def exists(self):
637637

638638
def sql_schema(self):
639639
from sqlalchemy.schema import CreateTable
640-
return str(CreateTable(self.table).compile(self.pd_sql.engine))
640+
return str(CreateTable(self.table).compile(self.pd_sql.connectable))
641641

642642
def _execute_create(self):
643643
# Inserting table into database, add to MetaData object
@@ -982,11 +982,11 @@ class PandasSQL(PandasObject):
982982
"""
983983

984984
def read_sql(self, *args, **kwargs):
985-
raise ValueError("PandasSQL must be created with an SQLAlchemy engine"
985+
raise ValueError("PandasSQL must be created with an SQLAlchemy connectable"
986986
" or connection+sql flavor")
987987

988988
def to_sql(self, *args, **kwargs):
989-
raise ValueError("PandasSQL must be created with an SQLAlchemy engine"
989+
raise ValueError("PandasSQL must be created with an SQLAlchemy connectable"
990990
" or connection+sql flavor")
991991

992992

@@ -997,8 +997,8 @@ class SQLDatabase(PandasSQL):
997997
998998
Parameters
999999
----------
1000-
engine : SQLAlchemy engine
1001-
Engine to connect with the database. Using SQLAlchemy makes it
1000+
engine : SQLAlchemy connectable
1001+
Connectable to connect with the database. Using SQLAlchemy makes it
10021002
possible to use any DB supported by that library.
10031003
schema : string, default None
10041004
Name of SQL schema in database to write to (if database flavor
@@ -1011,19 +1011,24 @@ class SQLDatabase(PandasSQL):
10111011
"""
10121012

10131013
def __init__(self, engine, schema=None, meta=None):
1014-
self.engine = engine
1014+
self.connectable = engine
10151015
if not meta:
10161016
from sqlalchemy.schema import MetaData
1017-
meta = MetaData(self.engine, schema=schema)
1017+
meta = MetaData(self.connectable, schema=schema)
10181018

10191019
self.meta = meta
10201020

1021+
@contextmanager
10211022
def run_transaction(self):
1022-
return self.engine.begin()
1023+
with self.connectable.begin() as tx:
1024+
if hasattr(tx, 'execute'):
1025+
yield tx
1026+
else:
1027+
yield self.connectable
10231028

10241029
def execute(self, *args, **kwargs):
1025-
"""Simple passthrough to SQLAlchemy engine"""
1026-
return self.engine.execute(*args, **kwargs)
1030+
"""Simple passthrough to SQLAlchemy connectable"""
1031+
return self.connectable.execute(*args, **kwargs)
10271032

10281033
def read_table(self, table_name, index_col=None, coerce_float=True,
10291034
parse_dates=None, columns=None, schema=None,
@@ -1191,7 +1196,13 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
11911196
table.create()
11921197
table.insert(chunksize)
11931198
# check for potentially case sensitivity issues (GH7815)
1194-
if name not in self.engine.table_names(schema=schema or self.meta.schema):
1199+
engine = self.connectable.engine
1200+
with self.connectable.connect() as conn:
1201+
table_names = engine.table_names(
1202+
schema=schema or self.meta.schema,
1203+
connection=conn,
1204+
)
1205+
if name not in table_names:
11951206
warnings.warn("The provided table name '{0}' is not found exactly "
11961207
"as such in the database after writing the table, "
11971208
"possibly due to case sensitivity issues. Consider "
@@ -1202,7 +1213,11 @@ def tables(self):
12021213
return self.meta.tables
12031214

12041215
def has_table(self, name, schema=None):
1205-
return self.engine.has_table(name, schema or self.meta.schema)
1216+
return self.connectable.run_callable(
1217+
self.connectable.dialect.has_table,
1218+
name,
1219+
schema or self.meta.schema,
1220+
)
12061221

12071222
def get_table(self, table_name, schema=None):
12081223
schema = schema or self.meta.schema
@@ -1221,7 +1236,7 @@ def get_table(self, table_name, schema=None):
12211236

12221237
def drop_table(self, table_name, schema=None):
12231238
schema = schema or self.meta.schema
1224-
if self.engine.has_table(table_name, schema):
1239+
if self.has_table(table_name, schema):
12251240
self.meta.reflect(only=[table_name], schema=schema)
12261241
self.get_table(table_name, schema).drop()
12271242
self.meta.clear()
@@ -1610,12 +1625,12 @@ def get_schema(frame, name, flavor='sqlite', keys=None, con=None, dtype=None):
16101625
name : string
16111626
name of SQL table
16121627
flavor : {'sqlite', 'mysql'}, default 'sqlite'
1613-
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
1628+
The flavor of SQL to use. Ignored when using SQLAlchemy connectable.
16141629
'mysql' is deprecated and will be removed in future versions, but it
16151630
will be further supported through SQLAlchemy engines.
16161631
keys : string or sequence
16171632
columns to use a primary key
1618-
con: an open SQL database connection object or an SQLAlchemy engine
1633+
con: an open SQL database connection object or a SQLAlchemy connectable
16191634
Using SQLAlchemy makes it possible to use any DB supported by that
16201635
library.
16211636
If a DBAPI2 object, only sqlite3 is supported.
@@ -1673,8 +1688,8 @@ def write_frame(frame, name, con, flavor='sqlite', if_exists='fail', **kwargs):
16731688
16741689
- With ``to_sql`` the index is written to the sql database by default. To
16751690
keep the behaviour this function you need to specify ``index=False``.
1676-
- The new ``to_sql`` function supports sqlalchemy engines to work with
1677-
different sql flavors.
1691+
- The new ``to_sql`` function supports sqlalchemy connectables to work
1692+
with different sql flavors.
16781693
16791694
See also
16801695
--------

0 commit comments

Comments
 (0)