Skip to content

Commit a10fe41

Browse files
Merge pull request #8278 from artemyk/remove_context_transactions
Eliminating contextmanager based transaction-handling
2 parents f33eabb + 30c6679 commit a10fe41

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
@@ -645,13 +646,9 @@ def insert_data(self):
645646

646647
return column_names, data_list
647648

648-
def get_session(self):
649-
con = self.pd_sql.engine.connect()
650-
return con.begin()
651-
652-
def _execute_insert(self, trans, keys, data_iter):
649+
def _execute_insert(self, conn, keys, data_iter):
653650
data = [dict( (k, v) for k, v in zip(keys, row) ) for row in data_iter]
654-
trans.connection.execute(self.insert_statement(), data)
651+
conn.execute(self.insert_statement(), data)
655652

656653
def insert(self, chunksize=None):
657654
keys, data_list = self.insert_data()
@@ -661,15 +658,15 @@ def insert(self, chunksize=None):
661658
chunksize = nrows
662659
chunks = int(nrows / chunksize) + 1
663660

664-
with self.get_session() as trans:
661+
with self.pd_sql.run_transaction() as conn:
665662
for i in range(chunks):
666663
start_i = i * chunksize
667664
end_i = min((i + 1) * chunksize, nrows)
668665
if start_i >= end_i:
669666
break
670667

671668
chunk_iter = zip(*[arr[start_i:end_i] for arr in data_list])
672-
self._execute_insert(trans, keys, chunk_iter)
669+
self._execute_insert(conn, keys, chunk_iter)
673670

674671
def read(self, coerce_float=True, parse_dates=None, columns=None):
675672

@@ -892,6 +889,9 @@ def __init__(self, engine, schema=None, meta=None):
892889

893890
self.meta = meta
894891

892+
def run_transaction(self):
893+
return self.engine.begin()
894+
895895
def execute(self, *args, **kwargs):
896896
"""Simple passthrough to SQLAlchemy engine"""
897897
return self.engine.execute(*args, **kwargs)
@@ -1025,9 +1025,9 @@ def sql_schema(self):
10251025
return str(";\n".join(self.table))
10261026

10271027
def _execute_create(self):
1028-
with self.get_session():
1028+
with self.pd_sql.run_transaction() as conn:
10291029
for stmt in self.table:
1030-
self.pd_sql.execute(stmt)
1030+
conn.execute(stmt)
10311031

10321032
def insert_statement(self):
10331033
names = list(map(str, self.frame.columns))
@@ -1046,12 +1046,9 @@ def insert_statement(self):
10461046
self.name, col_names, wildcards)
10471047
return insert_statement
10481048

1049-
def get_session(self):
1050-
return self.pd_sql.con
1051-
1052-
def _execute_insert(self, trans, keys, data_iter):
1049+
def _execute_insert(self, conn, keys, data_iter):
10531050
data_list = list(data_iter)
1054-
trans.executemany(self.insert_statement(), data_list)
1051+
conn.executemany(self.insert_statement(), data_list)
10551052

10561053
def _create_table_setup(self):
10571054
"""Return a list of SQL statement that create a table reflecting the
@@ -1133,6 +1130,17 @@ def __init__(self, con, flavor, is_cursor=False):
11331130
else:
11341131
self.flavor = flavor
11351132

1133+
@contextmanager
1134+
def run_transaction(self):
1135+
cur = self.con.cursor()
1136+
try:
1137+
yield cur
1138+
self.con.commit()
1139+
except:
1140+
self.con.rollback()
1141+
finally:
1142+
cur.close()
1143+
11361144
def execute(self, *args, **kwargs):
11371145
if self.is_cursor:
11381146
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)