Diff
Modified: trunk/docs/contents/changelog.rst (994 => 995)
--- trunk/docs/contents/changelog.rst 2019-04-25 11:41:30 UTC (rev 994)
+++ trunk/docs/contents/changelog.rst 2019-04-25 14:10:20 UTC (rev 995)
@@ -32,6 +32,11 @@
and this function is not part of the official API.
- Added new connection attributes `socket`, `backend_pid`, `ssl_in_use`
and `ssl_attributes` (the latter need PostgreSQL >= 9.5 on the client).
+- Changes in the DB-API 2 module (pgdb):
+ - Connections now have an `autocommit` attribute which is set to `False`
+ by default but can be set to `True` to switch to autocommit mode where
+ no transactions are started and calling commit() is not required. Note
+ that this is not part of the DB-API 2 standard.
Vesion 5.0.7 (2019-mm-dd)
-------------------------
Modified: trunk/docs/contents/pgdb/connection.rst (994 => 995)
--- trunk/docs/contents/pgdb/connection.rst 2019-04-25 11:41:30 UTC (rev 994)
+++ trunk/docs/contents/pgdb/connection.rst 2019-04-25 14:10:20 UTC (rev 995)
@@ -7,8 +7,10 @@
These connection objects respond to the following methods.
-Note that ``pgdb.Connection`` objects also implement the context manager protocol,
-i.e. you can use them in a ``with`` statement.
+Note that ``pgdb.Connection`` objects also implement the context manager
+protocol, i.e. you can use them in a ``with`` statement. When the ``with``
+block ends, the current transaction will be automatically committed or
+rolled back if there was an exception, and you won't need to do this manually.
close -- close the connection
-----------------------------
@@ -34,7 +36,8 @@
:rtype: None
-Note that connections always use a transaction, there is no auto-commit.
+Note that connections always use a transaction, unless you set the
+:attr:`Connection.autocommit` attribute described below.
rollback -- roll back the connection
------------------------------------
@@ -88,7 +91,27 @@
A dictionary with the various type codes for the PostgreSQL types
This can be used for getting more information on the PostgreSQL database
-types or changing the typecast functions used for the connection. See the
+types or changing the typecast functions used for the connection. See the
description of the :class:`TypeCache` class for details.
.. versionadded:: 5.0
+
+.. attribute:: Connection.autocommit
+
+ A read/write attribute to get/set the autocommit mode
+
+Normally, all DB-API 2 SQL commands are run inside a transaction. Sometimes
+this behavior is not desired; there are also some SQL commands such as VACUUM
+which cannot be run inside a transaction.
+
+By setting this attribute to ``True`` you can change this behavior so that no
+transactions will be started for that connection. In this case every executed
+SQL command has immediate effect on the database and you don't need to call
+:meth:`Connection.commit` explicitly. In this mode, you can still use
+``with con:`` blocks to run parts of the code using the connection ``con``
+inside a transaction.
+
+By default, this attribute is set to ``False`` which conforms to the behavior
+specified by the DB-API 2 standard (manual commit required).
+
+.. versionadded:: 5.1
Modified: trunk/pgdb.py (994 => 995)
--- trunk/pgdb.py 2019-04-25 11:41:30 UTC (rev 994)
+++ trunk/pgdb.py 2019-04-25 14:10:20 UTC (rev 995)
@@ -1047,7 +1047,7 @@
rowcount = 0
sql = "BEGIN"
try:
- if not self._dbcnx._tnx:
+ if not self._dbcnx._tnx and not self._dbcnx.autocommit:
try:
self._src.execute(sql)
except DatabaseError:
@@ -1054,7 +1054,8 @@
raise # database provides error message
except Exception:
raise _op_error("Can't start transaction")
- self._dbcnx._tnx = True
+ else:
+ self._dbcnx._tnx = True
for parameters in seq_of_parameters:
sql = operation
sql = self._quoteparams(sql, parameters)
@@ -1463,6 +1464,7 @@
self._tnx = False # transaction state
self.type_cache = TypeCache(cnx)
self.cursor_type = Cursor
+ self.autocommit = False
try:
self._cnx.source()
except Exception:
@@ -1472,7 +1474,18 @@
"""Enter the runtime context for the connection object.
The runtime context can be used for running transactions.
+
+ This also starts a transaction in autocommit mode.
"""
+ if self.autocommit:
+ try:
+ self._cnx.source().execute("BEGIN")
+ except DatabaseError:
+ raise # database provides error message
+ except Exception:
+ raise _op_error("Can't start transaction")
+ else:
+ self._tnx = True
return self
def __exit__(self, et, ev, tb):
@@ -1514,9 +1527,9 @@
try:
self._cnx.source().execute("COMMIT")
except DatabaseError:
- raise
+ raise # database provides error message
except Exception:
- raise _op_error("Can't commit")
+ raise _op_error("Can't commit transaction")
else:
raise _op_error("Connection has been closed")
@@ -1528,9 +1541,9 @@
try:
self._cnx.source().execute("ROLLBACK")
except DatabaseError:
- raise
+ raise # database provides error message
except Exception:
- raise _op_error("Can't rollback")
+ raise _op_error("Can't rollback transaction")
else:
raise _op_error("Connection has been closed")
Modified: trunk/tests/test_dbapi20.py (994 => 995)
--- trunk/tests/test_dbapi20.py 2019-04-25 11:41:30 UTC (rev 994)
+++ trunk/tests/test_dbapi20.py 2019-04-25 14:10:20 UTC (rev 995)
@@ -34,7 +34,7 @@
from datetime import date, time, datetime, timedelta
from uuid import UUID as Uuid
-try:
+try: # noinspection PyUnresolvedReferences
long
except NameError: # Python >= 3.0
long = int
@@ -1180,39 +1180,80 @@
self.assertEqual(con.DataError, pgdb.DataError)
self.assertEqual(con.NotSupportedError, pgdb.NotSupportedError)
+ def test_transaction(self):
+ table = self.table_prefix + 'booze'
+ con1 = self._connect()
+ cur1 = con1.cursor()
+ self.executeDDL1(cur1)
+ con1.commit()
+ con2 = self._connect()
+ cur2 = con2.cursor()
+ cur2.execute("select name from %s" % table)
+ self.assertIsNone(cur2.fetchone())
+ cur1.execute("insert into %s values('Schlafly')" % table)
+ cur2.execute("select name from %s" % table)
+ self.assertIsNone(cur2.fetchone())
+ con1.commit()
+ cur2.execute("select name from %s" % table)
+ self.assertEqual(cur2.fetchone(), ('Schlafly',))
+ con2.close()
+ con1.close()
+
+ def test_autocommit(self):
+ table = self.table_prefix + 'booze'
+ con1 = self._connect()
+ con1.autocommit = True
+ cur1 = con1.cursor()
+ self.executeDDL1(cur1)
+ con2 = self._connect()
+ cur2 = con2.cursor()
+ cur2.execute("select name from %s" % table)
+ self.assertIsNone(cur2.fetchone())
+ cur1.execute("insert into %s values('Shmaltz Pastrami')" % table)
+ cur2.execute("select name from %s" % table)
+ self.assertEqual(cur2.fetchone(), ('Shmaltz Pastrami',))
+ con2.close()
+ con1.close()
+
def test_connection_as_contextmanager(self):
table = self.table_prefix + 'booze'
- con = self._connect()
- try:
- cur = con.cursor()
- cur.execute("create table %s (n smallint check(n!=4))" % table)
- with con:
- cur.execute("insert into %s values (1)" % table)
- cur.execute("insert into %s values (2)" % table)
+ for autocommit in False, True:
+ con = self._connect()
+ con.autocommit = autocommit
try:
+ cur = con.cursor()
+ if autocommit:
+ cur.execute("truncate %s" % table)
+ else:
+ cur.execute(
+ "create table %s (n smallint check(n!=4))" % table)
with con:
- cur.execute("insert into %s values (3)" % table)
- cur.execute("insert into %s values (4)" % table)
- except con.IntegrityError as error:
- self.assertTrue('check' in str(error).lower())
- with con:
- cur.execute("insert into %s values (5)" % table)
- cur.execute("insert into %s values (6)" % table)
- try:
+ cur.execute("insert into %s values (1)" % table)
+ cur.execute("insert into %s values (2)" % table)
+ try:
+ with con:
+ cur.execute("insert into %s values (3)" % table)
+ cur.execute("insert into %s values (4)" % table)
+ except con.IntegrityError as error:
+ self.assertTrue('check' in str(error).lower())
with con:
- cur.execute("insert into %s values (7)" % table)
- cur.execute("insert into %s values (8)" % table)
- raise ValueError('transaction should rollback')
- except ValueError as error:
- self.assertEqual(str(error), 'transaction should rollback')
- with con:
- cur.execute("insert into %s values (9)" % table)
- cur.execute("select * from %s order by 1" % table)
- rows = cur.fetchall()
- rows = [row[0] for row in rows]
- finally:
- con.close()
- self.assertEqual(rows, [1, 2, 5, 6, 9])
+ cur.execute("insert into %s values (5)" % table)
+ cur.execute("insert into %s values (6)" % table)
+ try:
+ with con:
+ cur.execute("insert into %s values (7)" % table)
+ cur.execute("insert into %s values (8)" % table)
+ raise ValueError('transaction should rollback')
+ except ValueError as error:
+ self.assertEqual(str(error), 'transaction should rollback')
+ with con:
+ cur.execute("insert into %s values (9)" % table)
+ cur.execute("select * from %s order by 1" % table)
+ rows = cur.fetchall()
+ rows = [row[0] for row in rows]
+ finally:
+ con.close()
+ self.assertEqual(rows, [1, 2, 5, 6, 9])
def test_cursor_connection(self):
con = self._connect()