Skip to content

Commit 30c6679

Browse files
author
Artemy Kolchinsky
committed
Avoid transaction context-managers for legacy SQL drivers (GH8277)
Eliminating contextmanager based transaction-handling Rewriting as contextmanager Code review fixes Changing sqlalchemy version to 0.7.10 for python2.6
1 parent 4179245 commit 30c6679

File tree

3 files changed

+50
-16
lines changed

3 files changed

+50
-16
lines changed

ci/requirements-2.6.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pytz==2013b
55
http://www.crummy.com/software/BeautifulSoup/bs4/download/4.2/beautifulsoup4-4.2.0.tar.gz
66
html5lib==1.0b2
77
numexpr==1.4.2
8-
sqlalchemy==0.7.4
8+
sqlalchemy==0.7.10
99
pymysql==0.6.0
1010
psycopg2==2.5
1111
scipy==0.11.0

pandas/io/sql.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from pandas.core.base import PandasObject
2020
from pandas.tseries.tools import to_datetime
2121

22+
from contextlib import contextmanager
2223

2324
class SQLAlchemyRequired(ImportError):
2425
pass
@@ -637,13 +638,9 @@ def insert_data(self):
637638

638639
return column_names, data_list
639640

640-
def get_session(self):
641-
con = self.pd_sql.engine.connect()
642-
return con.begin()
643-
644-
def _execute_insert(self, trans, keys, data_iter):
641+
def _execute_insert(self, conn, keys, data_iter):
645642
data = [dict( (k, v) for k, v in zip(keys, row) ) for row in data_iter]
646-
trans.connection.execute(self.insert_statement(), data)
643+
conn.execute(self.insert_statement(), data)
647644

648645
def insert(self, chunksize=None):
649646
keys, data_list = self.insert_data()
@@ -653,15 +650,15 @@ def insert(self, chunksize=None):
653650
chunksize = nrows
654651
chunks = int(nrows / chunksize) + 1
655652

656-
with self.get_session() as trans:
653+
with self.pd_sql.run_transaction() as conn:
657654
for i in range(chunks):
658655
start_i = i * chunksize
659656
end_i = min((i + 1) * chunksize, nrows)
660657
if start_i >= end_i:
661658
break
662659

663660
chunk_iter = zip(*[arr[start_i:end_i] for arr in data_list])
664-
self._execute_insert(trans, keys, chunk_iter)
661+
self._execute_insert(conn, keys, chunk_iter)
665662

666663
def read(self, coerce_float=True, parse_dates=None, columns=None):
667664

@@ -884,6 +881,9 @@ def __init__(self, engine, schema=None, meta=None):
884881

885882
self.meta = meta
886883

884+
def run_transaction(self):
885+
return self.engine.begin()
886+
887887
def execute(self, *args, **kwargs):
888888
"""Simple passthrough to SQLAlchemy engine"""
889889
return self.engine.execute(*args, **kwargs)
@@ -1017,9 +1017,9 @@ def sql_schema(self):
10171017
return str(";\n".join(self.table))
10181018

10191019
def _execute_create(self):
1020-
with self.get_session():
1020+
with self.pd_sql.run_transaction() as conn:
10211021
for stmt in self.table:
1022-
self.pd_sql.execute(stmt)
1022+
conn.execute(stmt)
10231023

10241024
def insert_statement(self):
10251025
names = list(map(str, self.frame.columns))
@@ -1038,12 +1038,9 @@ def insert_statement(self):
10381038
self.name, col_names, wildcards)
10391039
return insert_statement
10401040

1041-
def get_session(self):
1042-
return self.pd_sql.con
1043-
1044-
def _execute_insert(self, trans, keys, data_iter):
1041+
def _execute_insert(self, conn, keys, data_iter):
10451042
data_list = list(data_iter)
1046-
trans.executemany(self.insert_statement(), data_list)
1043+
conn.executemany(self.insert_statement(), data_list)
10471044

10481045
def _create_table_setup(self):
10491046
"""Return a list of SQL statement that create a table reflecting the
@@ -1125,6 +1122,17 @@ def __init__(self, con, flavor, is_cursor=False):
11251122
else:
11261123
self.flavor = flavor
11271124

1125+
@contextmanager
1126+
def run_transaction(self):
1127+
cur = self.con.cursor()
1128+
try:
1129+
yield cur
1130+
self.con.commit()
1131+
except:
1132+
self.con.rollback()
1133+
finally:
1134+
cur.close()
1135+
11281136
def execute(self, *args, **kwargs):
11291137
if self.is_cursor:
11301138
cur = self.con

pandas/io/tests/test_sql.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,28 @@ def _to_sql_save_index(self):
331331
ix_cols = self._get_index_columns('test_to_sql_saves_index')
332332
self.assertEqual(ix_cols, [['A',],])
333333

334+
def _transaction_test(self):
335+
self.pandasSQL.execute("CREATE TABLE test_trans (A INT, B TEXT)")
336+
337+
ins_sql = "INSERT INTO test_trans (A,B) VALUES (1, 'blah')"
338+
339+
# Make sure when transaction is rolled back, no rows get inserted
340+
try:
341+
with self.pandasSQL.run_transaction() as trans:
342+
trans.execute(ins_sql)
343+
raise Exception('error')
344+
except:
345+
# ignore raised exception
346+
pass
347+
res = self.pandasSQL.read_sql('SELECT * FROM test_trans')
348+
self.assertEqual(len(res), 0)
349+
350+
# Make sure when transaction is committed, rows do get inserted
351+
with self.pandasSQL.run_transaction() as trans:
352+
trans.execute(ins_sql)
353+
res2 = self.pandasSQL.read_sql('SELECT * FROM test_trans')
354+
self.assertEqual(len(res2), 1)
355+
334356

335357
#------------------------------------------------------------------------------
336358
#--- Testing the public API
@@ -1072,6 +1094,8 @@ def _get_index_columns(self, tbl_name):
10721094
def test_to_sql_save_index(self):
10731095
self._to_sql_save_index()
10741096

1097+
def test_transactions(self):
1098+
self._transaction_test()
10751099

10761100
class TestSQLiteAlchemy(_TestSQLAlchemy):
10771101
"""
@@ -1380,6 +1404,8 @@ def _get_index_columns(self, tbl_name):
13801404
def test_to_sql_save_index(self):
13811405
self._to_sql_save_index()
13821406

1407+
def test_transactions(self):
1408+
self._transaction_test()
13831409

13841410
class TestMySQLLegacy(TestSQLiteLegacy):
13851411
"""

0 commit comments

Comments
 (0)