Title: [995] trunk: Support autocommit attribute on pgdb connections
Revision
995
Author
cito
Date
2019-04-25 10:10:20 -0400 (Thu, 25 Apr 2019)

Log Message

Support autocommit attribute on pgdb connections

Modified Paths


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()
_______________________________________________
PyGreSQL mailing list
PyGreSQL@Vex.Net
https://mail.vex.net/mailman/listinfo/pygresql

Reply via email to