From 308b32848c6ea7cf3f3de384b3ef019436d7e257 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 14:17:11 +0200
Subject: [PATCH 01/84] Remove Webware Examples, move Tests and Docs up

---
 DBUtils/Examples/DBUtilsExample.py            | 455 ------------------
 DBUtils/Examples/Main.py                      |  21 -
 DBUtils/Examples/__init__.py                  |   1 -
 {DBUtils/Docs => Docs}/Doc.css                |   0
 {DBUtils/Docs => Docs}/DocUtils.css           |   0
 {DBUtils/Docs => Docs}/RelNotes-0.8.1.html    |   0
 {DBUtils/Docs => Docs}/RelNotes-0.9.1.html    |   0
 {DBUtils/Docs => Docs}/RelNotes-0.9.2.html    |   0
 {DBUtils/Docs => Docs}/RelNotes-0.9.3.html    |   0
 {DBUtils/Docs => Docs}/RelNotes-0.9.4.html    |   0
 {DBUtils/Docs => Docs}/RelNotes-1.0.html      |   0
 {DBUtils/Docs => Docs}/RelNotes-1.1.1.html    |   0
 {DBUtils/Docs => Docs}/RelNotes-1.1.html      |   0
 {DBUtils/Docs => Docs}/RelNotes-1.2.html      |   0
 {DBUtils/Docs => Docs}/RelNotes-1.3.html      |   0
 {DBUtils/Docs => Docs}/RelNotes-1.4.html      |   0
 {DBUtils/Docs => Docs}/UsersGuide.de.html     |   0
 {DBUtils/Docs => Docs}/UsersGuide.de.rst      |   0
 {DBUtils/Docs => Docs}/UsersGuide.html        |   0
 {DBUtils/Docs => Docs}/UsersGuide.rst         |   0
 {DBUtils/Docs => Docs}/dbdep.gif              | Bin
 {DBUtils/Docs => Docs}/persist.gif            | Bin
 {DBUtils/Docs => Docs}/pgdep.gif              | Bin
 {DBUtils/Docs => Docs}/pool.gif               | Bin
 {DBUtils/Tests => Tests}/TestPersistentDB.py  |   0
 {DBUtils/Tests => Tests}/TestPersistentPg.py  |   0
 {DBUtils/Tests => Tests}/TestPooledDB.py      |   0
 {DBUtils/Tests => Tests}/TestPooledPg.py      |   0
 .../Tests => Tests}/TestSimplePooledDB.py     |   0
 .../Tests => Tests}/TestSimplePooledPg.py     |   0
 {DBUtils/Tests => Tests}/TestSteadyDB.py      |   0
 {DBUtils/Tests => Tests}/TestSteadyPg.py      |   0
 .../Tests => Tests}/TestThreadingLocal.py     |   0
 {DBUtils/Tests => Tests}/__init__.py          |   0
 {DBUtils/Tests => Tests}/mock_db.py           |   0
 {DBUtils/Tests => Tests}/mock_pg.py           |   0
 36 files changed, 477 deletions(-)
 delete mode 100644 DBUtils/Examples/DBUtilsExample.py
 delete mode 100644 DBUtils/Examples/Main.py
 delete mode 100644 DBUtils/Examples/__init__.py
 rename {DBUtils/Docs => Docs}/Doc.css (100%)
 rename {DBUtils/Docs => Docs}/DocUtils.css (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-0.8.1.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-0.9.1.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-0.9.2.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-0.9.3.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-0.9.4.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-1.0.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-1.1.1.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-1.1.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-1.2.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-1.3.html (100%)
 rename {DBUtils/Docs => Docs}/RelNotes-1.4.html (100%)
 rename {DBUtils/Docs => Docs}/UsersGuide.de.html (100%)
 rename {DBUtils/Docs => Docs}/UsersGuide.de.rst (100%)
 rename {DBUtils/Docs => Docs}/UsersGuide.html (100%)
 rename {DBUtils/Docs => Docs}/UsersGuide.rst (100%)
 rename {DBUtils/Docs => Docs}/dbdep.gif (100%)
 rename {DBUtils/Docs => Docs}/persist.gif (100%)
 rename {DBUtils/Docs => Docs}/pgdep.gif (100%)
 rename {DBUtils/Docs => Docs}/pool.gif (100%)
 rename {DBUtils/Tests => Tests}/TestPersistentDB.py (100%)
 rename {DBUtils/Tests => Tests}/TestPersistentPg.py (100%)
 rename {DBUtils/Tests => Tests}/TestPooledDB.py (100%)
 rename {DBUtils/Tests => Tests}/TestPooledPg.py (100%)
 rename {DBUtils/Tests => Tests}/TestSimplePooledDB.py (100%)
 rename {DBUtils/Tests => Tests}/TestSimplePooledPg.py (100%)
 rename {DBUtils/Tests => Tests}/TestSteadyDB.py (100%)
 rename {DBUtils/Tests => Tests}/TestSteadyPg.py (100%)
 rename {DBUtils/Tests => Tests}/TestThreadingLocal.py (100%)
 rename {DBUtils/Tests => Tests}/__init__.py (100%)
 rename {DBUtils/Tests => Tests}/mock_db.py (100%)
 rename {DBUtils/Tests => Tests}/mock_pg.py (100%)

diff --git a/DBUtils/Examples/DBUtilsExample.py b/DBUtils/Examples/DBUtilsExample.py
deleted file mode 100644
index 8e40172..0000000
--- a/DBUtils/Examples/DBUtilsExample.py
+++ /dev/null
@@ -1,455 +0,0 @@
-
-from MiscUtils.Configurable import Configurable
-from WebKit.Examples.ExamplePage import ExamplePage
-
-
-class DBConfig(Configurable):
-    """Database configuration."""
-
-    def defaultConfig(self):
-        return {
-            'dbapi': 'pg',
-            'database': 'demo',
-            'user': 'demo',
-            'password': 'demo',
-            'mincached': 5,
-            'maxcached': 25
-        }
-
-    def configFilename(self):
-        return 'Configs/Database.config'
-
-
-# the database tables used in this example:
-tables = (
-    '''seminars (
-        id varchar(4) primary key,
-        title varchar(64) unique not null,
-        cost money,
-        places_left smallint)''',
-    '''attendees (
-        name varchar(64) not null,
-        seminar varchar(4),
-        paid boolean,
-        primary key(name, seminar),
-        foreign key (seminar) references seminars(id) on delete cascade)''')
-
-
-class DBUtilsExample(ExamplePage):
-    """Example page for the DBUtils package."""
-
-    # Initialize the database class once when this class is loaded:
-    config = DBConfig().config()
-    if config.get('maxcached', None) is None:
-        dbmod_name = 'Persistent'
-    else:
-        dbmod_name = 'Pooled'
-    dbapi_name = config.pop('dbapi', 'pg')
-    if dbapi_name == 'pg':  # use the PyGreSQL classic DB API
-        dbmod_name += 'Pg'
-        if 'database' in config:
-            config['dbname'] = config['database']
-            del config['database']
-        if 'password' in config:
-            config['passwd'] = config['password']
-            del config['password']
-    else:  # use a DB-API 2 compliant module
-        dbmod_name += 'DB'
-    dbapi = dbmod = dbclass = dbstatus = None
-    try:
-        dbapi = __import__(dbapi_name)
-        try:
-            dbmod = getattr(__import__('DBUtils.' + dbmod_name), dbmod_name)
-            try:
-                if dbapi_name != 'pg':
-                    config['creator'] = dbapi
-                dbclass = getattr(dbmod, dbmod_name)(**config)
-            except dbapi.Error as error:
-                dbstatus = str(error)
-            except Exception:
-                dbstatus = 'Could not connect to the database.'
-        except Exception:
-            dbstatus = 'Could not import DBUtils.%s.' % dbmod_name
-    except Exception:
-        dbstatus = 'Could not import %s.' % dbapi_name
-
-    # Initialize the buttons
-    _actions = []
-    _buttons = []
-    for action in (
-            'create tables', 'list seminars', 'list attendees',
-            'add seminar', 'add attendee'):
-        value = action.capitalize()
-        action = action.split()
-        action[1] = action[1].capitalize()
-        action = ''.join(action)
-        _actions.append(action)
-        _buttons.append(
-            '<input name="_action_%s" type="submit" value="%s">'
-            % (action, value))
-    _buttons = tuple(_buttons)
-
-    def title(self):
-        return "DBUtils Example"
-
-    def actions(self):
-        return ExamplePage.actions(self) + self._actions
-
-    def awake(self, transaction):
-        ExamplePage.awake(self, transaction)
-        self._output = []
-
-    def postAction(self, actionName):
-        self.writeBody()
-        del self._output
-        ExamplePage.postAction(self, actionName)
-
-    def output(self, s):
-        self._output.append(s)
-
-    def outputMsg(self, msg, error=False):
-        self._output.append(
-            '<p style="color:%s">%s</p>' % ('red' if error else 'green', msg))
-
-    def connection(self, shareable=True):
-        if self.dbstatus:
-            error = self.dbstatus
-        else:
-            try:
-                if self.dbmod_name == 'PooledDB':
-                    return self.dbclass.connection(shareable)
-                else:
-                    return self.dbclass.connection()
-            except self.dbapi.Error as error:
-                error = str(error)
-            except Exception:
-                error = 'Cannot connect to the database.'
-        self.outputMsg(error, True)
-
-    def dedicated_connection(self):
-        return self.connection(False)
-
-    def sqlEncode(self, s):
-        if s is None:
-            return 'null'
-        s = s.replace('\\', '\\\\').replace('\'', '\\\'')
-        return "'%s'" % s
-
-    def createTables(self):
-        db = self.dedicated_connection()
-        if not db:
-            return
-        for table in tables:
-            self._output.append(
-                '<p>Creating the following table:</p><pre>%s</pre>' % table)
-            ddl = 'create table ' + table
-            try:
-                if self.dbapi_name == 'pg':
-                    db.query(ddl)
-                else:
-                    db.cursor().execute(ddl)
-                    db.commit()
-            except self.dbapi.Error as error:
-                if self.dbapi_name != 'pg':
-                    db.rollback()
-                self.outputMsg(error, True)
-            else:
-                self.outputMsg('The table was successfully created.')
-        db.close()
-
-    def listSeminars(self):
-        id = self.request().field('id', None)
-        if id:
-            if not isinstance(id, list):
-                id = [id]
-            cmd = ','.join(map(self.sqlEncode, id))
-            cmd = 'delete from seminars where id in (%s)' % cmd
-            db = self.dedicated_connection()
-            if not db:
-                return
-            try:
-                if self.dbapi_name == 'pg':
-                    db.query('begin')
-                    db.query(cmd)
-                    db.query('end')
-                else:
-                    db.cursor().execute(cmd)
-                    db.commit()
-            except self.dbapi.Error as error:
-                try:
-                    if self.dbapi_name == 'pg':
-                        db.query('end')
-                    else:
-                        db.rollback()
-                except Exception:
-                    pass
-                self.outputMsg(error, True)
-                return
-            else:
-                self.outputMsg('Entries deleted: %d' % len(id))
-        db = self.connection()
-        if not db:
-            return
-        query = ('select id, title, cost, places_left'
-                 ' from seminars order by title')
-        try:
-            if self.dbapi_name == 'pg':
-                result = db.query(query).getresult()
-            else:
-                cursor = db.cursor()
-                cursor.execute(query)
-                result = cursor.fetchall()
-                cursor.close()
-        except self.dbapi.Error as error:
-            self.outputMsg(error, True)
-            return
-        if not result:
-            self.outputMsg('There are no seminars in the database.', True)
-            return
-        wr = self.output
-        button = self._buttons[1].replace('List seminars', 'Delete')
-        wr('<h4>List of seminars in the database:</h4>')
-        wr('<form action=""><table border="1" cellspacing="0" cellpadding="2">'
-            '<tr><th>ID</th><th>Seminar title</th><th>Cost</th>'
-            '<th>Places left</th><th>%s</th></tr>' % button)
-        for id, title, cost, places in result:
-            if places is None:
-                places = 'unlimited'
-            if not cost:
-                cost = 'free'
-            wr('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>'
-                '<input type="checkbox" name="id" value="%s">'
-                '</td></tr>' % (id, title, cost, places, id))
-        wr('</table></form>')
-
-    def listAttendees(self):
-        id = self.request().field('id', None)
-        if id:
-            if not isinstance(id, list):
-                id = [id]
-            cmds = [
-                'delete from attendees where rpad(seminar,4)||name in (%s)'
-                % ','.join(map(self.sqlEncode, id))]
-            places = {}
-            for i in id:
-                i = i[:4].rstrip()
-                if i in places:
-                    places[i] += 1
-                else:
-                    places[i] = 1
-            for i, n in places.items():
-                cmds.append(
-                    'update seminars set places_left=places_left+%d'
-                    ' where id=%s' % (n, self.sqlEncode(i)))
-            db = self.dedicated_connection()
-            if not db:
-                return
-            try:
-                if self.dbapi_name == 'pg':
-                    db.query('begin')
-                    for cmd in cmds:
-                        db.query(cmd)
-                    db.query('end')
-                else:
-                    for cmd in cmds:
-                        db.cursor().execute(cmd)
-                    db.commit()
-            except self.dbapi.Error as error:
-                if self.dbapi_name == 'pg':
-                    db.query('end')
-                else:
-                    db.rollback()
-                self.outputMsg(error, True)
-                return
-            else:
-                self.outputMsg('Entries deleted: %d' % len(id))
-        db = self.connection()
-        if not db:
-            return
-        query = ('select a.name, s.id, s.title, a.paid'
-                 ' from attendees a,seminars s'
-                 ' where s.id=a.seminar order by a.name, s.title')
-        try:
-            if self.dbapi_name == 'pg':
-                result = db.query(query).getresult()
-            else:
-                cursor = db.cursor()
-                cursor.execute(query)
-                result = cursor.fetchall()
-                cursor.close()
-        except self.dbapi.Error as error:
-            self.outputMsg(error, True)
-            return
-        if not result:
-            self.outputMsg('There are no attendees in the database.', True)
-            return
-        wr = self.output
-        button = self._buttons[2].replace('List attendees', 'Delete')
-        wr('<h4>List of attendees in the database:</h4>')
-        wr('<form action=""><table border="1" cellspacing="0" cellpadding="2">'
-            '<tr><th>Name</th><th>Seminar</th><th>Paid</th>'
-            '<th>%s</th></tr>' % button)
-        for name, id, title, paid in result:
-            paid = 'Yes' if paid else 'No'
-            id = id.ljust(4) + name
-            wr('<tr><td>%s</td><td>%s</td><td>%s</td>'
-                '<td><input type="checkbox" name="id" value="%s"></td>'
-                '</tr>' % (name, title, paid, id))
-        wr('</table></form>')
-
-    def addSeminar(self):
-        wr = self.output
-        wr('<h4>Add a seminar entry to the database:</h4>')
-        wr('<form action=""><table>'
-            '<tr><th>ID</th><td><input name="id" type="text" '
-            'size="4" maxlength="4"></td></tr>'
-            '<tr><th>Title</th><td><input name="title" type="text" '
-            'size="40" maxlength="64"></td></tr>'
-            '<tr><th>Cost</th><td><input name="cost" type="text" '
-            'size="20" maxlength="20"></td></tr>'
-            '<tr><th>Places</th><td><input name="places" type="text" '
-            'size="20" maxlength="20"></td></tr>'
-            '<tr><td colspan="2" align="right">%s</td></tr>'
-            '</table></form>' % self._buttons[3])
-        request = self.request()
-        if not request.hasField('id'):
-            return
-        values = []
-        for name in ('id', 'title', 'cost', 'places'):
-            values.append(request.field(name, '').strip())
-        if not values[0] or not values[1]:
-            self.outputMsg('You must enter a seminar ID and a title!')
-            return
-        if not values[2]:
-            values[2] = None
-        if not values[3]:
-            values[3] = None
-        db = self.dedicated_connection()
-        if not db:
-            return
-        cmd = ('insert into seminars values (%s,%s,%s,%s)'
-               % tuple(map(self.sqlEncode, values)))
-        try:
-            if self.dbapi_name == 'pg':
-                db.query('begin')
-                db.query(cmd)
-                db.query('end')
-            else:
-                db.cursor().execute(cmd)
-                db.commit()
-        except self.dbapi.Error as error:
-            if self.dbapi_name == 'pg':
-                db.query('end')
-            else:
-                db.rollback()
-            self.outputMsg(error, True)
-        else:
-            self.outputMsg('"%s" added to seminars.' % values[1])
-        db.close()
-
-    def addAttendee(self):
-        db = self.connection()
-        if not db:
-            return
-        query = ('select id, title from seminars'
-                 ' where places_left is null or places_left>0 order by title')
-        try:
-            if self.dbapi_name == 'pg':
-                result = db.query(query).getresult()
-            else:
-                cursor = db.cursor()
-                cursor.execute(query)
-                result = cursor.fetchall()
-                cursor.close()
-        except self.dbapi.Error as error:
-            self.outputMsg(error, True)
-            return
-        if not result:
-            self.outputMsg('You have to define seminars first.')
-            return
-        sem = ['<select name="seminar" size="1">']
-        for id, title in result:
-            sem.append('<option value="%s">%s</option>' % (id, title))
-        sem.append('</select>')
-        sem = ''.join(sem)
-        wr = self.output
-        wr('<h4>Add an attendee entry to the database:</h4>')
-        wr('<form action=""><table>'
-            '<tr><th>Name</th><td><input name="name" type="text" '
-            'size="40" maxlength="64"></td></tr>'
-            '<tr><th>Seminar</th><td>%s</td></tr>'
-            '<tr><th>Paid</th><td>'
-            '<input type="radio" name="paid" value="t">Yes '
-            '<input type="radio" name="paid" value="f" checked="checked">No'
-            '</td></tr><tr><td colspan="2" align="right">%s</td></tr>'
-            '</table></form>' % (sem, self._buttons[4]))
-        request = self.request()
-        if not request.hasField('name'):
-            return
-        values = []
-        for name in ('name', 'seminar', 'paid'):
-            values.append(request.field(name, '').strip())
-        if not values[0] or not values[1]:
-            self.outputMsg('You must enter a name and a seminar!')
-            return
-        db = self.dedicated_connection()
-        if not db:
-            return
-        try:
-            if self.dbapi_name == 'pg':
-                db.query('begin')
-                cmd = ('update seminars set places_left=places_left-1'
-                       ' where id=%s' % self.sqlEncode(values[1]))
-                db.query(cmd)
-                cmd = ('select places_left from seminars'
-                       ' where id=%s' % self.sqlEncode(values[1]))
-                if (db.query(cmd).getresult()[0][0] or 0) < 0:
-                    raise self.dbapi.Error("No more places left.")
-                cmd = ('insert into attendees values (%s,%s,%s)'
-                       % tuple(map(self.sqlEncode, values)))
-                db.query(cmd)
-                db.query('end')
-            else:
-                cursor = db.cursor()
-                cmd = ('update seminars set places_left=places_left-1'
-                       ' where id=%s' % self.sqlEncode(values[1]))
-                cursor.execute(cmd)
-                cmd = ('select places_left from seminars'
-                       ' where id=%s' % self.sqlEncode(values[1]))
-                cursor.execute(cmd)
-                if (cursor.fetchone()[0] or 0) < 0:
-                    raise self.dbapi.Error("No more places left.")
-                cmd = ('insert into attendees values (%s,%s,%s)'
-                       % tuple(map(self.sqlEncode, values)))
-                db.cursor().execute(cmd)
-                cursor.close()
-                db.commit()
-        except self.dbapi.Error as error:
-            if self.dbapi_name == 'pg':
-                db.query('end')
-            else:
-                db.rollback()
-            self.outputMsg(error, True)
-        else:
-            self.outputMsg('%s added to attendees.' % values[0])
-        db.close()
-
-    def writeContent(self):
-        wr = self.writeln
-        if self._output:
-            wr('\n'.join(self._output))
-            wr('<p><a href="DBUtilsExample">Back</a></p>')
-        else:
-            wr('<h2>Welcome to the %s!</h2>' % self.title())
-            wr('<h4>We are using DBUtils.%s and the %s database module.</h4>'
-                % (self.dbmod_name, self.dbapi_name))
-            wr('<p>Configuration: %r</p>' % DBConfig().config())
-            wr('<p>This example uses a small demo database '
-                'designed to track the attendees for a series of seminars '
-                '(see <a href="http://www.linuxjournal.com/article/2605">"'
-                'The Python DB-API"</a> by Andrew Kuchling).</p>')
-            wr('<form action="">'
-                '<p>%s (create the needed database tables first)</p>'
-                '<p>%s %s (list all database entries)</p>'
-                '<p>%s %s (add entries)</p>'
-                '</form>' % self._buttons)
diff --git a/DBUtils/Examples/Main.py b/DBUtils/Examples/Main.py
deleted file mode 100644
index 39bf4b6..0000000
--- a/DBUtils/Examples/Main.py
+++ /dev/null
@@ -1,21 +0,0 @@
-
-from WebKit.Examples.ExamplePage import ExamplePage
-
-
-class Main(ExamplePage):
-
-    def writeContent(self):
-        self.writeln('''<h2>DBUtils example</h2>
-<p>You can set the DBUtils parameters in the following file</p>
-<ul>
-<li><tt>Configs/Database.config</tt></li>
-</ul>
-<p>With the default settings,</p>
-<ul>
-<li>you must have the PostgreSQL database</li>
-<li>and the PyGreSQL adapter installed, and</li>
-<li>you must have created a database with the name "demo" and</li>
-<li>a database user with the name "demo" and password "demo".</li>
-</ul>
-<p><a href="DBUtilsExample">Start the demo!</a></p>
-''')
diff --git a/DBUtils/Examples/__init__.py b/DBUtils/Examples/__init__.py
deleted file mode 100644
index 71f8157..0000000
--- a/DBUtils/Examples/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# DBUtils Examples
diff --git a/DBUtils/Docs/Doc.css b/Docs/Doc.css
similarity index 100%
rename from DBUtils/Docs/Doc.css
rename to Docs/Doc.css
diff --git a/DBUtils/Docs/DocUtils.css b/Docs/DocUtils.css
similarity index 100%
rename from DBUtils/Docs/DocUtils.css
rename to Docs/DocUtils.css
diff --git a/DBUtils/Docs/RelNotes-0.8.1.html b/Docs/RelNotes-0.8.1.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-0.8.1.html
rename to Docs/RelNotes-0.8.1.html
diff --git a/DBUtils/Docs/RelNotes-0.9.1.html b/Docs/RelNotes-0.9.1.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-0.9.1.html
rename to Docs/RelNotes-0.9.1.html
diff --git a/DBUtils/Docs/RelNotes-0.9.2.html b/Docs/RelNotes-0.9.2.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-0.9.2.html
rename to Docs/RelNotes-0.9.2.html
diff --git a/DBUtils/Docs/RelNotes-0.9.3.html b/Docs/RelNotes-0.9.3.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-0.9.3.html
rename to Docs/RelNotes-0.9.3.html
diff --git a/DBUtils/Docs/RelNotes-0.9.4.html b/Docs/RelNotes-0.9.4.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-0.9.4.html
rename to Docs/RelNotes-0.9.4.html
diff --git a/DBUtils/Docs/RelNotes-1.0.html b/Docs/RelNotes-1.0.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-1.0.html
rename to Docs/RelNotes-1.0.html
diff --git a/DBUtils/Docs/RelNotes-1.1.1.html b/Docs/RelNotes-1.1.1.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-1.1.1.html
rename to Docs/RelNotes-1.1.1.html
diff --git a/DBUtils/Docs/RelNotes-1.1.html b/Docs/RelNotes-1.1.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-1.1.html
rename to Docs/RelNotes-1.1.html
diff --git a/DBUtils/Docs/RelNotes-1.2.html b/Docs/RelNotes-1.2.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-1.2.html
rename to Docs/RelNotes-1.2.html
diff --git a/DBUtils/Docs/RelNotes-1.3.html b/Docs/RelNotes-1.3.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-1.3.html
rename to Docs/RelNotes-1.3.html
diff --git a/DBUtils/Docs/RelNotes-1.4.html b/Docs/RelNotes-1.4.html
similarity index 100%
rename from DBUtils/Docs/RelNotes-1.4.html
rename to Docs/RelNotes-1.4.html
diff --git a/DBUtils/Docs/UsersGuide.de.html b/Docs/UsersGuide.de.html
similarity index 100%
rename from DBUtils/Docs/UsersGuide.de.html
rename to Docs/UsersGuide.de.html
diff --git a/DBUtils/Docs/UsersGuide.de.rst b/Docs/UsersGuide.de.rst
similarity index 100%
rename from DBUtils/Docs/UsersGuide.de.rst
rename to Docs/UsersGuide.de.rst
diff --git a/DBUtils/Docs/UsersGuide.html b/Docs/UsersGuide.html
similarity index 100%
rename from DBUtils/Docs/UsersGuide.html
rename to Docs/UsersGuide.html
diff --git a/DBUtils/Docs/UsersGuide.rst b/Docs/UsersGuide.rst
similarity index 100%
rename from DBUtils/Docs/UsersGuide.rst
rename to Docs/UsersGuide.rst
diff --git a/DBUtils/Docs/dbdep.gif b/Docs/dbdep.gif
similarity index 100%
rename from DBUtils/Docs/dbdep.gif
rename to Docs/dbdep.gif
diff --git a/DBUtils/Docs/persist.gif b/Docs/persist.gif
similarity index 100%
rename from DBUtils/Docs/persist.gif
rename to Docs/persist.gif
diff --git a/DBUtils/Docs/pgdep.gif b/Docs/pgdep.gif
similarity index 100%
rename from DBUtils/Docs/pgdep.gif
rename to Docs/pgdep.gif
diff --git a/DBUtils/Docs/pool.gif b/Docs/pool.gif
similarity index 100%
rename from DBUtils/Docs/pool.gif
rename to Docs/pool.gif
diff --git a/DBUtils/Tests/TestPersistentDB.py b/Tests/TestPersistentDB.py
similarity index 100%
rename from DBUtils/Tests/TestPersistentDB.py
rename to Tests/TestPersistentDB.py
diff --git a/DBUtils/Tests/TestPersistentPg.py b/Tests/TestPersistentPg.py
similarity index 100%
rename from DBUtils/Tests/TestPersistentPg.py
rename to Tests/TestPersistentPg.py
diff --git a/DBUtils/Tests/TestPooledDB.py b/Tests/TestPooledDB.py
similarity index 100%
rename from DBUtils/Tests/TestPooledDB.py
rename to Tests/TestPooledDB.py
diff --git a/DBUtils/Tests/TestPooledPg.py b/Tests/TestPooledPg.py
similarity index 100%
rename from DBUtils/Tests/TestPooledPg.py
rename to Tests/TestPooledPg.py
diff --git a/DBUtils/Tests/TestSimplePooledDB.py b/Tests/TestSimplePooledDB.py
similarity index 100%
rename from DBUtils/Tests/TestSimplePooledDB.py
rename to Tests/TestSimplePooledDB.py
diff --git a/DBUtils/Tests/TestSimplePooledPg.py b/Tests/TestSimplePooledPg.py
similarity index 100%
rename from DBUtils/Tests/TestSimplePooledPg.py
rename to Tests/TestSimplePooledPg.py
diff --git a/DBUtils/Tests/TestSteadyDB.py b/Tests/TestSteadyDB.py
similarity index 100%
rename from DBUtils/Tests/TestSteadyDB.py
rename to Tests/TestSteadyDB.py
diff --git a/DBUtils/Tests/TestSteadyPg.py b/Tests/TestSteadyPg.py
similarity index 100%
rename from DBUtils/Tests/TestSteadyPg.py
rename to Tests/TestSteadyPg.py
diff --git a/DBUtils/Tests/TestThreadingLocal.py b/Tests/TestThreadingLocal.py
similarity index 100%
rename from DBUtils/Tests/TestThreadingLocal.py
rename to Tests/TestThreadingLocal.py
diff --git a/DBUtils/Tests/__init__.py b/Tests/__init__.py
similarity index 100%
rename from DBUtils/Tests/__init__.py
rename to Tests/__init__.py
diff --git a/DBUtils/Tests/mock_db.py b/Tests/mock_db.py
similarity index 100%
rename from DBUtils/Tests/mock_db.py
rename to Tests/mock_db.py
diff --git a/DBUtils/Tests/mock_pg.py b/Tests/mock_pg.py
similarity index 100%
rename from DBUtils/Tests/mock_pg.py
rename to Tests/mock_pg.py

From bef64b7d77f1df2f20efe5e09c5129865bfc5041 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 14:24:03 +0200
Subject: [PATCH 02/84] Rename top folders to lower case

---
 {DBUtils => dbutils}/PersistentDB.py   |   0
 {DBUtils => dbutils}/PersistentPg.py   |   0
 {DBUtils => dbutils}/PooledDB.py       |   0
 {DBUtils => dbutils}/PooledPg.py       |   0
 {DBUtils => dbutils}/Properties.py     |   0
 {DBUtils => dbutils}/SimplePooledDB.py |   0
 {DBUtils => dbutils}/SimplePooledPg.py |   0
 {DBUtils => dbutils}/SteadyDB.py       |   0
 {DBUtils => dbutils}/SteadyPg.py       |   0
 {DBUtils => dbutils}/__init__.py       |   0
 {Docs => docs}/Doc.css                 |   0
 {Docs => docs}/DocUtils.css            |   0
 {Docs => docs}/RelNotes-0.8.1.html     |   0
 {Docs => docs}/RelNotes-0.9.1.html     |   0
 {Docs => docs}/RelNotes-0.9.2.html     |   0
 {Docs => docs}/RelNotes-0.9.3.html     |   0
 {Docs => docs}/RelNotes-0.9.4.html     |   0
 {Docs => docs}/RelNotes-1.0.html       |   0
 {Docs => docs}/RelNotes-1.1.1.html     |   0
 {Docs => docs}/RelNotes-1.1.html       |   0
 {Docs => docs}/RelNotes-1.2.html       |   0
 {Docs => docs}/RelNotes-1.3.html       |   0
 {Docs => docs}/RelNotes-1.4.html       |   0
 {Docs => docs}/UsersGuide.de.html      |   0
 {Docs => docs}/UsersGuide.de.rst       |   0
 {Docs => docs}/UsersGuide.html         |   0
 {Docs => docs}/UsersGuide.rst          |   0
 {Docs => docs}/dbdep.gif               | Bin
 {Docs => docs}/persist.gif             | Bin
 {Docs => docs}/pgdep.gif               | Bin
 {Docs => docs}/pool.gif                | Bin
 {Tests => tests}/TestPersistentDB.py   |   0
 {Tests => tests}/TestPersistentPg.py   |   0
 {Tests => tests}/TestPooledDB.py       |   0
 {Tests => tests}/TestPooledPg.py       |   0
 {Tests => tests}/TestSimplePooledDB.py |   0
 {Tests => tests}/TestSimplePooledPg.py |   0
 {Tests => tests}/TestSteadyDB.py       |   0
 {Tests => tests}/TestSteadyPg.py       |   0
 {Tests => tests}/TestThreadingLocal.py |   0
 {Tests => tests}/__init__.py           |   0
 {Tests => tests}/mock_db.py            |   0
 {Tests => tests}/mock_pg.py            |   0
 43 files changed, 0 insertions(+), 0 deletions(-)
 rename {DBUtils => dbutils}/PersistentDB.py (100%)
 rename {DBUtils => dbutils}/PersistentPg.py (100%)
 rename {DBUtils => dbutils}/PooledDB.py (100%)
 rename {DBUtils => dbutils}/PooledPg.py (100%)
 rename {DBUtils => dbutils}/Properties.py (100%)
 rename {DBUtils => dbutils}/SimplePooledDB.py (100%)
 rename {DBUtils => dbutils}/SimplePooledPg.py (100%)
 rename {DBUtils => dbutils}/SteadyDB.py (100%)
 rename {DBUtils => dbutils}/SteadyPg.py (100%)
 rename {DBUtils => dbutils}/__init__.py (100%)
 rename {Docs => docs}/Doc.css (100%)
 rename {Docs => docs}/DocUtils.css (100%)
 rename {Docs => docs}/RelNotes-0.8.1.html (100%)
 rename {Docs => docs}/RelNotes-0.9.1.html (100%)
 rename {Docs => docs}/RelNotes-0.9.2.html (100%)
 rename {Docs => docs}/RelNotes-0.9.3.html (100%)
 rename {Docs => docs}/RelNotes-0.9.4.html (100%)
 rename {Docs => docs}/RelNotes-1.0.html (100%)
 rename {Docs => docs}/RelNotes-1.1.1.html (100%)
 rename {Docs => docs}/RelNotes-1.1.html (100%)
 rename {Docs => docs}/RelNotes-1.2.html (100%)
 rename {Docs => docs}/RelNotes-1.3.html (100%)
 rename {Docs => docs}/RelNotes-1.4.html (100%)
 rename {Docs => docs}/UsersGuide.de.html (100%)
 rename {Docs => docs}/UsersGuide.de.rst (100%)
 rename {Docs => docs}/UsersGuide.html (100%)
 rename {Docs => docs}/UsersGuide.rst (100%)
 rename {Docs => docs}/dbdep.gif (100%)
 rename {Docs => docs}/persist.gif (100%)
 rename {Docs => docs}/pgdep.gif (100%)
 rename {Docs => docs}/pool.gif (100%)
 rename {Tests => tests}/TestPersistentDB.py (100%)
 rename {Tests => tests}/TestPersistentPg.py (100%)
 rename {Tests => tests}/TestPooledDB.py (100%)
 rename {Tests => tests}/TestPooledPg.py (100%)
 rename {Tests => tests}/TestSimplePooledDB.py (100%)
 rename {Tests => tests}/TestSimplePooledPg.py (100%)
 rename {Tests => tests}/TestSteadyDB.py (100%)
 rename {Tests => tests}/TestSteadyPg.py (100%)
 rename {Tests => tests}/TestThreadingLocal.py (100%)
 rename {Tests => tests}/__init__.py (100%)
 rename {Tests => tests}/mock_db.py (100%)
 rename {Tests => tests}/mock_pg.py (100%)

diff --git a/DBUtils/PersistentDB.py b/dbutils/PersistentDB.py
similarity index 100%
rename from DBUtils/PersistentDB.py
rename to dbutils/PersistentDB.py
diff --git a/DBUtils/PersistentPg.py b/dbutils/PersistentPg.py
similarity index 100%
rename from DBUtils/PersistentPg.py
rename to dbutils/PersistentPg.py
diff --git a/DBUtils/PooledDB.py b/dbutils/PooledDB.py
similarity index 100%
rename from DBUtils/PooledDB.py
rename to dbutils/PooledDB.py
diff --git a/DBUtils/PooledPg.py b/dbutils/PooledPg.py
similarity index 100%
rename from DBUtils/PooledPg.py
rename to dbutils/PooledPg.py
diff --git a/DBUtils/Properties.py b/dbutils/Properties.py
similarity index 100%
rename from DBUtils/Properties.py
rename to dbutils/Properties.py
diff --git a/DBUtils/SimplePooledDB.py b/dbutils/SimplePooledDB.py
similarity index 100%
rename from DBUtils/SimplePooledDB.py
rename to dbutils/SimplePooledDB.py
diff --git a/DBUtils/SimplePooledPg.py b/dbutils/SimplePooledPg.py
similarity index 100%
rename from DBUtils/SimplePooledPg.py
rename to dbutils/SimplePooledPg.py
diff --git a/DBUtils/SteadyDB.py b/dbutils/SteadyDB.py
similarity index 100%
rename from DBUtils/SteadyDB.py
rename to dbutils/SteadyDB.py
diff --git a/DBUtils/SteadyPg.py b/dbutils/SteadyPg.py
similarity index 100%
rename from DBUtils/SteadyPg.py
rename to dbutils/SteadyPg.py
diff --git a/DBUtils/__init__.py b/dbutils/__init__.py
similarity index 100%
rename from DBUtils/__init__.py
rename to dbutils/__init__.py
diff --git a/Docs/Doc.css b/docs/Doc.css
similarity index 100%
rename from Docs/Doc.css
rename to docs/Doc.css
diff --git a/Docs/DocUtils.css b/docs/DocUtils.css
similarity index 100%
rename from Docs/DocUtils.css
rename to docs/DocUtils.css
diff --git a/Docs/RelNotes-0.8.1.html b/docs/RelNotes-0.8.1.html
similarity index 100%
rename from Docs/RelNotes-0.8.1.html
rename to docs/RelNotes-0.8.1.html
diff --git a/Docs/RelNotes-0.9.1.html b/docs/RelNotes-0.9.1.html
similarity index 100%
rename from Docs/RelNotes-0.9.1.html
rename to docs/RelNotes-0.9.1.html
diff --git a/Docs/RelNotes-0.9.2.html b/docs/RelNotes-0.9.2.html
similarity index 100%
rename from Docs/RelNotes-0.9.2.html
rename to docs/RelNotes-0.9.2.html
diff --git a/Docs/RelNotes-0.9.3.html b/docs/RelNotes-0.9.3.html
similarity index 100%
rename from Docs/RelNotes-0.9.3.html
rename to docs/RelNotes-0.9.3.html
diff --git a/Docs/RelNotes-0.9.4.html b/docs/RelNotes-0.9.4.html
similarity index 100%
rename from Docs/RelNotes-0.9.4.html
rename to docs/RelNotes-0.9.4.html
diff --git a/Docs/RelNotes-1.0.html b/docs/RelNotes-1.0.html
similarity index 100%
rename from Docs/RelNotes-1.0.html
rename to docs/RelNotes-1.0.html
diff --git a/Docs/RelNotes-1.1.1.html b/docs/RelNotes-1.1.1.html
similarity index 100%
rename from Docs/RelNotes-1.1.1.html
rename to docs/RelNotes-1.1.1.html
diff --git a/Docs/RelNotes-1.1.html b/docs/RelNotes-1.1.html
similarity index 100%
rename from Docs/RelNotes-1.1.html
rename to docs/RelNotes-1.1.html
diff --git a/Docs/RelNotes-1.2.html b/docs/RelNotes-1.2.html
similarity index 100%
rename from Docs/RelNotes-1.2.html
rename to docs/RelNotes-1.2.html
diff --git a/Docs/RelNotes-1.3.html b/docs/RelNotes-1.3.html
similarity index 100%
rename from Docs/RelNotes-1.3.html
rename to docs/RelNotes-1.3.html
diff --git a/Docs/RelNotes-1.4.html b/docs/RelNotes-1.4.html
similarity index 100%
rename from Docs/RelNotes-1.4.html
rename to docs/RelNotes-1.4.html
diff --git a/Docs/UsersGuide.de.html b/docs/UsersGuide.de.html
similarity index 100%
rename from Docs/UsersGuide.de.html
rename to docs/UsersGuide.de.html
diff --git a/Docs/UsersGuide.de.rst b/docs/UsersGuide.de.rst
similarity index 100%
rename from Docs/UsersGuide.de.rst
rename to docs/UsersGuide.de.rst
diff --git a/Docs/UsersGuide.html b/docs/UsersGuide.html
similarity index 100%
rename from Docs/UsersGuide.html
rename to docs/UsersGuide.html
diff --git a/Docs/UsersGuide.rst b/docs/UsersGuide.rst
similarity index 100%
rename from Docs/UsersGuide.rst
rename to docs/UsersGuide.rst
diff --git a/Docs/dbdep.gif b/docs/dbdep.gif
similarity index 100%
rename from Docs/dbdep.gif
rename to docs/dbdep.gif
diff --git a/Docs/persist.gif b/docs/persist.gif
similarity index 100%
rename from Docs/persist.gif
rename to docs/persist.gif
diff --git a/Docs/pgdep.gif b/docs/pgdep.gif
similarity index 100%
rename from Docs/pgdep.gif
rename to docs/pgdep.gif
diff --git a/Docs/pool.gif b/docs/pool.gif
similarity index 100%
rename from Docs/pool.gif
rename to docs/pool.gif
diff --git a/Tests/TestPersistentDB.py b/tests/TestPersistentDB.py
similarity index 100%
rename from Tests/TestPersistentDB.py
rename to tests/TestPersistentDB.py
diff --git a/Tests/TestPersistentPg.py b/tests/TestPersistentPg.py
similarity index 100%
rename from Tests/TestPersistentPg.py
rename to tests/TestPersistentPg.py
diff --git a/Tests/TestPooledDB.py b/tests/TestPooledDB.py
similarity index 100%
rename from Tests/TestPooledDB.py
rename to tests/TestPooledDB.py
diff --git a/Tests/TestPooledPg.py b/tests/TestPooledPg.py
similarity index 100%
rename from Tests/TestPooledPg.py
rename to tests/TestPooledPg.py
diff --git a/Tests/TestSimplePooledDB.py b/tests/TestSimplePooledDB.py
similarity index 100%
rename from Tests/TestSimplePooledDB.py
rename to tests/TestSimplePooledDB.py
diff --git a/Tests/TestSimplePooledPg.py b/tests/TestSimplePooledPg.py
similarity index 100%
rename from Tests/TestSimplePooledPg.py
rename to tests/TestSimplePooledPg.py
diff --git a/Tests/TestSteadyDB.py b/tests/TestSteadyDB.py
similarity index 100%
rename from Tests/TestSteadyDB.py
rename to tests/TestSteadyDB.py
diff --git a/Tests/TestSteadyPg.py b/tests/TestSteadyPg.py
similarity index 100%
rename from Tests/TestSteadyPg.py
rename to tests/TestSteadyPg.py
diff --git a/Tests/TestThreadingLocal.py b/tests/TestThreadingLocal.py
similarity index 100%
rename from Tests/TestThreadingLocal.py
rename to tests/TestThreadingLocal.py
diff --git a/Tests/__init__.py b/tests/__init__.py
similarity index 100%
rename from Tests/__init__.py
rename to tests/__init__.py
diff --git a/Tests/mock_db.py b/tests/mock_db.py
similarity index 100%
rename from Tests/mock_db.py
rename to tests/mock_db.py
diff --git a/Tests/mock_pg.py b/tests/mock_pg.py
similarity index 100%
rename from Tests/mock_pg.py
rename to tests/mock_pg.py

From f22adcec9b8eab2cf236808bd33f1220cb66526d Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 16:15:28 +0200
Subject: [PATCH 03/84] Rename UsersGuide, make Changelog from ReleaseNotes

---
 docs/RelNotes-0.8.1.html                  |  19 --
 docs/RelNotes-0.9.1.html                  |  29 ---
 docs/RelNotes-0.9.2.html                  |  26 ---
 docs/RelNotes-0.9.3.html                  |  34 ---
 docs/RelNotes-0.9.4.html                  |  26 ---
 docs/RelNotes-1.0.html                    |  49 ----
 docs/RelNotes-1.1.1.html                  |  32 ---
 docs/RelNotes-1.1.html                    |  46 ----
 docs/RelNotes-1.2.html                    |  26 ---
 docs/RelNotes-1.3.html                    |  26 ---
 docs/RelNotes-1.4.html                    |  26 ---
 docs/changelog.html                       | 270 ++++++++++++++++++++++
 docs/changelog.rst                        | 211 +++++++++++++++++
 docs/{Doc.css => doc.css}                 |   2 +-
 docs/{DocUtils.css => docutils.css}       |   0
 docs/{UsersGuide.de.html => main.de.html} |   4 +-
 docs/{UsersGuide.de.rst => main.de.rst}   |   4 +-
 docs/{UsersGuide.html => main.html}       |   4 +-
 docs/{UsersGuide.rst => main.rst}         |   4 +-
 buildhtml.py => docs/make.py              |   4 +-
 20 files changed, 492 insertions(+), 350 deletions(-)
 delete mode 100644 docs/RelNotes-0.8.1.html
 delete mode 100644 docs/RelNotes-0.9.1.html
 delete mode 100644 docs/RelNotes-0.9.2.html
 delete mode 100644 docs/RelNotes-0.9.3.html
 delete mode 100644 docs/RelNotes-0.9.4.html
 delete mode 100644 docs/RelNotes-1.0.html
 delete mode 100644 docs/RelNotes-1.1.1.html
 delete mode 100644 docs/RelNotes-1.1.html
 delete mode 100644 docs/RelNotes-1.2.html
 delete mode 100644 docs/RelNotes-1.3.html
 delete mode 100644 docs/RelNotes-1.4.html
 create mode 100644 docs/changelog.html
 create mode 100644 docs/changelog.rst
 rename docs/{Doc.css => doc.css} (99%)
 rename docs/{DocUtils.css => docutils.css} (100%)
 rename docs/{UsersGuide.de.html => main.de.html} (99%)
 rename docs/{UsersGuide.de.rst => main.de.rst} (99%)
 rename docs/{UsersGuide.html => main.html} (99%)
 rename docs/{UsersGuide.rst => main.rst} (99%)
 rename buildhtml.py => docs/make.py (92%)

diff --git a/docs/RelNotes-0.8.1.html b/docs/RelNotes-0.8.1.html
deleted file mode 100644
index fbb16f8..0000000
--- a/docs/RelNotes-0.8.1.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 0.8.1 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 0.8.1 Release Notes</h1>
-
-<p>DBUtils 0.8.1 was released on September 13, 2005.</p>
-
-<p>This is the first public release of DBUtils.</p>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-0.9.1.html b/docs/RelNotes-0.9.1.html
deleted file mode 100644
index 55b96b4..0000000
--- a/docs/RelNotes-0.9.1.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 0.9.1 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 0.9.1 Release Notes</h1>
-
-<p>DBUtils 0.9.1 was released on May 8, 2006.</p>
-
-<p>This is the second public release of DBUtils.</p>
-
-<h2>Changes:</h2>
-<ul>
-<li>Added <code>_closeable</code> attribute and made persistent connections
-not closeable by default. This allows <code>PersistentDB</code> to be used
-in the same way as you would use <code>PooledDB</code>.</li>
-<li>Allowed arguments in the DB-API 2 <code>cursor()</code> method.
-MySQLdb is using this to specify cursor classes. (Suggested by Michael Palmer.)</li>
-<li>Improved the documentation and added a User's Guide.</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-0.9.2.html b/docs/RelNotes-0.9.2.html
deleted file mode 100644
index b3e566d..0000000
--- a/docs/RelNotes-0.9.2.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 0.9.2 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 0.9.2 Release Notes</h1>
-
-<p>DBUtils 0.9.2 was released on September 22, 2006.</p>
-
-<p>This is the third public release of DBUtils.</p>
-
-<h2>Changes:</h2>
-<ul>
-<li>Renamed <code>SolidDB</code> to <code>SteadyDB</code> to avoid confusion
-with the <i>solidDB</i> storage engine.</li>
-<li>Accordingly, renamed <code>SolidPg</code> to <code>SteadyPg</code>.</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-0.9.3.html b/docs/RelNotes-0.9.3.html
deleted file mode 100644
index 72f4fb8..0000000
--- a/docs/RelNotes-0.9.3.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 0.9.3 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 0.9.3 Release Notes</h1>
-
-<p>DBUtils 0.9.3 was released on May 21, 2007.</p>
-
-<p>This is the fourth public release of DBUtils.</p>
-
-<h2>Changes:</h2>
-<ul>
-<li>Support custom creator functions for database connections.
-These can now be used as the first parameter instead of an DB-API module
-(suggested by Ezio Vernacotola).</li>
-<li>Added destructor for steady connections.</li>
-<li>Use <a href="https://pypi.python.org/pypi/setuptools">setuptools</a>
-if available.</li>
-<li>Some code cleanup.</li>
-<li>Some fixes in the documentation.
-Added <a href="UsersGuide.zh.html">Chinese translation</a>
-of the <a href="UsersGuide.html">User's Guide</a>,
-kindly contributed by <a href="http://blog.csdn.net/gashero">gashero</a>.</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-0.9.4.html b/docs/RelNotes-0.9.4.html
deleted file mode 100644
index 674a6c8..0000000
--- a/docs/RelNotes-0.9.4.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 0.9.4 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 0.9.4 Release Notes</h1>
-
-<p>DBUtils 0.9.4 was released on July 7, 2007.</p>
-
-<p>This is the fifth public release of DBUtils.</p>
-
-<p>This release fixes a problem in the destructor code and has been
-supplemented with a German User's Guide.</p>
-
-<p>Please note that the <code>dbapi</code> parameter has been renamed to
-<code>creator</code> in the last release since you can now pass custom
-creator functions for database connections instead of DB-API 2 modules.</p>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-1.0.html b/docs/RelNotes-1.0.html
deleted file mode 100644
index 2606375..0000000
--- a/docs/RelNotes-1.0.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 1.0 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 1.0 Release Notes</h1>
-
-<p>DBUtils 1.0 was released on November 29, 2008.</p>
-
-<p>This is the sixth public release of DBUtils.</p>
-
-<h2>Changes:</h2>
-<ul>
-<li>Added a <code>failures</code> parameter for configuring the exception classes for
-which the failover mechanisms is applied (as suggested by Matthew Harriger).</li>
-<li>Added a <code>closeable</code> parameter for configuring whether connections
-can be closed (otherwise closing connections will be silently ignored).</li>
-<li>It is now possible to override defaults via the <code>creator.dbapi</code>
-and <code>creator.threadsafety</code> attributes.</li>
-<li>Added alias method <code>dedicated_connection</code> for
-<code>connection(shareable=False)</code>.</li>
-<li>Added a <code>version</code> attribute to all exported classes.</li>
-<li>Where <code>0</code> has the meaning "unlimited", parameters can now be also
-set to <code>None</code> instead.</li>
-<li>It turned out that <code>threading.local</code> does not work properly with
-<code>mod_wsgi</code>, so we use the Python implementation for thread-local data
-even when a faster <code>threading.local</code> implementation is available.
-A new parameter <code>threadlocal</code> allows you to pass an arbitrary class
-such as <code>threading.local</code> if you know it works in your environment.</li>
-</ul>
-
-<h2>Bugfixes and Improvements:</h2>
-<ul>
-<li>In some cases, when instance initialization failed or referenced objects
-were already destroyed, finalizers could throw exceptions or create infinite
-recursion (problem reported by Gregory Pinero and Jehiah Czebotar).</li>
-<li>DBUtils now tries harder to find the underlying DB-API 2 module if only a
-connection creator function is specified. This had not worked before with the
-MySQLdb module (problem reported by Gregory Pinero).</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-1.1.1.html b/docs/RelNotes-1.1.1.html
deleted file mode 100644
index b780067..0000000
--- a/docs/RelNotes-1.1.1.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 1.1.1 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 1.1.1 Release Notes</h1>
-
-<p>DBUtils 1.1.1 was released on 02/04/17.</p>
-
-<p>This bugfix release is the eight public release of DBUtils.</p>
-
-<p>It is intended to be used with Python versions 2.3 to 2.7</p>
-
-<h2>Improvements:</h2>
-<ul>
-<li>Reopen <code>SteadyDB</code> connections when commit or rollback fails
-(suggested by Ben Hoyt).</li>
-</ul>
-
-<h2>Bugfixes:</h2>
-<ul>
-<li>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-1.1.html b/docs/RelNotes-1.1.html
deleted file mode 100644
index 6cdb156..0000000
--- a/docs/RelNotes-1.1.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 1.1 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 1.1 Release Notes</h1>
-
-<p>DBUtils 1.1 was released on August 14, 2011.</p>
-
-<p>This is the seventh public release of DBUtils.</p>
-
-<h2>Improvements:</h2>
-<ul>
-<li>The transparent reopening of connections is actually an undesired behavior
-if it happens during database transactions. In these cases, the transaction
-should fail and the error be reported back to the application instead of the
-rest of the transaction being executed in a new connection and therefore in
-a new transaction. Therefore DBUtils now allows suspending the transparent
-reopening during transactions. All you need to do is indicate the beginning
-of a transaction by calling the <code>begin()</code> method of the connection.
-DBUtils makes sure that this method always exists, even if the database driver
-does not support it.</li>
-<li>If the database driver supports a <code>ping()</code> method, then DBUtils
-can use it to check whether connections are alive instead of just trying
-to use the connection and reestablishing it in case it was dead. Since these
-checks are done at the expense of some performance, you have exact control
-when these are executed via the new <code>ping</code> parameter.</li>
-<li><code>PooledDB</code> has got another new parameter <code>reset</code> for
-controlling how connections are reset before being put back into the pool.</li>
-</ul>
-
-<h2>Bugfixes:</h2>
-<ul>
-<li>Fixed propagation of error messages when the connection was lost.</li>
-<li>Fixed an issue with the <code>setoutputsize()</code> cursor method.</li>
-<li>Fixed some minor issues with the <code>DBUtilsExample</code> for Webware.</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-1.2.html b/docs/RelNotes-1.2.html
deleted file mode 100644
index a90eae2..0000000
--- a/docs/RelNotes-1.2.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 1.2 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 1.2 Release Notes</h1>
-
-<p>DBUtils 1.2 was released on 02/05/17.</p>
-
-<p>This is the ninth public release of DBUtils.</p>
-
-<p>It is intended to be used with Python versions 2.6 and newer.</p>
-
-<h2>Improvements:</h2>
-<ul>
-<li>Python 3 is now supported.</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-1.3.html b/docs/RelNotes-1.3.html
deleted file mode 100644
index 05f8614..0000000
--- a/docs/RelNotes-1.3.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 1.3 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 1.3 Release Notes</h1>
-
-<p>DBUtils 1.3 was released on 03/08/18.</p>
-
-<p>This is the tenth public release of DBUtils.</p>
-
-<p>It is intended to be used with Python versions 2.6, 2.7 or 3.4 - 3.7.</p>
-
-<h2>Improvements:</h2>
-<ul>
-<li>Supports context handlers for connections and cursors.</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/RelNotes-1.4.html b/docs/RelNotes-1.4.html
deleted file mode 100644
index 0565fd0..0000000
--- a/docs/RelNotes-1.4.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>DBUtils 1.4 Release Notes</title>
-<link rel="stylesheet" href="Doc.css" type="text/css">
-</head>
-<body>
-<h1 class="header">DBUtils 1.4 Release Notes</h1>
-
-<p>DBUtils 1.4 was released on 09/26/20.</p>
-
-<p>This version is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
-
-<h2>Improvements:</h2>
-<ul>
-<li>The <code>SteadyDB</code> and <code>SteadyPg</code> classes only reconnect
-after the <code>maxusage</code> limit has been reached when the connection is
-not currently inside a transaction.</li>
-</ul>
-
-<div class="footer">
-DBUtils
-(<a href="https://github.com/WebwareForPython/DBUtils">github.com/WebwareForPython/DBUtils</a>)
-</div>
-</body>
-</html>
diff --git a/docs/changelog.html b/docs/changelog.html
new file mode 100644
index 0000000..1ecb86c
--- /dev/null
+++ b/docs/changelog.html
@@ -0,0 +1,270 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta charset="utf-8"/>
+<meta name="generator" content="Docutils 0.15.2: http://docutils.sourceforge.net/" />
+<title>Changelog for DBUtils</title>
+<link rel="stylesheet" href="Doc.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="changelog-for-dbutils">
+<h1 class="title">Changelog for DBUtils</h1>
+
+<div class="contents topic" id="contents">
+<p class="topic-title first">Contents</p>
+<ul class="simple">
+<li><p><a class="reference internal" href="#id1" id="id21">2.0</a></p>
+<ul>
+<li><p><a class="reference internal" href="#changes" id="id22">Changes:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id2" id="id23">1.4</a></p>
+<ul>
+<li><p><a class="reference internal" href="#improvements" id="id24">Improvements:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id3" id="id25">1.3</a></p>
+<ul>
+<li><p><a class="reference internal" href="#id4" id="id26">Improvements:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id5" id="id27">1.2</a></p></li>
+<li><p><a class="reference internal" href="#id6" id="id28">1.1.1</a></p>
+<ul>
+<li><p><a class="reference internal" href="#id7" id="id29">Improvements:</a></p></li>
+<li><p><a class="reference internal" href="#bugfixes" id="id30">Bugfixes:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id8" id="id31">1.1</a></p>
+<ul>
+<li><p><a class="reference internal" href="#id9" id="id32">Improvements:</a></p></li>
+<li><p><a class="reference internal" href="#id10" id="id33">Bugfixes:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id11" id="id34">1.0</a></p>
+<ul>
+<li><p><a class="reference internal" href="#id12" id="id35">Changes:</a></p></li>
+<li><p><a class="reference internal" href="#bugfixes-and-improvements" id="id36">Bugfixes and Improvements:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id13" id="id37">0.9.4</a></p></li>
+<li><p><a class="reference internal" href="#id14" id="id38">0.9.3</a></p>
+<ul>
+<li><p><a class="reference internal" href="#id15" id="id39">Changes:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id16" id="id40">0.9.2</a></p>
+<ul>
+<li><p><a class="reference internal" href="#id17" id="id41">Changes:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id18" id="id42">0.9.1</a></p>
+<ul>
+<li><p><a class="reference internal" href="#id19" id="id43">Changes:</a></p></li>
+</ul>
+</li>
+<li><p><a class="reference internal" href="#id20" id="id44">0.8.1 - 2005-09-13</a></p></li>
+</ul>
+</div>
+<div class="section" id="id1">
+<h1>2.0</h1>
+<p>DBUtils 2.0 was released on September 26, 2020.</p>
+<p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
+<div class="section" id="changes">
+<h2>Changes:</h2>
+<ul class="simple">
+<li><p>DBUtils does not act as a Webware plugin anymore, it is now just an ordinary
+Python package (of course it could be used as such also before).</p></li>
+<li><p>The top-level packages and folders have been renamed to lower-case.
+Particularly, you need to import <span class="docutils literal">dbutils</span> instead of <span class="docutils literal">DBUtils</span> now.</p></li>
+<li><p>The Webware <span class="docutils literal">Examples</span> folder has been removed.</p></li>
+<li><p>This changelog file has been created from the former release notes.</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id2">
+<h1>1.4</h1>
+<p>DBUtils 1.4 was released on August 26, 2020.</p>
+<p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
+<div class="section" id="improvements">
+<h2>Improvements:</h2>
+<ul class="simple">
+<li><p>The <span class="docutils literal">SteadyDB</span> and <span class="docutils literal">SteadyPg</span> classes only reconnect after the
+<span class="docutils literal">maxusage</span> limit has been reached when the connection is not currently
+inside a transaction.</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id3">
+<h1>1.3</h1>
+<p>DBUtils 1.3 was released on March 3, 2018.</p>
+<p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
+<div class="section" id="id4">
+<h2>Improvements:</h2>
+<ul class="simple">
+<li><p>This version now supports context handlers for connections and cursors.</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id5">
+<h1>1.2</h1>
+<p>DBUtils 1.2 was released on February 5, 2017.</p>
+<p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
+</div>
+<div class="section" id="id6">
+<h1>1.1.1</h1>
+<p>DBUtils 1.1.1 was released on February 4, 2017.</p>
+<p>It is intended to be used with Python versions 2.3 to 2.7.</p>
+<div class="section" id="id7">
+<h2>Improvements:</h2>
+<ul class="simple">
+<li><p>Reopen <span class="docutils literal">SteadyDB</span> connections when commit or rollback fails
+(suggested by Ben Hoyt).</p></li>
+</ul>
+</div>
+<div class="section" id="bugfixes">
+<h2>Bugfixes:</h2>
+<ul class="simple">
+<li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id8">
+<h1>1.1</h1>
+<p>DBUtils 1.1 was released on August 14, 2011.</p>
+<div class="section" id="id9">
+<h2>Improvements:</h2>
+<ul class="simple">
+<li><p>The transparent reopening of connections is actually an undesired behavior
+if it happens during database transactions. In these cases, the transaction
+should fail and the error be reported back to the application instead of the
+rest of the transaction being executed in a new connection and therefore in
+a new transaction. Therefore DBUtils now allows suspending the transparent
+reopening during transactions. All you need to do is indicate the beginning
+of a transaction by calling the <span class="docutils literal">begin()</span> method of the connection.
+DBUtils makes sure that this method always exists, even if the database
+driver does not support it.</p></li>
+<li><p>If the database driver supports a <span class="docutils literal">ping()</span> method, then DBUtils can use it
+to check whether connections are alive instead of just trying to use the
+connection and reestablishing it in case it was dead. Since these checks are
+done at the expense of some performance, you have exact control when these
+are executed via the new <span class="docutils literal">ping</span> parameter.</p></li>
+<li><p><span class="docutils literal">PooledDB</span> has got another new parameter <span class="docutils literal">reset</span> for controlling how
+connections are reset before being put back into the pool.</p></li>
+</ul>
+</div>
+<div class="section" id="id10">
+<h2>Bugfixes:</h2>
+<div class="system-message">
+<p class="system-message-title">System Message: WARNING/2 (<span class="docutils literal">changelog.rst</span>, line 100)</p>
+<p>Title underline too short.</p>
+<pre class="literal-block">Bugfixes:
+--------</pre>
+</div>
+<ul class="simple">
+<li><p>Fixed propagation of error messages when the connection was lost.</p></li>
+<li><p>Fixed an issue with the <span class="docutils literal">setoutputsize()</span>  cursor method.</p></li>
+<li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id11">
+<h1>1.0</h1>
+<p>DBUtils 1.0 was released on November 29, 2008.</p>
+<p>It is intended to be used with Python versions 2.2 to 2.6.</p>
+<div class="section" id="id12">
+<h2>Changes:</h2>
+<ul class="simple">
+<li><p>Added a <span class="docutils literal">failures</span> parameter for configuring the exception classes for
+which the failover mechanisms is applied (as suggested by Matthew Harriger).</p></li>
+<li><p>Added a <span class="docutils literal">closeable</span> parameter for configuring whether connections can be
+closed (otherwise closing connections will be silently ignored).</p></li>
+<li><p>It is now possible to override defaults via the <span class="docutils literal">creator.dbapi</span> and
+<span class="docutils literal">creator.threadsafety</span> attributes.</p></li>
+<li><p>Added an alias method <span class="docutils literal">dedicated_connection</span> as a shorthand for
+<span class="docutils literal">connection(shareable=False)</span>.</p></li>
+<li><p>Added a version attribute to all exported classes.</p></li>
+<li><p>Where the value <span class="docutils literal">0</span> has the meaning &quot;unlimited&quot;, parameters can now be also
+set to the value <span class="docutils literal">None</span> instead.</p></li>
+<li><p>It turned out that <span class="docutils literal">threading.local</span> does not work properly with
+<span class="docutils literal">mod_wsgi</span>, so we use the Python implementation for thread-local data
+even when a faster <span class="docutils literal">threading.local</span> implementation is available.
+A new parameter <span class="docutils literal">threadlocal</span> allows you to pass an arbitrary class
+such as <span class="docutils literal">threading.local</span> if you know it works in your environment.</p></li>
+</ul>
+</div>
+<div class="section" id="bugfixes-and-improvements">
+<h2>Bugfixes and Improvements:</h2>
+<ul class="simple">
+<li><p>In some cases, when instance initialization failed or referenced objects
+were already destroyed, finalizers could throw exceptions or create infinite
+recursion (problem reported by Gregory Pinero and Jehiah Czebotar).</p></li>
+<li><p>DBUtils now tries harder to find the underlying DB-API 2 module if only a
+connection creator function is specified. This had not worked before with
+the MySQLdb module (problem reported by Gregory Pinero).</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id13">
+<h1>0.9.4</h1>
+<p>DBUtils 0.9.4 was released on July 7, 2007.</p>
+<p>This release fixes a problem in the destructor code and has been supplemented
+with a German User's Guide.</p>
+<p>Again, please note that the <span class="docutils literal">dbapi</span> parameter has been renamed to <span class="docutils literal">creator</span>
+in the last release, since you can now pass custom creator functions
+for database connections instead of DB-API 2 modules.</p>
+</div>
+<div class="section" id="id14">
+<h1>0.9.3</h1>
+<p>DBUtils 0.9.3 was released on May 21, 2007.</p>
+<div class="section" id="id15">
+<h2>Changes:</h2>
+<ul class="simple">
+<li><p>Support custom creator functions for database connections.
+These can now be used as the first parameter instead of an DB-API module
+(suggested by Ezio Vernacotola).</p></li>
+<li><p>Added destructor for steady connections.</p></li>
+<li><p>Use <a class="reference external" href="https://github.com/pypa/setuptools">setuptools</a> if available.</p></li>
+<li><p>Some code cleanup.</p></li>
+<li><p>Some fixes in the documentation.
+Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id16">
+<h1>0.9.2</h1>
+<p>DBUtils 0.9.2 was released on September 22, 2006.</p>
+<p>It is intended to be used with Python versions 2.2 to 2.5.</p>
+<div class="section" id="id17">
+<h2>Changes:</h2>
+<ul class="simple">
+<li><p>Renamed <span class="docutils literal">SolidDB</span> to <span class="docutils literal">SteadyDB</span> to avoid confusion with the &quot;solidDB&quot;
+storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id18">
+<h1>0.9.1</h1>
+<p>DBUtils 0.9.1 was released on May 8, 2006.</p>
+<p>It is intended to be used with Python versions 2.2 to 2.4.</p>
+<div class="section" id="id19">
+<h2>Changes:</h2>
+<ul class="simple">
+<li><p>Added <span class="docutils literal">_closeable</span> attribute and made persistent connections not closeable
+by default. This allows <span class="docutils literal">PersistentDB</span>  to be used in the same way as you
+would use <span class="docutils literal">PooledDB</span>.</p></li>
+<li><p>Allowed arguments in the DB-API 2 <span class="docutils literal">cursor()</span> method. MySQLdb is using this
+to specify cursor classes. (Suggested by Michael Palmer.)</p></li>
+<li><p>Improved the documentation and added a User's Guide.</p></li>
+</ul>
+</div>
+</div>
+<div class="section" id="id20">
+<h1>0.8.1 - 2005-09-13</h1>
+<p>DBUtils 0.8.1 was released on September 13, 2005.</p>
+<p>It is intended to be used with Python versions 2.0 to 2.4.</p>
+<p>This is the first public release of DBUtils.</p>
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644
index 0000000..fb97129
--- /dev/null
+++ b/docs/changelog.rst
@@ -0,0 +1,211 @@
+Changelog for DBUtils
++++++++++++++++++++++
+
+.. contents:: Contents
+
+2.0
+===
+
+DBUtils 2.0 was released on September 26, 2020.
+
+It is intended to be used with Python versions 2.7 and 3.5 to 3.9.
+
+Changes:
+--------
+
+* DBUtils does not act as a Webware plugin anymore, it is now just an ordinary
+  Python package (of course it could be used as such also before).
+* The top-level packages and folders have been renamed to lower-case.
+  Particularly, you need to import ``dbutils`` instead of ``DBUtils`` now.
+* The Webware ``Examples`` folder has been removed.
+* This changelog file has been created from the former release notes.
+
+1.4
+===
+
+DBUtils 1.4 was released on August 26, 2020.
+
+It is intended to be used with Python versions 2.7 and 3.5 to 3.9.
+
+Improvements:
+-------------
+
+* The ``SteadyDB`` and ``SteadyPg`` classes only reconnect after the
+  ``maxusage`` limit has been reached when the connection is not currently
+  inside a transaction.
+
+1.3
+===
+
+DBUtils 1.3 was released on March 3, 2018.
+
+It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.
+
+Improvements:
+-------------
+
+* This version now supports context handlers for connections and cursors.
+
+1.2
+===
+
+DBUtils 1.2 was released on February 5, 2017.
+
+It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.
+
+1.1.1
+=====
+
+DBUtils 1.1.1 was released on February 4, 2017.
+
+It is intended to be used with Python versions 2.3 to 2.7.
+
+Improvements:
+-------------
+
+* Reopen ``SteadyDB`` connections when commit or rollback fails
+  (suggested by Ben Hoyt).
+
+Bugfixes:
+---------
+
+* Fixed a problem when running under Jython (reported by Vitaly Kruglikov).
+
+1.1
+===
+
+DBUtils 1.1 was released on August 14, 2011.
+
+Improvements:
+-------------
+
+* The transparent reopening of connections is actually an undesired behavior
+  if it happens during database transactions. In these cases, the transaction
+  should fail and the error be reported back to the application instead of the
+  rest of the transaction being executed in a new connection and therefore in
+  a new transaction. Therefore DBUtils now allows suspending the transparent
+  reopening during transactions. All you need to do is indicate the beginning
+  of a transaction by calling the ``begin()`` method of the connection.
+  DBUtils makes sure that this method always exists, even if the database
+  driver does not support it.
+* If the database driver supports a ``ping()`` method, then DBUtils can use it
+  to check whether connections are alive instead of just trying to use the
+  connection and reestablishing it in case it was dead. Since these checks are
+  done at the expense of some performance, you have exact control when these
+  are executed via the new ``ping`` parameter.
+* ``PooledDB`` has got another new parameter ``reset`` for controlling how
+  connections are reset before being put back into the pool.
+
+Bugfixes:
+--------
+
+* Fixed propagation of error messages when the connection was lost.
+* Fixed an issue with the ``setoutputsize()``  cursor method.
+* Fixed some minor issues with the ``DBUtilsExample`` for Webware.
+
+
+1.0
+===
+
+DBUtils 1.0 was released on November 29, 2008.
+
+It is intended to be used with Python versions 2.2 to 2.6.
+
+Changes:
+--------
+
+* Added a ``failures`` parameter for configuring the exception classes for
+  which the failover mechanisms is applied (as suggested by Matthew Harriger).
+* Added a ``closeable`` parameter for configuring whether connections can be
+  closed (otherwise closing connections will be silently ignored).
+* It is now possible to override defaults via the ``creator.dbapi`` and
+  ``creator.threadsafety`` attributes.
+* Added an alias method ``dedicated_connection`` as a shorthand for
+  ``connection(shareable=False)``.
+* Added a version attribute to all exported classes.
+* Where the value ``0`` has the meaning "unlimited", parameters can now be also
+  set to the value ``None`` instead.
+* It turned out that ``threading.local`` does not work properly with
+  ``mod_wsgi``, so we use the Python implementation for thread-local data
+  even when a faster ``threading.local`` implementation is available.
+  A new parameter ``threadlocal`` allows you to pass an arbitrary class
+  such as ``threading.local`` if you know it works in your environment.
+
+Bugfixes and Improvements:
+--------------------------
+
+* In some cases, when instance initialization failed or referenced objects
+  were already destroyed, finalizers could throw exceptions or create infinite
+  recursion (problem reported by Gregory Pinero and Jehiah Czebotar).
+* DBUtils now tries harder to find the underlying DB-API 2 module if only a
+  connection creator function is specified. This had not worked before with
+  the MySQLdb module (problem reported by Gregory Pinero).
+
+0.9.4
+=====
+
+DBUtils 0.9.4 was released on July 7, 2007.
+
+This release fixes a problem in the destructor code and has been supplemented
+with a German User's Guide.
+
+Again, please note that the ``dbapi`` parameter has been renamed to ``creator``
+in the last release, since you can now pass custom creator functions
+for database connections instead of DB-API 2 modules.
+
+0.9.3
+=====
+
+DBUtils 0.9.3 was released on May 21, 2007.
+
+Changes:
+--------
+
+* Support custom creator functions for database connections.
+  These can now be used as the first parameter instead of an DB-API module
+  (suggested by Ezio Vernacotola).
+* Added destructor for steady connections.
+* Use setuptools_ if available.
+* Some code cleanup.
+* Some fixes in the documentation.
+  Added Chinese translation of the User's Guide, kindly contributed by gashero.
+
+.. _setuptools: https://github.com/pypa/setuptools
+
+0.9.2
+=====
+
+DBUtils 0.9.2 was released on September 22, 2006.
+
+It is intended to be used with Python versions 2.2 to 2.5.
+
+Changes:
+--------
+
+* Renamed ``SolidDB`` to ``SteadyDB`` to avoid confusion with the "solidDB"
+  storage engine. Accordingly, renamed ``SolidPg`` to ``SteadyPg``.
+
+0.9.1
+=====
+
+DBUtils 0.9.1 was released on May 8, 2006.
+
+It is intended to be used with Python versions 2.2 to 2.4.
+
+Changes:
+--------
+* Added ``_closeable`` attribute and made persistent connections not closeable
+  by default. This allows ``PersistentDB``  to be used in the same way as you
+  would use ``PooledDB``.
+* Allowed arguments in the DB-API 2 ``cursor()`` method. MySQLdb is using this
+  to specify cursor classes. (Suggested by Michael Palmer.)
+* Improved the documentation and added a User's Guide.
+
+0.8.1 - 2005-09-13
+==================
+
+DBUtils 0.8.1 was released on September 13, 2005.
+
+It is intended to be used with Python versions 2.0 to 2.4.
+
+This is the first public release of DBUtils.
diff --git a/docs/Doc.css b/docs/doc.css
similarity index 99%
rename from docs/Doc.css
rename to docs/doc.css
index 391e17c..c815371 100644
--- a/docs/Doc.css
+++ b/docs/doc.css
@@ -6,7 +6,7 @@
 
 /* First import default style for pages created with Docutils: */
 
-@import url(DocUtils.css);
+@import url(docutils.css);
 
 /* Customization for Webware goes here: */
 
diff --git a/docs/DocUtils.css b/docs/docutils.css
similarity index 100%
rename from docs/DocUtils.css
rename to docs/docutils.css
diff --git a/docs/UsersGuide.de.html b/docs/main.de.html
similarity index 99%
rename from docs/UsersGuide.de.html
rename to docs/main.de.html
index 948e12a..10a3eef 100644
--- a/docs/UsersGuide.de.html
+++ b/docs/main.de.html
@@ -11,12 +11,12 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
-<dd class="version">1.4</dd>
+<dd class="version">2.0</dd>
 <dt class="released">Released</dt>
 <dd class="released"><p>09/26/20</p>
 </dd>
 <dt class="translations">Translations</dt>
-<dd class="translations"><p><a class="reference external" href="UsersGuide.html">English</a> | German</p>
+<dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
 </dl>
 <div class="contents topic" id="inhalt">
diff --git a/docs/UsersGuide.de.rst b/docs/main.de.rst
similarity index 99%
rename from docs/UsersGuide.de.rst
rename to docs/main.de.rst
index ee59e63..00013ac 100644
--- a/docs/UsersGuide.de.rst
+++ b/docs/main.de.rst
@@ -1,11 +1,11 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 1.4
+:Version: 2.0
 :Released: 09/26/20
 :Translations: English_ | German
 
-.. _English: UsersGuide.html
+.. _English: main.html
 
 .. contents:: Inhalt
 
diff --git a/docs/UsersGuide.html b/docs/main.html
similarity index 99%
rename from docs/UsersGuide.html
rename to docs/main.html
index 2ec476e..9ec6f26 100644
--- a/docs/UsersGuide.html
+++ b/docs/main.html
@@ -11,12 +11,12 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
-<dd class="version">1.4</dd>
+<dd class="version">2.0</dd>
 <dt class="released">Released</dt>
 <dd class="released"><p>09/26/20</p>
 </dd>
 <dt class="translations">Translations</dt>
-<dd class="translations"><p>English | <a class="reference external" href="UsersGuide.de.html">German</a></p>
+<dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
 </dl>
 <div class="contents topic" id="contents">
diff --git a/docs/UsersGuide.rst b/docs/main.rst
similarity index 99%
rename from docs/UsersGuide.rst
rename to docs/main.rst
index 5c17298..edbfd31 100644
--- a/docs/UsersGuide.rst
+++ b/docs/main.rst
@@ -1,11 +1,11 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 1.4
+:Version: 2.0
 :Released: 09/26/20
 :Translations: English | German_
 
-.. _German: UsersGuide.de.html
+.. _German: main.de.html
 
 .. contents:: Contents
 
diff --git a/buildhtml.py b/docs/make.py
similarity index 92%
rename from buildhtml.py
rename to docs/make.py
index 420a64c..fb82e88 100755
--- a/buildhtml.py
+++ b/docs/make.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3
+#!/usr/bin/python3.8
 
 """Build HMTL from reST files."""
 
@@ -10,7 +10,7 @@
 
 print("Creating the documentation...")
 
-for rst_file in glob(join('DBUtils', 'Docs', '*.rst')):
+for rst_file in glob('*.rst'):
     name = splitext(rst_file)[0]
     lang = splitext(name)[1]
     if lang.startswith('.'):

From 5b9fe81108f87fdfa689332b7183c4d8dfb2b721 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 16:29:26 +0200
Subject: [PATCH 04/84] Rename all modules to lower-case

Also remove Properties.py since this is no longer a Webware plug-in.
---
 MANIFEST.in                                   | 12 ++++++++++--
 dbutils/Properties.py                         | 19 -------------------
 dbutils/{PersistentDB.py => persistent_db.py} |  0
 dbutils/{PersistentPg.py => persistent_pg.py} |  0
 dbutils/{PooledDB.py => pooled_db.py}         |  0
 dbutils/{PooledPg.py => pooled_pg.py}         |  0
 ...{SimplePooledDB.py => simple_pooled_db.py} |  0
 ...{SimplePooledPg.py => simple_pooled_pg.py} |  0
 dbutils/{SteadyDB.py => steady_db.py}         |  0
 dbutils/{SteadyPg.py => steady_pg.py}         |  0
 Release.md => release.md                      |  0
 setup.py                                      |  4 ++--
 ...tPersistentDB.py => test_persistend_db.py} |  0
 ...tPersistentPg.py => test_persistend_pg.py} |  0
 tests/{TestPooledDB.py => test_pooled_db.py}  |  0
 tests/{TestPooledPg.py => test_pooled_pg.py}  |  0
 ...lePooledDB.py => test_simple_pooled_db.py} |  0
 ...lePooledPg.py => test_simple_pooled_pg.py} |  0
 tests/{TestSteadyDB.py => test_steady_db.py}  |  0
 tests/{TestSteadyPg.py => test_steady_pg.py}  |  0
 ...eadingLocal.py => test_threading_local.py} |  0
 tox.ini                                       |  7 ++-----
 22 files changed, 14 insertions(+), 28 deletions(-)
 delete mode 100644 dbutils/Properties.py
 rename dbutils/{PersistentDB.py => persistent_db.py} (100%)
 rename dbutils/{PersistentPg.py => persistent_pg.py} (100%)
 rename dbutils/{PooledDB.py => pooled_db.py} (100%)
 rename dbutils/{PooledPg.py => pooled_pg.py} (100%)
 rename dbutils/{SimplePooledDB.py => simple_pooled_db.py} (100%)
 rename dbutils/{SimplePooledPg.py => simple_pooled_pg.py} (100%)
 rename dbutils/{SteadyDB.py => steady_db.py} (100%)
 rename dbutils/{SteadyPg.py => steady_pg.py} (100%)
 rename Release.md => release.md (100%)
 rename tests/{TestPersistentDB.py => test_persistend_db.py} (100%)
 rename tests/{TestPersistentPg.py => test_persistend_pg.py} (100%)
 rename tests/{TestPooledDB.py => test_pooled_db.py} (100%)
 rename tests/{TestPooledPg.py => test_pooled_pg.py} (100%)
 rename tests/{TestSimplePooledDB.py => test_simple_pooled_db.py} (100%)
 rename tests/{TestSimplePooledPg.py => test_simple_pooled_pg.py} (100%)
 rename tests/{TestSteadyDB.py => test_steady_db.py} (100%)
 rename tests/{TestSteadyPg.py => test_steady_pg.py} (100%)
 rename tests/{TestThreadingLocal.py => test_threading_local.py} (100%)

diff --git a/MANIFEST.in b/MANIFEST.in
index 14ffe38..b4efc19 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,11 @@
-exclude Release.md setversion.py
+include MANIFEST.in
+
+include LICENSE
 include README.md
-include DBUtils/Docs/*
+
+recursive-include docs *.rst make.py *.html *.css *.png *.gif
+
+prune docs/_build
+exclude release.md setversion.py
+
+global-exclude *.py[co] __pycache__
diff --git a/dbutils/Properties.py b/dbutils/Properties.py
deleted file mode 100644
index da7426e..0000000
--- a/dbutils/Properties.py
+++ /dev/null
@@ -1,19 +0,0 @@
-name = 'DBUtils'
-
-version = (1, 4, 0)
-
-docs = [
-    {'name': "User's Guide", 'file': 'UsersGuide.html'},
-]
-
-status = 'beta'
-
-requiredPyVersion = (2, 7, 0)
-
-synopsis = (
-    "DBUtils provides database related support classes and functions"
-    " to Webware. There is plenty of useful reusable code here.")
-
-WebKitConfig = {
-    'examplePages': ['DBUtilsExample']
-}
diff --git a/dbutils/PersistentDB.py b/dbutils/persistent_db.py
similarity index 100%
rename from dbutils/PersistentDB.py
rename to dbutils/persistent_db.py
diff --git a/dbutils/PersistentPg.py b/dbutils/persistent_pg.py
similarity index 100%
rename from dbutils/PersistentPg.py
rename to dbutils/persistent_pg.py
diff --git a/dbutils/PooledDB.py b/dbutils/pooled_db.py
similarity index 100%
rename from dbutils/PooledDB.py
rename to dbutils/pooled_db.py
diff --git a/dbutils/PooledPg.py b/dbutils/pooled_pg.py
similarity index 100%
rename from dbutils/PooledPg.py
rename to dbutils/pooled_pg.py
diff --git a/dbutils/SimplePooledDB.py b/dbutils/simple_pooled_db.py
similarity index 100%
rename from dbutils/SimplePooledDB.py
rename to dbutils/simple_pooled_db.py
diff --git a/dbutils/SimplePooledPg.py b/dbutils/simple_pooled_pg.py
similarity index 100%
rename from dbutils/SimplePooledPg.py
rename to dbutils/simple_pooled_pg.py
diff --git a/dbutils/SteadyDB.py b/dbutils/steady_db.py
similarity index 100%
rename from dbutils/SteadyDB.py
rename to dbutils/steady_db.py
diff --git a/dbutils/SteadyPg.py b/dbutils/steady_pg.py
similarity index 100%
rename from dbutils/SteadyPg.py
rename to dbutils/steady_pg.py
diff --git a/Release.md b/release.md
similarity index 100%
rename from Release.md
rename to release.md
diff --git a/setup.py b/setup.py
index 7e97f95..478fe8e 100755
--- a/setup.py
+++ b/setup.py
@@ -43,6 +43,6 @@
     url='https://webwareforpython.github.io/DBUtils/',
     platforms=['any'],
     license='MIT License',
-    packages=['DBUtils', 'DBUtils.Examples', 'DBUtils.Tests'],
-    package_data={'DBUtils': ['Docs/*']}
+    packages=['dbutils'],
+    zip_safe=False,
 )
diff --git a/tests/TestPersistentDB.py b/tests/test_persistend_db.py
similarity index 100%
rename from tests/TestPersistentDB.py
rename to tests/test_persistend_db.py
diff --git a/tests/TestPersistentPg.py b/tests/test_persistend_pg.py
similarity index 100%
rename from tests/TestPersistentPg.py
rename to tests/test_persistend_pg.py
diff --git a/tests/TestPooledDB.py b/tests/test_pooled_db.py
similarity index 100%
rename from tests/TestPooledDB.py
rename to tests/test_pooled_db.py
diff --git a/tests/TestPooledPg.py b/tests/test_pooled_pg.py
similarity index 100%
rename from tests/TestPooledPg.py
rename to tests/test_pooled_pg.py
diff --git a/tests/TestSimplePooledDB.py b/tests/test_simple_pooled_db.py
similarity index 100%
rename from tests/TestSimplePooledDB.py
rename to tests/test_simple_pooled_db.py
diff --git a/tests/TestSimplePooledPg.py b/tests/test_simple_pooled_pg.py
similarity index 100%
rename from tests/TestSimplePooledPg.py
rename to tests/test_simple_pooled_pg.py
diff --git a/tests/TestSteadyDB.py b/tests/test_steady_db.py
similarity index 100%
rename from tests/TestSteadyDB.py
rename to tests/test_steady_db.py
diff --git a/tests/TestSteadyPg.py b/tests/test_steady_pg.py
similarity index 100%
rename from tests/TestSteadyPg.py
rename to tests/test_steady_pg.py
diff --git a/tests/TestThreadingLocal.py b/tests/test_threading_local.py
similarity index 100%
rename from tests/TestThreadingLocal.py
rename to tests/test_threading_local.py
diff --git a/tox.ini b/tox.ini
index cfc48a4..78dd8dc 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,6 @@
 [tox]
 envlist = py{27,35,36,37,38,39}, flake8
 
-[pytest]
-python_files=Test*.py
-
 [testenv]
 setenv =
     PYTHONPATH = {toxinidir}
@@ -14,8 +11,8 @@ commands =
 
 [testenv:flake8]
 basepython =
-    python
+    python3.8
 deps =
     flake8
 commands =
-    flake8 DBUtils
+    flake8 *.py dbutils

From 1513377fd16de472325bd83cdd0216007e69bcf3 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 18:01:47 +0200
Subject: [PATCH 05/84] Use the new lower-case module names everywhere

Also use the bump2version tool instead of setversion.py.
---
 .bumpversion.cfg                              |  22 ++++
 README.md                                     |   2 +-
 dbutils/__init__.py                           |  14 +-
 dbutils/persistent_db.py                      |   7 +-
 dbutils/persistent_pg.py                      |   7 +-
 dbutils/pooled_db.py                          |   7 +-
 dbutils/pooled_pg.py                          |   7 +-
 dbutils/simple_pooled_db.py                   |   4 +-
 dbutils/simple_pooled_pg.py                   |   4 +-
 dbutils/steady_db.py                          |   4 +-
 dbutils/steady_pg.py                          |   4 +-
 docs/changelog.html                           |   7 +-
 docs/changelog.rst                            |   3 +-
 docs/main.de.html                             |   3 -
 docs/main.de.rst                              |   1 -
 docs/main.html                                |   3 -
 docs/main.rst                                 |   1 -
 docs/make.py                                  |  16 +--
 setup.py                                      |   2 +-
 setversion.py                                 | 123 ------------------
 tests/__init__.py                             |   2 +-
 tests/mock_pg.py                              |   4 +-
 ...persistend_db.py => test_persistent_db.py} | 122 +++++++++--------
 ...persistend_pg.py => test_persistent_pg.py} | 112 ++++++++--------
 tests/test_pooled_db.py                       |  68 +++++-----
 tests/test_pooled_pg.py                       |  34 +++--
 tests/test_simple_pooled_db.py                |  69 +++++-----
 tests/test_simple_pooled_pg.py                |  49 ++++---
 tests/test_steady_db.py                       |  56 ++++----
 tests/test_steady_pg.py                       |  34 +++--
 tests/test_threading_local.py                 |  68 +++++-----
 tox.ini                                       |   2 +-
 32 files changed, 350 insertions(+), 511 deletions(-)
 create mode 100644 .bumpversion.cfg
 delete mode 100755 setversion.py
 rename tests/{test_persistend_db.py => test_persistent_db.py} (74%)
 rename tests/{test_persistend_pg.py => test_persistent_pg.py} (64%)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
new file mode 100644
index 0000000..9eab400
--- /dev/null
+++ b/.bumpversion.cfg
@@ -0,0 +1,22 @@
+[bumpversion]
+current_version = 2.0
+
+[bumpversion:file:setup.py]
+search = __version__ = '{current_version}'
+replace = __version__ = '{new_version}'
+
+[bumpversion:file:dbutils/__init__.py]
+search = __version__ = '{current_version}'
+replace = __version__ = '{new_version}'
+
+[bumpversion:file:README.md]
+search = The current version {current_version}
+replace = The current version {new_version}
+
+[bumpversion:file:docs/main.rst]
+search = :Version: {current_version}
+search = :Version: {new_version}
+
+[bumpversion:file:docs/main.de.rst]
+search = :Version: {current_version}
+search = :Version: {new_version}
diff --git a/README.md b/README.md
index 481657f..3697d59 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,6 @@ to a database that can be used in all kinds of multi-threaded environments
 like Webware for Python or other web application servers. The suite supports
 DB-API 2 compliant database interfaces and the classic PyGreSQL interface.
 
-The current version of DBUtils supports Python versions 2.7 and 3.5 - 3.8.
+The current version 2.0 of DBUtils supports Python versions 2.7 and 3.5 - 3.8.
 
 The DBUtils home page can be found here: https://webwareforpython.github.io/DBUtils/
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index 7fd5939..adfdbf7 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -1,12 +1,8 @@
-# DBUtils package
+# DBUtils main package
 
 __all__ = [
-    'SimplePooledPg', 'SteadyPg', 'PooledPg', 'PersistentPg',
-    'SimplePooledDB', 'SteadyDB', 'PooledDB', 'PersistentDB'
-]
+    '__version__',
+    'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
+    'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '1.4'
-
-
-def InstallInWebKit(appServer):
-    pass
+__version__ = '2.0'
diff --git a/dbutils/persistent_db.py b/dbutils/persistent_db.py
index ae383d4..ac26c11 100644
--- a/dbutils/persistent_db.py
+++ b/dbutils/persistent_db.py
@@ -64,7 +64,7 @@
 every connection to your local database 'mydb' to be reused 1000 times:
 
     import pgdb  # import used DB-API 2 module
-    from DBUtils.PersistentDB import PersistentDB
+    from dbutils.persistent_db import PersistentDB
     persist = PersistentDB(pgdb, 1000, database='mydb')
 
 Once you have set up the generator with these parameters, you can
@@ -111,9 +111,8 @@
 
 """
 
-from DBUtils.SteadyDB import connect
-
-__version__ = '1.4'
+from . import __version__
+from .steady_db import connect
 
 try:
     # Prefer the pure Python version of threading.local.
diff --git a/dbutils/persistent_pg.py b/dbutils/persistent_pg.py
index 08ea171..b14960e 100644
--- a/dbutils/persistent_pg.py
+++ b/dbutils/persistent_pg.py
@@ -54,7 +54,7 @@
 For instance, if you want every connection to your local database 'mydb'
 to be reused 1000 times:
 
-    from DBUtils.PersistentPg import PersistentPg
+    from dbutils.persistent_pg import PersistentPg
     persist = PersistentPg(5, dbname='mydb')
 
 Once you have set up the generator with these parameters, you can
@@ -102,9 +102,8 @@
 
 """
 
-from DBUtils.SteadyPg import SteadyPgConnection
-
-__version__ = '1.4'
+from . import __version__
+from .steady_pg import SteadyPgConnection
 
 try:
     # Prefer the pure Python version of threading.local.
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 3336177..0b13c96 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -74,7 +74,7 @@
 want a pool of at least five connections to your local database 'mydb':
 
     import pgdb  # import used DB-API 2 module
-    from DBUtils.PooledDB import PooledDB
+    from dbutils.pooled_db import PooledDB
     pool = PooledDB(pgdb, 5, database='mydb')
 
 Once you have set up the connection pool you can request
@@ -141,9 +141,8 @@
 
 from threading import Condition
 
-from DBUtils.SteadyDB import connect
-
-__version__ = '1.4'
+from . import __version__
+from .steady_db import connect
 
 
 class PooledDBError(Exception):
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index c451dec..2f8b937 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -55,7 +55,7 @@
 For instance, if you want a pool of at least five connections
 to your local database 'mydb':
 
-    from DBUtils.PooledPg import PooledPg
+    from dbutils.pooled_pg import PooledPg
     pool = PooledPg(5, dbname='mydb')
 
 Once you have set up the connection pool you can request
@@ -113,9 +113,8 @@
 except ImportError:  # Python 3
     from queue import Queue, Empty, Full
 
-from DBUtils.SteadyPg import SteadyPgConnection
-
-__version__ = '1.4'
+from . import __version__
+from .steady_pg import SteadyPgConnection
 
 
 class PooledPgError(Exception):
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index 3318163..3e1b406 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -26,7 +26,7 @@
 to be cached in the pool and the connection parameters, e.g.
 
     import pgdb  # import used DB-API 2 module
-    from DBUtils.SimplePooledDB import PooledDB
+    from dbutils.simple_pooled_db import PooledDB
     dbpool = PooledDB(pgdb, 5, host=..., database=..., user=..., ...)
 
 you can demand database connections from that pool,
@@ -73,7 +73,7 @@
 
 """
 
-__version__ = '1.4'
+from . import __version__
 
 
 class PooledDBError(Exception):
diff --git a/dbutils/simple_pooled_pg.py b/dbutils/simple_pooled_pg.py
index 84cd1a2..4927a35 100644
--- a/dbutils/simple_pooled_pg.py
+++ b/dbutils/simple_pooled_pg.py
@@ -26,7 +26,7 @@
 number of connections to be cached in the pool and the
 connection parameters, e.g.
 
-    from DBUtils.SimplePooledPg import PooledPg
+    from dbutils.simple_pooled_pg import PooledPg
     dbpool = PooledPg(5, host=..., database=..., user=..., ...)
 
 you can demand database connections from that pool,
@@ -70,7 +70,7 @@
 
 from pg import DB as PgConnection
 
-__version__ = '1.4'
+from . import __version__
 
 
 class PooledPgConnection:
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 6ad6423..13918d6 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -58,7 +58,7 @@
 without further notice.
 
     import pgdb  # import used DB-API 2 module
-    from DBUtils.SteadyDB import connect
+    from dbutils.steady_db import connect
     db = connect(pgdb, 10000, ["set datestyle to german"],
         host=..., database=..., user=..., ...)
     ...
@@ -92,7 +92,7 @@
 
 import sys
 
-__version__ = '1.4'
+from . import __version__
 
 try:
     baseint = (int, long)
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index 5c10be8..a75b025 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -45,7 +45,7 @@
 database is lost or has been used too often, it will be automatically
 reset, without further notice.
 
-    from DBUtils.SteadyPg import SteadyPgConnection
+    from dbutils.steady_pg import SteadyPgConnection
     db = SteadyPgConnection(10000, ["set datestyle to german"],
         host=..., dbname=..., user=..., ...)
     ...
@@ -72,7 +72,7 @@
 
 from pg import DB as PgConnection
 
-__version__ = '1.4'
+from . import __version__
 
 try:
     baseint = (int, long)
diff --git a/docs/changelog.html b/docs/changelog.html
index 1ecb86c..7db17cd 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -78,6 +78,7 @@ <h2>Changes:</h2>
 <li><p>The top-level packages and folders have been renamed to lower-case.
 Particularly, you need to import <span class="docutils literal">dbutils</span> instead of <span class="docutils literal">DBUtils</span> now.</p></li>
 <li><p>The Webware <span class="docutils literal">Examples</span> folder has been removed.</p></li>
+<li><p>The internal naming conventions have been changed to comply with PEP8.</p></li>
 <li><p>This changelog file has been created from the former release notes.</p></li>
 </ul>
 </div>
@@ -155,12 +156,6 @@ <h2>Improvements:</h2>
 </div>
 <div class="section" id="id10">
 <h2>Bugfixes:</h2>
-<div class="system-message">
-<p class="system-message-title">System Message: WARNING/2 (<span class="docutils literal">changelog.rst</span>, line 100)</p>
-<p>Title underline too short.</p>
-<pre class="literal-block">Bugfixes:
---------</pre>
-</div>
 <ul class="simple">
 <li><p>Fixed propagation of error messages when the connection was lost.</p></li>
 <li><p>Fixed an issue with the <span class="docutils literal">setoutputsize()</span>  cursor method.</p></li>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index fb97129..bf41adf 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -18,6 +18,7 @@ Changes:
 * The top-level packages and folders have been renamed to lower-case.
   Particularly, you need to import ``dbutils`` instead of ``DBUtils`` now.
 * The Webware ``Examples`` folder has been removed.
+* The internal naming conventions have been changed to comply with PEP8.
 * This changelog file has been created from the former release notes.
 
 1.4
@@ -97,7 +98,7 @@ Improvements:
   connections are reset before being put back into the pool.
 
 Bugfixes:
---------
+---------
 
 * Fixed propagation of error messages when the connection was lost.
 * Fixed an issue with the ``setoutputsize()``  cursor method.
diff --git a/docs/main.de.html b/docs/main.de.html
index 10a3eef..b26431d 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -12,9 +12,6 @@ <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
 <dd class="version">2.0</dd>
-<dt class="released">Released</dt>
-<dd class="released"><p>09/26/20</p>
-</dd>
 <dt class="translations">Translations</dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 00013ac..f0fda86 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -2,7 +2,6 @@
 +++++++++++++++++++++++++++++
 
 :Version: 2.0
-:Released: 09/26/20
 :Translations: English_ | German
 
 .. _English: main.html
diff --git a/docs/main.html b/docs/main.html
index 9ec6f26..a6625bf 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -12,9 +12,6 @@ <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
 <dd class="version">2.0</dd>
-<dt class="released">Released</dt>
-<dd class="released"><p>09/26/20</p>
-</dd>
 <dt class="translations">Translations</dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
diff --git a/docs/main.rst b/docs/main.rst
index edbfd31..43b7905 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -2,7 +2,6 @@
 ++++++++++++++++++++
 
 :Version: 2.0
-:Released: 09/26/20
 :Translations: English | German_
 
 .. _German: main.de.html
diff --git a/docs/make.py b/docs/make.py
index fb82e88..f2153df 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -5,7 +5,7 @@
 from __future__ import print_function
 
 from glob import glob
-from os.path import splitext, join
+from os.path import splitext
 from docutils.core import publish_file
 
 print("Creating the documentation...")
@@ -25,13 +25,11 @@
     with open(rst_file, encoding='utf-8-sig') as source:
         with open(html_file, 'w', encoding='utf-8') as destination:
             publish_file(writer_name='html5',
-                source=source, destination=destination,
-                settings_overrides = dict(
-                    stylesheet_path='Doc.css',
-                    embed_stylesheet=False,
-                    toc_backlinks=False,
-                    language_code=lang
-                )
-            )
+                         source=source, destination=destination,
+                         settings_overrides=dict(
+                            stylesheet_path='Doc.css',
+                            embed_stylesheet=False,
+                            toc_backlinks=False,
+                            language_code=lang))
 
 print("Done.")
diff --git a/setup.py b/setup.py
index 478fe8e..5f54dfe 100755
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '1.4'
+__version__ = '2.0'
 
 readme = open('README.md').read()
 
diff --git a/setversion.py b/setversion.py
deleted file mode 100755
index 2164a07..0000000
--- a/setversion.py
+++ /dev/null
@@ -1,123 +0,0 @@
-#!/usr/bin/env python
-
-"""Set version.
-
-This script sets the DBUtils version number information
-consistently in all files of the distribution.
-
-"""
-
-from __future__ import print_function
-
-import os
-import sys
-import re
-from glob import glob
-
-# Version format is (Major, Minor, Sub, Alpha/Beta/etc)
-# The Sub is optional, and if 0 is not returned.
-# Examples: (0, 8, 1, 'b1'), (0, 8, 2) or (0, 9, 0, 'rc1')
-# releaseDate format should be 'MM/DD/YY'.
-
-# Update this to change the current version and release date:
-# version = ('X', 'Y', 0)
-version = (1, 4, 0)
-# releaseDate = '@@/@@/@@'
-releaseDate = '09/26/20'
-
-# Verbose output (output unchanged files also):
-verbose = False
-
-path = os.path.dirname(os.path.abspath(sys.argv[0]))
-sys.path.append(path)
-os.chdir(path)
-print("Setversion", path)
-
-
-def versionString(version):
-    """Create version string.
-
-    For a sequence containing version information such as (2, 0, 0, 'pre'),
-    this returns a printable string such as '2.0pre'.
-    The micro version number is only excluded from the string if it is zero.
-
-    """
-    ver = list(map(str, version))
-    numbers, rest = ver[:2 if ver[2] == '0' else 3], ver[3:]
-    return '.'.join(numbers) + '-'.join(rest)
-
-
-versionString = versionString(version)
-
-if versionString == 'X.Y':
-    print("Please set the version.")
-    sys.exit(1)
-if releaseDate == '@@/@@/@@':
-    print("Please set the release date.")
-    sys.exit(1)
-
-
-class Replacer:
-    """Class to handle substitutions in a file."""
-
-    def __init__(self, *args):
-        self._subs = list(args)
-
-    def add(self, search, replace):
-        self._subs.append((re.compile(search, re.M), replace))
-
-    def replaceInStr(self, data):
-        for search, replace in self._subs:
-            data = re.sub(search, replace, data)
-        return data
-
-    def replaceInFile(self, filename):
-        data = open(filename).read()
-        newData = self.replaceInStr(data)
-        if data == newData:
-            if verbose:
-                print("Unchanged", filename)
-        else:
-            print("Updating", filename)
-            open(filename, 'w').write(newData)
-
-    def replaceGlob(self, pattern):
-        for file in glob(pattern):
-            if os.path.exists(file):
-                self.replaceInFile(file)
-
-
-pyReplace = Replacer()
-pyReplace.add(r"(__version__\s*=\s*)'.*'", r"\g<1>%s" % repr(versionString))
-
-propReplace = Replacer()
-propReplace.add(r"(version\s*=\s*).*", r"\g<1>%s" % repr(version))
-propReplace.add(r"(releaseDate\s*=\s*).*", r"\g<1>%s" % repr(releaseDate))
-
-htmlReplace = Replacer()
-htmlReplace.add(
-    r"<!--\s*version\s*-->[^<]*<!--\s*/version\s*-->",
-    r"<!-- version --> %s <!-- /version -->" % versionString)
-htmlReplace.add(
-    r"<!--\s*relDate\s*-->[^<]*<!--\s*/relDate\s*-->",
-    r"<!-- relDate --> %s <!-- /relDate -->" % releaseDate)
-
-rstReplace = Replacer()
-rstReplace.add(
-    r"^:(.+)?: (X|\d+)\.(Y|\d+)(\.\d+)?$", r":\1: %s" % versionString)
-rstReplace.add(
-    r"^:(.+)?: (@|\d){2}/(@|\d){2}/(@|\d){2}$", r":\1: %s" % releaseDate)
-
-# Replace in Python files:
-pyReplace.replaceGlob('*.py')
-pyReplace.replaceGlob('DBUtils/*.py')
-pyReplace.replaceGlob('DBUtils/*/*.py')
-
-# Replace in Properties files:
-propReplace.replaceGlob('DBUtils/Properties.py')
-
-# Replace in existing HTML:
-htmlReplace.replaceGlob('DBUtils/Docs/*.html')
-
-# Replace in reStructuredText files:
-rstReplace.replaceGlob('DBUtils/Docs/*.rst')
diff --git a/tests/__init__.py b/tests/__init__.py
index 3195867..ec99602 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +1 @@
-# DBUtils Tests
+# DBUtils tests
diff --git a/tests/mock_pg.py b/tests/mock_pg.py
index 48fe998..7b4fcdc 100644
--- a/tests/mock_pg.py
+++ b/tests/mock_pg.py
@@ -22,10 +22,10 @@ class ProgrammingError(DatabaseError):
 
 
 def connect(*args, **kwargs):
-    return pgConnection(*args, **kwargs)
+    return PgConnection(*args, **kwargs)
 
 
-class pgConnection:
+class PgConnection:
     """The underlying pg API connection class."""
 
     def __init__(self, dbname=None, user=None):
diff --git a/tests/test_persistend_db.py b/tests/test_persistent_db.py
similarity index 74%
rename from tests/test_persistend_db.py
rename to tests/test_persistent_db.py
index 6f7ec53..bf1f8c6 100644
--- a/tests/test_persistend_db.py
+++ b/tests/test_persistent_db.py
@@ -13,11 +13,9 @@
 
 import unittest
 
-import DBUtils.Tests.mock_db as dbapi
+from . import mock_db as dbapi
 
-from DBUtils.PersistentDB import PersistentDB, local
-
-__version__ = '1.4'
+from dbutils.persistent_db import PersistentDB, local
 
 
 class TestPersistentDB(unittest.TestCase):
@@ -25,19 +23,17 @@ class TestPersistentDB(unittest.TestCase):
     def setUp(self):
         dbapi.threadsafety = 1
 
-    def test0_CheckVersion(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        from DBUtils.PersistentDB import __version__ as PersistentDBVersion
-        self.assertEqual(PersistentDBVersion, __version__)
+    def test_version(self):
+        from dbutils import __version__, persistent_db
+        self.assertEqual(persistent_db.__version__, __version__)
         self.assertEqual(PersistentDB.version, __version__)
 
-    def test1_NoThreadsafety(self):
-        from DBUtils.PersistentDB import NotSupportedError
+    def test_no_threadsafety(self):
+        from dbutils.persistent_db import NotSupportedError
         for dbapi.threadsafety in (None, 0):
             self.assertRaises(NotSupportedError, PersistentDB, dbapi)
 
-    def test2_Close(self):
+    def test_close(self):
         for closeable in (False, True):
             persist = PersistentDB(dbapi, closeable=closeable)
             db = persist.connection()
@@ -51,7 +47,7 @@ def test2_Close(self):
             db._close()
             self.assertFalse(db._con.valid)
 
-    def test3_Connection(self):
+    def test_connection(self):
         persist = PersistentDB(dbapi)
         db = persist.connection()
         db_con = db._con
@@ -65,26 +61,26 @@ def test3_Connection(self):
         db2.close()
         db.close()
 
-    def test4_Threads(self):
-        numThreads = 3
+    def test_threads(self):
+        num_threads = 3
         persist = PersistentDB(dbapi, closeable=True)
         try:
             from queue import Queue, Empty
         except ImportError:  # Python 2
             from Queue import Queue, Empty
-        queryQueue, resultQueue = [], []
-        for i in range(numThreads):
-            queryQueue.append(Queue(1))
-            resultQueue.append(Queue(1))
+        query_queue, result_queue = [], []
+        for i in range(num_threads):
+            query_queue.append(Queue(1))
+            result_queue.append(Queue(1))
 
-        def runQueries(i):
+        def run_queries(i):
             this_db = persist.connection()
             while 1:
                 try:
                     try:
-                        q = queryQueue[i].get(1, 1)
+                        q = query_queue[i].get(1, 1)
                     except TypeError:
-                        q = queryQueue[i].get(1)
+                        q = query_queue[i].get(1)
                 except Empty:
                     q = None
                 if not q:
@@ -105,82 +101,82 @@ def runQueries(i):
                         cursor.close()
                 r = '%d(%d): %s' % (i, db._usage, r)
                 try:
-                    resultQueue[i].put(r, 1, 1)
+                    result_queue[i].put(r, 1, 1)
                 except TypeError:
-                    resultQueue[i].put(r, 1)
+                    result_queue[i].put(r, 1)
             db.close()
 
         from threading import Thread
         threads = []
-        for i in range(numThreads):
-            thread = Thread(target=runQueries, args=(i,))
+        for i in range(num_threads):
+            thread = Thread(target=run_queries, args=(i,))
             threads.append(thread)
             thread.start()
-        for i in range(numThreads):
+        for i in range(num_threads):
             try:
-                queryQueue[i].put('ping', 1, 1)
+                query_queue[i].put('ping', 1, 1)
             except TypeError:
-                queryQueue[i].put('ping', 1)
-        for i in range(numThreads):
+                query_queue[i].put('ping', 1)
+        for i in range(num_threads):
             try:
-                r = resultQueue[i].get(1, 1)
+                r = result_queue[i].get(1, 1)
             except TypeError:
-                r = resultQueue[i].get(1)
+                r = result_queue[i].get(1)
             self.assertEqual(r, '%d(0): ok - thread alive' % i)
             self.assertTrue(threads[i].is_alive())
-        for i in range(numThreads):
+        for i in range(num_threads):
             for j in range(i + 1):
                 try:
-                    queryQueue[i].put('select test%d' % j, 1, 1)
-                    r = resultQueue[i].get(1, 1)
+                    query_queue[i].put('select test%d' % j, 1, 1)
+                    r = result_queue[i].get(1, 1)
                 except TypeError:
-                    queryQueue[i].put('select test%d' % j, 1)
-                    r = resultQueue[i].get(1)
+                    query_queue[i].put('select test%d' % j, 1)
+                    r = result_queue[i].get(1)
                 self.assertEqual(r, '%d(%d): test%d' % (i, j + 1, j))
         try:
-            queryQueue[1].put('select test4', 1, 1)
+            query_queue[1].put('select test4', 1, 1)
         except TypeError:
-            queryQueue[1].put('select test4', 1)
+            query_queue[1].put('select test4', 1)
         try:
-            r = resultQueue[1].get(1, 1)
+            r = result_queue[1].get(1, 1)
         except TypeError:
-            r = resultQueue[1].get(1)
+            r = result_queue[1].get(1)
         self.assertEqual(r, '1(3): test4')
         try:
-            queryQueue[1].put('close', 1, 1)
-            r = resultQueue[1].get(1, 1)
+            query_queue[1].put('close', 1, 1)
+            r = result_queue[1].get(1, 1)
         except TypeError:
-            queryQueue[1].put('close', 1)
-            r = resultQueue[1].get(1)
+            query_queue[1].put('close', 1)
+            r = result_queue[1].get(1)
         self.assertEqual(r, '1(3): ok - connection closed')
         for j in range(2):
             try:
-                queryQueue[1].put('select test%d' % j, 1, 1)
-                r = resultQueue[1].get(1, 1)
+                query_queue[1].put('select test%d' % j, 1, 1)
+                r = result_queue[1].get(1, 1)
             except TypeError:
-                queryQueue[1].put('select test%d' % j, 1)
-                r = resultQueue[1].get(1)
+                query_queue[1].put('select test%d' % j, 1)
+                r = result_queue[1].get(1)
             self.assertEqual(r, '1(%d): test%d' % (j + 1, j))
-        for i in range(numThreads):
+        for i in range(num_threads):
             self.assertTrue(threads[i].is_alive())
             try:
-                queryQueue[i].put('ping', 1, 1)
+                query_queue[i].put('ping', 1, 1)
             except TypeError:
-                queryQueue[i].put('ping', 1)
-        for i in range(numThreads):
+                query_queue[i].put('ping', 1)
+        for i in range(num_threads):
             try:
-                r = resultQueue[i].get(1, 1)
+                r = result_queue[i].get(1, 1)
             except TypeError:
-                r = resultQueue[i].get(1)
+                r = result_queue[i].get(1)
             self.assertEqual(r, '%d(%d): ok - thread alive' % (i, i + 1))
             self.assertTrue(threads[i].is_alive())
-        for i in range(numThreads):
+        for i in range(num_threads):
             try:
-                queryQueue[i].put(None, 1, 1)
+                query_queue[i].put(None, 1, 1)
             except TypeError:
-                queryQueue[i].put(None, 1)
+                query_queue[i].put(None, 1)
 
-    def test5_MaxUsage(self):
+    def test_maxusage(self):
         persist = PersistentDB(dbapi, 20)
         db = persist.connection()
         self.assertEqual(db._maxusage, 20)
@@ -196,7 +192,7 @@ def test5_MaxUsage(self):
             self.assertEqual(db._con.num_uses, j)
             self.assertEqual(db._con.num_queries, j)
 
-    def test6_SetSession(self):
+    def test_setsession(self):
         persist = PersistentDB(dbapi, 3, ('set datestyle',))
         db = persist.connection()
         self.assertEqual(db._maxusage, 3)
@@ -214,7 +210,7 @@ def test6_SetSession(self):
             cursor.close()
         self.assertEqual(db._con.session, ['datestyle'])
 
-    def test7_ThreadLocal(self):
+    def test_threadlocal(self):
         persist = PersistentDB(dbapi)
         self.assertTrue(isinstance(persist.thread, local))
 
@@ -224,7 +220,7 @@ class threadlocal:
         persist = PersistentDB(dbapi, threadlocal=threadlocal)
         self.assertTrue(isinstance(persist.thread, threadlocal))
 
-    def test8_PingCheck(self):
+    def test_ping_check(self):
         Connection = dbapi.Connection
         Connection.has_ping = True
         Connection.num_pings = 0
@@ -276,7 +272,7 @@ def test8_PingCheck(self):
         Connection.has_ping = False
         Connection.num_pings = 0
 
-    def test9_FailedTransaction(self):
+    def test_failed_transaction(self):
         persist = PersistentDB(dbapi)
         db = persist.connection()
         cursor = db.cursor()
diff --git a/tests/test_persistend_pg.py b/tests/test_persistent_pg.py
similarity index 64%
rename from tests/test_persistend_pg.py
rename to tests/test_persistent_pg.py
index f5ba411..bed067b 100644
--- a/tests/test_persistend_pg.py
+++ b/tests/test_persistent_pg.py
@@ -13,23 +13,19 @@
 
 import unittest
 
-import DBUtils.Tests.mock_pg as pg
+from . import mock_pg as pg
 
-from DBUtils.PersistentPg import PersistentPg
-
-__version__ = '1.4'
+from dbutils.persistent_pg import PersistentPg
 
 
 class TestPersistentPg(unittest.TestCase):
 
-    def test0_CheckVersion(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        from DBUtils.PersistentPg import __version__ as PersistentPgVersion
-        self.assertEqual(PersistentPgVersion, __version__)
+    def test_version(self):
+        from dbutils import __version__, persistent_pg
+        self.assertEqual(persistent_pg.__version__, __version__)
         self.assertEqual(PersistentPg.version, __version__)
 
-    def test1_Close(self):
+    def test_close(self):
         for closeable in (False, True):
             persist = PersistentPg(closeable=closeable)
             db = persist.connection()
@@ -45,26 +41,26 @@ def test1_Close(self):
             db._close()
             self.assertFalse(db._con.db and db._con.valid)
 
-    def test2_Threads(self):
-        numThreads = 3
+    def test_threads(self):
+        num_threads = 3
         persist = PersistentPg()
         try:
             from queue import Queue, Empty
         except ImportError:  # Python 2
             from Queue import Queue, Empty
-        queryQueue, resultQueue = [], []
-        for i in range(numThreads):
-            queryQueue.append(Queue(1))
-            resultQueue.append(Queue(1))
+        query_queue, result_queue = [], []
+        for i in range(num_threads):
+            query_queue.append(Queue(1))
+            result_queue.append(Queue(1))
 
-        def runQueries(i):
+        def run_queries(i):
             this_db = persist.connection().db
             while 1:
                 try:
                     try:
-                        q = queryQueue[i].get(1, 1)
+                        q = query_queue[i].get(1, 1)
                     except TypeError:
-                        q = queryQueue[i].get(1)
+                        q = query_queue[i].get(1)
                 except Empty:
                     q = None
                 if not q:
@@ -82,80 +78,80 @@ def runQueries(i):
                         r = db.query(q)
                 r = '%d(%d): %s' % (i, db._usage, r)
                 try:
-                    resultQueue[i].put(r, 1, 1)
+                    result_queue[i].put(r, 1, 1)
                 except TypeError:
-                    resultQueue[i].put(r, 1)
+                    result_queue[i].put(r, 1)
             db.close()
 
         from threading import Thread
         threads = []
-        for i in range(numThreads):
-            thread = Thread(target=runQueries, args=(i,))
+        for i in range(num_threads):
+            thread = Thread(target=run_queries, args=(i,))
             threads.append(thread)
             thread.start()
-        for i in range(numThreads):
+        for i in range(num_threads):
             try:
-                queryQueue[i].put('ping', 1, 1)
+                query_queue[i].put('ping', 1, 1)
             except TypeError:
-                queryQueue[i].put('ping', 1)
-        for i in range(numThreads):
+                query_queue[i].put('ping', 1)
+        for i in range(num_threads):
             try:
-                r = resultQueue[i].get(1, 1)
+                r = result_queue[i].get(1, 1)
             except TypeError:
-                r = resultQueue[i].get(1)
+                r = result_queue[i].get(1)
             self.assertEqual(r, '%d(0): ok - thread alive' % i)
             self.assertTrue(threads[i].is_alive())
-        for i in range(numThreads):
+        for i in range(num_threads):
             for j in range(i + 1):
                 try:
-                    queryQueue[i].put('select test%d' % j, 1, 1)
-                    r = resultQueue[i].get(1, 1)
+                    query_queue[i].put('select test%d' % j, 1, 1)
+                    r = result_queue[i].get(1, 1)
                 except TypeError:
-                    queryQueue[i].put('select test%d' % j, 1)
-                    r = resultQueue[i].get(1)
+                    query_queue[i].put('select test%d' % j, 1)
+                    r = result_queue[i].get(1)
                 self.assertEqual(r, '%d(%d): test%d' % (i, j + 1, j))
         try:
-            queryQueue[1].put('select test4', 1, 1)
-            r = resultQueue[1].get(1, 1)
+            query_queue[1].put('select test4', 1, 1)
+            r = result_queue[1].get(1, 1)
         except TypeError:
-            queryQueue[1].put('select test4', 1)
-            r = resultQueue[1].get(1)
+            query_queue[1].put('select test4', 1)
+            r = result_queue[1].get(1)
         self.assertEqual(r, '1(3): test4')
         try:
-            queryQueue[1].put('close', 1, 1)
-            r = resultQueue[1].get(1, 1)
+            query_queue[1].put('close', 1, 1)
+            r = result_queue[1].get(1, 1)
         except TypeError:
-            queryQueue[1].put('close', 1)
-            r = resultQueue[1].get(1)
+            query_queue[1].put('close', 1)
+            r = result_queue[1].get(1)
         self.assertEqual(r, '1(3): ok - connection closed')
         for j in range(2):
             try:
-                queryQueue[1].put('select test%d' % j, 1, 1)
-                r = resultQueue[1].get(1, 1)
+                query_queue[1].put('select test%d' % j, 1, 1)
+                r = result_queue[1].get(1, 1)
             except TypeError:
-                queryQueue[1].put('select test%d' % j, 1)
-                r = resultQueue[1].get(1)
+                query_queue[1].put('select test%d' % j, 1)
+                r = result_queue[1].get(1)
             self.assertEqual(r, '1(%d): test%d' % (j + 1, j))
-        for i in range(numThreads):
+        for i in range(num_threads):
             self.assertTrue(threads[i].is_alive())
             try:
-                queryQueue[i].put('ping', 1, 1)
+                query_queue[i].put('ping', 1, 1)
             except TypeError:
-                queryQueue[i].put('ping', 1)
-        for i in range(numThreads):
+                query_queue[i].put('ping', 1)
+        for i in range(num_threads):
             try:
-                r = resultQueue[i].get(1, 1)
+                r = result_queue[i].get(1, 1)
             except TypeError:
-                r = resultQueue[i].get(1)
+                r = result_queue[i].get(1)
             self.assertEqual(r, '%d(%d): ok - thread alive' % (i, i + 1))
             self.assertTrue(threads[i].is_alive())
-        for i in range(numThreads):
+        for i in range(num_threads):
             try:
-                queryQueue[i].put(None, 1, 1)
+                query_queue[i].put(None, 1, 1)
             except TypeError:
-                queryQueue[i].put(None, 1)
+                query_queue[i].put(None, 1)
 
-    def test3_MaxUsage(self):
+    def test_maxusage(self):
         persist = PersistentPg(20)
         db = persist.connection()
         self.assertEqual(db._maxusage, 20)
@@ -167,7 +163,7 @@ def test3_MaxUsage(self):
             self.assertEqual(db._usage, j)
             self.assertEqual(db.num_queries, j)
 
-    def test4_SetSession(self):
+    def test_setsession(self):
         persist = PersistentPg(3, ('set datestyle',))
         db = persist.connection()
         self.assertEqual(db._maxusage, 3)
@@ -179,7 +175,7 @@ def test4_SetSession(self):
             db.query('select test')
         self.assertEqual(db.db.session, ['datestyle'])
 
-    def test5_FailedTransaction(self):
+    def test_failed_transaction(self):
         persist = PersistentPg()
         db = persist.connection()
         db._con.close()
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 04ee5b8..860b7f5 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -13,30 +13,26 @@
 
 import unittest
 
-import DBUtils.Tests.mock_db as dbapi
+from . import mock_db as dbapi
 
-from DBUtils.PooledDB import (
+from dbutils.pooled_db import (
     PooledDB, SharedDBConnection, InvalidConnection, TooManyConnections)
 
-__version__ = '1.4'
-
 
 class TestPooledDB(unittest.TestCase):
 
-    def test00_CheckVersion(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        from DBUtils.PooledDB import __version__ as PooledDBVersion
-        self.assertEqual(PooledDBVersion, __version__)
+    def test_version(self):
+        from dbutils import __version__, pooled_db
+        self.assertEqual(pooled_db.__version__, __version__)
         self.assertEqual(PooledDB.version, __version__)
 
-    def test01_NoThreadsafety(self):
-        from DBUtils.PooledDB import NotSupportedError
+    def test_no_threadsafety(self):
+        from dbutils.pooled_db import NotSupportedError
         for threadsafety in (None, 0):
             dbapi.threadsafety = threadsafety
             self.assertRaises(NotSupportedError, PooledDB, dbapi)
 
-    def test02_Threadsafety(self):
+    def test_threadsafety(self):
         for threadsafety in (1, 2, 3):
             dbapi.threadsafety = threadsafety
             pool = PooledDB(dbapi, 0, 0, 1)
@@ -48,7 +44,7 @@ def test02_Threadsafety(self):
                 self.assertEqual(pool._maxshared, 0)
                 self.assertFalse(hasattr(pool, '_shared_cache'))
 
-    def test03_CreateConnection(self):
+    def test_create_connection(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -67,7 +63,7 @@ def test03_CreateConnection(self):
             self.assertTrue(hasattr(pool, '_setsession'))
             self.assertIsNone(pool._setsession)
             con = pool._idle_cache[0]
-            from DBUtils.SteadyDB import SteadyDBConnection
+            from dbutils.steady_db import SteadyDBConnection
             self.assertTrue(isinstance(con, SteadyDBConnection))
             self.assertTrue(hasattr(con, '_maxusage'))
             self.assertEqual(con._maxusage, 0)
@@ -167,7 +163,7 @@ def test03_CreateConnection(self):
             self.assertEqual(con._maxusage, 3)
             self.assertEqual(con._setsession_sql, ('set datestyle',))
 
-    def test04_CloseConnection(self):
+    def test_close_connection(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -189,7 +185,7 @@ def test04_CloseConnection(self):
                 self.assertEqual(shared_con.shared, 1)
                 self.assertTrue(hasattr(shared_con, 'con'))
                 self.assertEqual(shared_con.con, con)
-            from DBUtils.SteadyDB import SteadyDBConnection
+            from dbutils.steady_db import SteadyDBConnection
             self.assertTrue(isinstance(con, SteadyDBConnection))
             self.assertTrue(hasattr(con, '_con'))
             db_con = con._con
@@ -247,7 +243,7 @@ def test04_CloseConnection(self):
             if shareable:
                 self.assertEqual(len(pool._shared_cache), 0)
 
-    def test05_CloseAll(self):
+    def test_close_all(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -304,7 +300,7 @@ def close_shared(what=closed):
                 del cache
             self.assertEqual(closed, ['idle', 'shared'])
 
-    def test06_ShareableConnection(self):
+    def test_shareable_connection(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -394,7 +390,7 @@ def test06_ShareableConnection(self):
             else:
                 self.assertEqual(len(pool._idle_cache), 3)
 
-    def test08_MinMaxCached(self):
+    def test_min_max_cached(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -475,7 +471,7 @@ def test08_MinMaxCached(self):
             if shareable:
                 self.assertEqual(len(pool._shared_cache), 0)
 
-    def test08_MaxShared(self):
+    def test_max_shared(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -514,7 +510,7 @@ def test08_MaxShared(self):
             if shareable:
                 self.assertEqual(len(pool._shared_cache), 7)
 
-    def test09_SortShared(self):
+    def test_sort_shared(self):
         dbapi.threadsafety = 2
         pool = PooledDB(dbapi, 0, 4, 4)
         cache = []
@@ -533,7 +529,7 @@ def test09_SortShared(self):
         db = pool.connection()
         self.assertIs(db._con, cache[3]._con)
 
-    def test10_EquallyShared(self):
+    def test_equally_shared(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -568,7 +564,7 @@ def test10_EquallyShared(self):
             if shareable:
                 self.assertEqual(len(pool._shared_cache), 0)
 
-    def test11_ManyShared(self):
+    def test_many_shared(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -619,7 +615,7 @@ def test11_ManyShared(self):
             else:
                 self.assertEqual(len(pool._idle_cache), 35)
 
-    def test12_Rollback(self):
+    def test_rollback(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             pool = PooledDB(dbapi, 0, 1)
@@ -651,7 +647,7 @@ def test12_Rollback(self):
                 'doit1', 'commit', 'dont1', 'rollback',
                 'doit2', 'commit', 'rollback'])
 
-    def test13_MaxConnections(self):
+    def test_maxconnections(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -856,7 +852,7 @@ def connection():
                 session, ['rollback', 'rollback', 'thread', 'rollback'])
             del db
 
-    def test14_MaxUsage(self):
+    def test_maxusage(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             for maxusage in (0, 3, 7):
@@ -891,7 +887,7 @@ def test14_MaxUsage(self):
                 self.assertEqual(db._con._con.num_uses, j + 1)
                 self.assertEqual(db._con._con.num_queries, j)
 
-    def test15_SetSession(self):
+    def test_setsession(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             setsession = ('set time zone', 'set datestyle')
@@ -920,7 +916,7 @@ def test15_SetSession(self):
             self.assertEqual(
                 db._con._con.session, ['time zone', 'datestyle', 'test2'])
 
-    def test16_OneThreadTwoConnections(self):
+    def test_one_thread_two_connections(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             shareable = threadsafety > 1
@@ -977,7 +973,7 @@ def test16_OneThreadTwoConnections(self):
             self.assertNotEqual(db1, db2)
             self.assertNotEqual(db1._con, db2._con)
 
-    def test17_ThreeThreadsTwoConnections(self):
+    def test_tnree_threads_two_connections(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             pool = PooledDB(dbapi, 2, 2, 0, 2, True)
@@ -1039,7 +1035,7 @@ def connection():
             self.assertNotEqual(db1._con, db2._con)
             self.assertEqual(db1._con, db1_con)
 
-    def test18_PingCheck(self):
+    def test_ping_check(self):
         Connection = dbapi.Connection
         Connection.has_ping = True
         Connection.num_pings = 0
@@ -1107,7 +1103,7 @@ def test18_PingCheck(self):
         Connection.has_ping = False
         Connection.num_pings = 0
 
-    def test19_FailedTransaction(self):
+    def test_failed_transaction(self):
         dbapi.threadsafety = 2
         pool = PooledDB(dbapi, 0, 1, 1)
         db = pool.connection()
@@ -1136,7 +1132,7 @@ def test19_FailedTransaction(self):
         db._con._con.close()
         cursor.execute('select test')
 
-    def test20_SharedInTransaction(self):
+    def test_shared_in_transaction(self):
         dbapi.threadsafety = 2
         pool = PooledDB(dbapi, 0, 1, 1)
         db = pool.connection()
@@ -1164,7 +1160,7 @@ def test20_SharedInTransaction(self):
         db = pool.connection()
         self.assertIs(db._con, db1._con)
 
-    def test21_ResetTransaction(self):
+    def test_reset_transaction(self):
         pool = PooledDB(dbapi, 1, 1, 0)
         db = pool.connection()
         db.begin()
@@ -1189,13 +1185,13 @@ def test21_ResetTransaction(self):
 
 class TestSharedDBConnection(unittest.TestCase):
 
-    def test01_CreateConnection(self):
+    def test_create_connection(self):
         db_con = dbapi.connect()
         con = SharedDBConnection(db_con)
         self.assertEqual(con.con, db_con)
         self.assertEqual(con.shared, 1)
 
-    def test01_ShareAndUnshare(self):
+    def test_share_and_unshare(self):
         con = SharedDBConnection(dbapi.connect())
         self.assertEqual(con.shared, 1)
         con.share()
@@ -1207,7 +1203,7 @@ def test01_ShareAndUnshare(self):
         con.unshare()
         self.assertEqual(con.shared, 1)
 
-    def test02_Comparison(self):
+    def test_comparison(self):
         con1 = SharedDBConnection(dbapi.connect())
         con1.con._transaction = False
         con2 = SharedDBConnection(dbapi.connect())
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index cd4103b..0379693 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -13,23 +13,19 @@
 
 import unittest
 
-import DBUtils.Tests.mock_pg  # noqa
+from . import mock_pg  # noqa
 
-from DBUtils.PooledPg import PooledPg, InvalidConnection
-
-__version__ = '1.4'
+from dbutils.pooled_pg import PooledPg, InvalidConnection
 
 
 class TestPooledPg(unittest.TestCase):
 
-    def test0_CheckVersion(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        from DBUtils.PooledPg import __version__ as PooledPgVersion
-        self.assertEqual(PooledPgVersion, __version__)
+    def test_version(self):
+        from dbutils import __version__, pooled_pg
+        self.assertEqual(pooled_pg.__version__, __version__)
         self.assertEqual(PooledPg.version, __version__)
 
-    def test1_CreateConnection(self):
+    def test_create_connection(self):
         pool = PooledPg(
             1, 1, 0, False, None, None, False,
             'PooledPgTestDB', user='PooledPgTestUser')
@@ -43,7 +39,7 @@ def test1_CreateConnection(self):
         self.assertFalse(pool._reset)
         db_con = pool._cache.get(0)
         pool._cache.put(db_con, 0)
-        from DBUtils.SteadyPg import SteadyPgConnection
+        from dbutils.steady_pg import SteadyPgConnection
         self.assertTrue(isinstance(db_con, SteadyPgConnection))
         db = pool.connection()
         self.assertEqual(pool._cache.qsize(), 0)
@@ -77,14 +73,14 @@ def test1_CreateConnection(self):
         self.assertEqual(db._maxusage, 3)
         self.assertEqual(db._setsession_sql, ('set datestyle',))
 
-    def test2_CloseConnection(self):
+    def test_close_connection(self):
         pool = PooledPg(
             0, 1, 0, False, None, None, False,
             'PooledPgTestDB', user='PooledPgTestUser')
         db = pool.connection()
         self.assertTrue(hasattr(db, '_con'))
         db_con = db._con
-        from DBUtils.SteadyPg import SteadyPgConnection
+        from dbutils.steady_pg import SteadyPgConnection
         self.assertTrue(isinstance(db_con, SteadyPgConnection))
         self.assertTrue(hasattr(pool, '_cache'))
         self.assertEqual(pool._cache.qsize(), 0)
@@ -105,7 +101,7 @@ def test2_CloseConnection(self):
         self.assertEqual(pool._cache.qsize(), 1)
         self.assertEqual(pool._cache.get(0), db_con)
 
-    def test3_MinMaxCached(self):
+    def test_min_max_cached(self):
         pool = PooledPg(3)
         self.assertTrue(hasattr(pool, '_cache'))
         self.assertEqual(pool._cache.qsize(), 3)
@@ -151,8 +147,8 @@ def test3_MinMaxCached(self):
             cache.pop().close()
         self.assertEqual(pool._cache.qsize(), 5)
 
-    def test4_MaxConnections(self):
-        from DBUtils.PooledPg import TooManyConnections
+    def test_max_connections(self):
+        from dbutils.pooled_pg import TooManyConnections
         pool = PooledPg(1, 2, 3)
         self.assertEqual(pool._cache.qsize(), 1)
         cache = [pool.connection() for i in range(3)]
@@ -205,7 +201,7 @@ def connection():
         self.assertEqual(session, ['thread'])
         del db
 
-    def test5_OneThreadTwoConnections(self):
+    def test_one_thread_two_connections(self):
         pool = PooledPg(2)
         db1 = pool.connection()
         for i in range(5):
@@ -228,7 +224,7 @@ def test5_OneThreadTwoConnections(self):
         db2.query('select test')
         self.assertEqual(db2.num_queries, 8)
 
-    def test6_ThreeThreadsTwoConnections(self):
+    def test_three_threads_two_connections(self):
         pool = PooledPg(2, 2, 2, True)
         try:
             from queue import Queue, Empty
@@ -268,7 +264,7 @@ def connection():
         self.assertNotEqual(db1._con, db2._con)
         self.assertEqual(db1._con, db1_con)
 
-    def test7_ResetTransaction(self):
+    def test_reset_transaction(self):
         pool = PooledPg(1)
         db = pool.connection()
         db.begin()
diff --git a/tests/test_simple_pooled_db.py b/tests/test_simple_pooled_db.py
index 28d2764..3c6ede1 100644
--- a/tests/test_simple_pooled_db.py
+++ b/tests/test_simple_pooled_db.py
@@ -14,49 +14,37 @@
 
 import unittest
 
-import DBUtils.Tests.mock_db as dbapi
+from . import mock_db as dbapi
 
-from DBUtils import SimplePooledDB
-
-__version__ = '1.4'
-
-
-def versionString(version):
-    """Create version string."""
-    ver = [str(v) for v in version]
-    numbers, rest = ver[:2 if ver[2] == '0' else 3], ver[3:]
-    return '.'.join(numbers) + '-'.join(rest)
+from dbutils import simple_pooled_db
 
 
 class TestSimplePooledDB(unittest.TestCase):
 
-    def my_dbpool(self, mythreadsafety, maxConnections):
+    def my_db_pool(self, mythreadsafety, maxConnections):
         threadsafety = dbapi.threadsafety
         dbapi.threadsafety = mythreadsafety
         try:
-            return SimplePooledDB.PooledDB(
+            return simple_pooled_db.PooledDB(
                 dbapi, maxConnections,
                 'SimplePooledDBTestDB', 'SimplePooledDBTestUser')
         finally:
             dbapi.threadsafety = threadsafety
 
-    def test0_check_version(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        from DBUtils.Properties import version
-        self.assertEqual(versionString(version), __version__)
-        self.assertEqual(SimplePooledDB.__version__, __version__)
-        self.assertEqual(SimplePooledDB.PooledDB.version, __version__)
+    def test_version(self):
+        from dbutils import __version__
+        self.assertEqual(simple_pooled_db.__version__, __version__)
+        self.assertEqual(simple_pooled_db.PooledDB.version, __version__)
 
-    def test1_no_threadsafety(self):
+    def test_no_threadsafety(self):
         for threadsafety in (None, -1, 0, 4):
             self.assertRaises(
-                SimplePooledDB.NotSupportedError,
-                self.my_dbpool, threadsafety, 1)
+                simple_pooled_db.NotSupportedError,
+                self.my_db_pool, threadsafety, 1)
 
-    def test2_create_connection(self):
+    def test_create_connection(self):
         for threadsafety in (1, 2, 3):
-            dbpool = self.my_dbpool(threadsafety, 1)
+            dbpool = self.my_db_pool(threadsafety, 1)
             db = dbpool.connection()
             self.assertTrue(hasattr(db, 'cursor'))
             self.assertTrue(hasattr(db, 'open_cursors'))
@@ -66,41 +54,44 @@ def test2_create_connection(self):
             self.assertTrue(hasattr(db, 'user'))
             self.assertEqual(db.user, 'SimplePooledDBTestUser')
             cursor = db.cursor()
+            self.assertIsNotNone(cursor)
             self.assertEqual(db.open_cursors, 1)
             del cursor
 
-    def test3_close_connection(self):
+    def test_close_connection(self):
         for threadsafety in (1, 2, 3):
-            dbpool = self.my_dbpool(threadsafety, 1)
-            db = dbpool.connection()
+            db_pool = self.my_db_pool(threadsafety, 1)
+            db = db_pool.connection()
             self.assertEqual(db.open_cursors, 0)
             cursor1 = db.cursor()
+            self.assertIsNotNone(cursor1)
             self.assertEqual(db.open_cursors, 1)
             db.close()
             self.assertFalse(hasattr(db, 'open_cursors'))
-            db = dbpool.connection()
+            db = db_pool.connection()
             self.assertTrue(hasattr(db, 'database'))
             self.assertEqual(db.database, 'SimplePooledDBTestDB')
             self.assertTrue(hasattr(db, 'user'))
             self.assertEqual(db.user, 'SimplePooledDBTestUser')
             self.assertEqual(db.open_cursors, 1)
             cursor2 = db.cursor()
+            self.assertIsNotNone(cursor2)
             self.assertEqual(db.open_cursors, 2)
             del cursor2
             del cursor1
 
-    def test4_two_connections(self):
+    def test_two_connections(self):
         for threadsafety in (1, 2, 3):
-            dbpool = self.my_dbpool(threadsafety, 2)
-            db1 = dbpool.connection()
+            db_pool = self.my_db_pool(threadsafety, 2)
+            db1 = db_pool.connection()
             cursors1 = [db1.cursor() for i in range(5)]
-            db2 = dbpool.connection()
+            db2 = db_pool.connection()
             self.assertNotEqual(db1, db2)
             cursors2 = [db2.cursor() for i in range(7)]
             self.assertEqual(db1.open_cursors, 5)
             self.assertEqual(db2.open_cursors, 7)
             db1.close()
-            db1 = dbpool.connection()
+            db1 = db_pool.connection()
             self.assertNotEqual(db1, db2)
             self.assertTrue(hasattr(db1, 'cursor'))
             for i in range(3):
@@ -111,8 +102,8 @@ def test4_two_connections(self):
             del cursors2
             del cursors1
 
-    def test5_threadsafety_1(self):
-        dbpool = self.my_dbpool(1, 2)
+    def test_threadsafety_1(self):
+        db_pool = self.my_db_pool(1, 2)
         try:
             from queue import Queue, Empty
         except ImportError:  # Python 2
@@ -120,7 +111,7 @@ def test5_threadsafety_1(self):
         queue = Queue(3)
 
         def connection():
-            queue.put(dbpool.connection())
+            queue.put(db_pool.connection())
 
         from threading import Thread
         threads = [Thread(target=connection).start() for i in range(3)]
@@ -145,9 +136,9 @@ def connection():
         self.assertNotEqual(db1, db3)
         self.assertNotEqual(db1._con, db3._con)
 
-    def test6_threadsafety_2(self):
+    def test_threadsafety_2(self):
         for threadsafety in (2, 3):
-            dbpool = self.my_dbpool(threadsafety, 2)
+            dbpool = self.my_db_pool(threadsafety, 2)
             db1 = dbpool.connection()
             db2 = dbpool.connection()
             cursors = [dbpool.connection().cursor() for i in range(100)]
diff --git a/tests/test_simple_pooled_pg.py b/tests/test_simple_pooled_pg.py
index 7ff7690..251fd72 100644
--- a/tests/test_simple_pooled_pg.py
+++ b/tests/test_simple_pooled_pg.py
@@ -13,28 +13,25 @@
 
 import unittest
 
-import DBUtils.Tests.mock_pg  # noqa
+from . import mock_pg  # noqa
 
-from DBUtils import SimplePooledPg
-
-__version__ = '1.4'
+from dbutils import simple_pooled_pg
 
 
 class TestSimplePooledPg(unittest.TestCase):
 
-    def my_dbpool(self, maxConnections):
-        return SimplePooledPg.PooledPg(
+    def my_db_pool(self, maxConnections):
+        return simple_pooled_pg.PooledPg(
             maxConnections, 'SimplePooledPgTestDB', 'SimplePooledPgTestUser')
 
-    def test0_check_version(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        self.assertEqual(SimplePooledPg.__version__, __version__)
-        self.assertEqual(SimplePooledPg.PooledPg.version, __version__)
+    def test_version(self):
+        from dbutils import __version__
+        self.assertEqual(simple_pooled_pg.__version__, __version__)
+        self.assertEqual(simple_pooled_pg.PooledPg.version, __version__)
 
-    def test1_create_connection(self):
-        dbpool = self.my_dbpool(1)
-        db = dbpool.connection()
+    def test_create_connection(self):
+        db_pool = self.my_db_pool(1)
+        db = db_pool.connection()
         self.assertTrue(hasattr(db, 'query'))
         self.assertTrue(hasattr(db, 'num_queries'))
         self.assertEqual(db.num_queries, 0)
@@ -45,15 +42,15 @@ def test1_create_connection(self):
         db.query('select 1')
         self.assertEqual(db.num_queries, 1)
 
-    def test2_close_connection(self):
-        dbpool = self.my_dbpool(1)
-        db = dbpool.connection()
+    def test_close_connection(self):
+        db_pool = self.my_db_pool(1)
+        db = db_pool.connection()
         self.assertEqual(db.num_queries, 0)
         db.query('select 1')
         self.assertEqual(db.num_queries, 1)
         db.close()
         self.assertFalse(hasattr(db, 'num_queries'))
-        db = dbpool.connection()
+        db = db_pool.connection()
         self.assertTrue(hasattr(db, 'dbname'))
         self.assertEqual(db.dbname, 'SimplePooledPgTestDB')
         self.assertTrue(hasattr(db, 'user'))
@@ -62,12 +59,12 @@ def test2_close_connection(self):
         db.query('select 1')
         self.assertEqual(db.num_queries, 2)
 
-    def test3_two_connections(self):
-        dbpool = self.my_dbpool(2)
-        db1 = dbpool.connection()
+    def test_two_connections(self):
+        db_pool = self.my_db_pool(2)
+        db1 = db_pool.connection()
         for i in range(5):
             db1.query('select 1')
-        db2 = dbpool.connection()
+        db2 = db_pool.connection()
         self.assertNotEqual(db1, db2)
         self.assertNotEqual(db1._con, db2._con)
         for i in range(7):
@@ -75,7 +72,7 @@ def test3_two_connections(self):
         self.assertEqual(db1.num_queries, 5)
         self.assertEqual(db2.num_queries, 7)
         db1.close()
-        db1 = dbpool.connection()
+        db1 = db_pool.connection()
         self.assertNotEqual(db1, db2)
         self.assertNotEqual(db1._con, db2._con)
         self.assertTrue(hasattr(db1, 'query'))
@@ -85,8 +82,8 @@ def test3_two_connections(self):
         db2.query('select 1')
         self.assertEqual(db2.num_queries, 8)
 
-    def test4_threads(self):
-        dbpool = self.my_dbpool(2)
+    def test_threads(self):
+        db_pool = self.my_db_pool(2)
         try:
             from queue import Queue, Empty
         except ImportError:  # Python 2
@@ -94,7 +91,7 @@ def test4_threads(self):
         queue = Queue(3)
 
         def connection():
-            queue.put(dbpool.connection())
+            queue.put(db_pool.connection())
 
         from threading import Thread
         threads = [Thread(target=connection).start() for i in range(3)]
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index f9c7d15..79342a6 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -12,24 +12,20 @@
 
 import unittest
 
-import DBUtils.Tests.mock_db as dbapi
+from . import mock_db as dbapi
 
-from DBUtils.SteadyDB import (
+from dbutils.steady_db import (
     connect as SteadyDBconnect, SteadyDBConnection, SteadyDBCursor)
 
-__version__ = '1.4'
-
 
 class TestSteadyDB(unittest.TestCase):
 
-    def test00_CheckVersion(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        from DBUtils.SteadyDB import __version__ as SteadyDBVersion
-        self.assertEqual(SteadyDBVersion, __version__)
-        self.assertEqual(SteadyDBConnection.version, __version__)
+    def test_version(self):
+        from dbutils import __version__, steady_db
+        self.assertEqual(steady_db.__version__, __version__)
+        self.assertEqual(steady_db.SteadyDBConnection.version, __version__)
 
-    def test01_MockedConnection(self):
+    def test_mocked_connection(self):
         db = dbapi.connect(
             'SteadyDBTestDB', user='SteadyDBTestUser')
         db.__class__.has_ping = False
@@ -101,7 +97,7 @@ def test01_MockedConnection(self):
         db.__class__.has_ping = False
         db.__class__.num_pings = 0
 
-    def test02_BrokenConnection(self):
+    def test_broken_connection(self):
         self.assertRaises(TypeError, SteadyDBConnection, None)
         self.assertRaises(TypeError, SteadyDBCursor, None)
         db = SteadyDBconnect(dbapi, database='ok')
@@ -119,7 +115,7 @@ def test02_BrokenConnection(self):
             cursor.close()
         self.assertRaises(dbapi.OperationalError, db.cursor, 'error')
 
-    def test03_Close(self):
+    def test_close(self):
         for closeable in (False, True):
             db = SteadyDBconnect(dbapi, closeable=closeable)
             self.assertTrue(db._con.valid)
@@ -132,7 +128,7 @@ def test03_Close(self):
             db._close()
             self.assertFalse(db._con.valid)
 
-    def test04_Connection(self):
+    def test_connection(self):
         db = SteadyDBconnect(
             dbapi, 0, None, None, None, True,
             'SteadyDBTestDB', user='SteadyDBTestUser')
@@ -249,7 +245,7 @@ def test04_Connection(self):
         self.assertEqual(
             db._con.session, ['doit', 'commit', 'dont', 'rollback'])
 
-    def test05_ConnectionContextHandler(self):
+    def test_connection_context_handler(self):
         db = SteadyDBconnect(
             dbapi, 0, None, None, None, True,
             'SteadyDBTestDB', user='SteadyDBTestUser')
@@ -267,7 +263,7 @@ def test05_ConnectionContextHandler(self):
         self.assertTrue(error)
         self.assertEqual(db._con.session, ['commit', 'rollback'])
 
-    def test06_CursorContextHandler(self):
+    def test_cursor_context_handler(self):
         db = SteadyDBconnect(
             dbapi, 0, None, None, None, True,
             'SteadyDBTestDB', user='SteadyDBTestUser')
@@ -278,7 +274,7 @@ def test06_CursorContextHandler(self):
             self.assertEqual(cursor.fetchone(), 'test')
         self.assertEqual(db._con.open_cursors, 0)
 
-    def test07_ConnectionCreatorFunction(self):
+    def test_connection_creator_function(self):
         db1 = SteadyDBconnect(
             dbapi, 0, None, None, None, True,
             'SteadyDBTestDB', user='SteadyDBTestUser')
@@ -293,7 +289,7 @@ def test07_ConnectionCreatorFunction(self):
         db2.close()
         db1.close()
 
-    def test08_ConnectionMaxUsage(self):
+    def test_connection_maxusage(self):
         db = SteadyDBconnect(dbapi, 10)
         cursor = db.cursor()
         for i in range(100):
@@ -343,7 +339,7 @@ def test08_ConnectionMaxUsage(self):
         self.assertEqual(db._con.num_uses, 1)
         self.assertEqual(db._con.num_queries, 1)
 
-    def test09_ConnectionSetSession(self):
+    def test_connection_setsession(self):
         db = SteadyDBconnect(dbapi, 3, ('set time zone', 'set datestyle'))
         self.assertTrue(hasattr(db, '_usage'))
         self.assertEqual(db._usage, 0)
@@ -401,7 +397,7 @@ def test09_ConnectionSetSession(self):
         self.assertEqual(db._con.num_queries, 1)
         self.assertEqual(db._con.session, ['time zone', 'datestyle'])
 
-    def test10_ConnectionFailures(self):
+    def test_connection_failures(self):
         db = SteadyDBconnect(dbapi)
         db.close()
         db.cursor()
@@ -416,7 +412,7 @@ def test10_ConnectionFailures(self):
         db.close()
         db.cursor()
 
-    def test11_ConnectionFailureError(self):
+    def test_connection_failure_error(self):
         db = SteadyDBconnect(dbapi)
         cursor = db.cursor()
         db.close()
@@ -425,7 +421,7 @@ def test11_ConnectionFailureError(self):
         db.close()
         self.assertRaises(dbapi.ProgrammingError, cursor.execute, 'error')
 
-    def test12_ConnectionSetSizes(self):
+    def test_connection_set_sizes(self):
         db = SteadyDBconnect(dbapi)
         cursor = db.cursor()
         cursor.execute('get sizes')
@@ -450,7 +446,7 @@ def test12_ConnectionSetSizes(self):
         result = cursor.fetchone()
         self.assertEqual(result, ([6, 42, 7], {None: 7, 3: 15, 9: 42}))
 
-    def test13_ConnectionPingCheck(self):
+    def test_connection_ping_check(self):
         Connection = dbapi.Connection
         Connection.has_ping = False
         Connection.num_pings = 0
@@ -539,7 +535,7 @@ def test13_ConnectionPingCheck(self):
         Connection.has_ping = False
         Connection.num_pings = 0
 
-    def test14_BeginTransaction(self):
+    def test_begin_transaction(self):
         db = SteadyDBconnect(dbapi, database='ok')
         cursor = db.cursor()
         cursor.close()
@@ -562,7 +558,7 @@ def test14_BeginTransaction(self):
         cursor.execute('select test12')
         self.assertEqual(cursor.fetchone(), 'test12')
 
-    def test15_WithBeginExtension(self):
+    def test_with_begin_extension(self):
         db = SteadyDBconnect(dbapi, database='ok')
         db._con._begin_called_with = None
 
@@ -576,7 +572,7 @@ def begin(a, b=None, c=7):
         self.assertEqual(cursor.fetchone(), 'test13')
         self.assertEqual(db._con._begin_called_with, (42, 6, 7))
 
-    def test16_CancelTransaction(self):
+    def test_cancel_transaction(self):
         db = SteadyDBconnect(dbapi, database='ok')
         cursor = db.cursor()
         db.begin()
@@ -586,7 +582,7 @@ def test16_CancelTransaction(self):
         cursor.execute('select test14')
         self.assertEqual(cursor.fetchone(), 'test14')
 
-    def test17_WithCancelExtension(self):
+    def test_with_cancel_extension(self):
         db = SteadyDBconnect(dbapi, database='ok')
         db._con._cancel_called = None
 
@@ -601,7 +597,7 @@ def cancel():
         db.cancel()
         self.assertEqual(db._con._cancel_called, 'yes')
 
-    def test18_ResetTransaction(self):
+    def test_reset_transaction(self):
         db = SteadyDBconnect(dbapi, database='ok')
         db.begin()
         self.assertFalse(db._con.session)
@@ -613,7 +609,7 @@ def test18_ResetTransaction(self):
         db.close()
         self.assertEqual(db._con.session, ['rollback'])
 
-    def test19_CommitError(self):
+    def test_commit_error(self):
         db = SteadyDBconnect(dbapi, database='ok')
         db.begin()
         self.assertFalse(db._con.session)
@@ -635,7 +631,7 @@ def test19_CommitError(self):
         self.assertEqual(db._con.session, ['commit'])
         self.assertTrue(db._con.valid)
 
-    def test20_RollbackError(self):
+    def test_rollback_error(self):
         db = SteadyDBconnect(dbapi, database='ok')
         db.begin()
         self.assertFalse(db._con.session)
diff --git a/tests/test_steady_pg.py b/tests/test_steady_pg.py
index e566a7b..df88b77 100644
--- a/tests/test_steady_pg.py
+++ b/tests/test_steady_pg.py
@@ -15,23 +15,19 @@
 import unittest
 import sys
 
-import DBUtils.Tests.mock_pg as pg
+from . import mock_pg as pg
 
-from DBUtils.SteadyPg import SteadyPgConnection
-
-__version__ = '1.4'
+from dbutils.steady_pg import SteadyPgConnection
 
 
 class TestSteadyPg(unittest.TestCase):
 
-    def test0_CheckVersion(self):
-        from DBUtils import __version__ as DBUtilsVersion
-        self.assertEqual(DBUtilsVersion, __version__)
-        from DBUtils.SteadyPg import __version__ as SteadyPgVersion
-        self.assertEqual(SteadyPgVersion, __version__)
-        self.assertEqual(SteadyPgConnection.version, __version__)
+    def test_version(self):
+        from dbutils import __version__, steady_pg
+        self.assertEqual(steady_pg.__version__, __version__)
+        self.assertEqual(steady_pg.SteadyPgConnection.version, __version__)
 
-    def test1_MockedConnection(self):
+    def test_mocked_connection(self):
         PgConnection = pg.DB
         db = PgConnection(
             'SteadyPgTestDB', user='SteadyPgTestUser')
@@ -80,7 +76,7 @@ def test1_MockedConnection(self):
         self.assertRaises(pg.InternalError, db.query, 'select test')
         self.assertRaises(pg.InternalError, db.get_tables)
 
-    def test2_BrokenConnection(self):
+    def test_broken_connection(self):
         self.assertRaises(TypeError, SteadyPgConnection, 'wrong')
         db = SteadyPgConnection(dbname='ok')
         InternalError = sys.modules[db._con.__module__].InternalError
@@ -89,7 +85,7 @@ def test2_BrokenConnection(self):
         del db
         self.assertRaises(InternalError, SteadyPgConnection, dbname='error')
 
-    def test3_Close(self):
+    def test_close(self):
         for closeable in (False, True):
             db = SteadyPgConnection(closeable=closeable)
             self.assertTrue(db._con.db and db._con.valid)
@@ -104,7 +100,7 @@ def test3_Close(self):
             db._close()
             self.assertFalse(db._con.db and db._con.valid)
 
-    def test4_Connection(self):
+    def test_connection(self):
         db = SteadyPgConnection(
             0, None, 1, 'SteadyPgTestDB', user='SteadyPgTestUser')
         self.assertTrue(hasattr(db, 'db'))
@@ -184,7 +180,7 @@ def test4_Connection(self):
         self.assertEqual(db._usage, 1)
         self.assertEqual(db.num_queries, 0)
 
-    def test5_ConnectionContextHandler(self):
+    def test_connection_context_handler(self):
         db = SteadyPgConnection(
             0, None, 1, 'SteadyPgTestDB', user='SteadyPgTestUser')
         self.assertEqual(db.session, [])
@@ -202,7 +198,7 @@ def test5_ConnectionContextHandler(self):
         self.assertEqual(
             db._con.session, ['begin', 'commit', 'begin', 'rollback'])
 
-    def test6_ConnectionMaxUsage(self):
+    def test_connection_maxusage(self):
         db = SteadyPgConnection(10)
         for i in range(100):
             r = db.query('select test%d' % i)
@@ -250,7 +246,7 @@ def test6_ConnectionMaxUsage(self):
         self.assertEqual(db._usage, 1)
         self.assertEqual(db.num_queries, 1)
 
-    def test7_ConnectionSetSession(self):
+    def test_connection_setsession(self):
         db = SteadyPgConnection(3, ('set time zone', 'set datestyle'))
         self.assertTrue(hasattr(db, 'num_queries'))
         self.assertEqual(db.num_queries, 0)
@@ -271,7 +267,7 @@ def test7_ConnectionSetSession(self):
         self.assertEqual(db.num_queries, 0)
         self.assertEqual(db.session, ['time zone', 'datestyle', 'test'])
 
-    def test8_Begin(self):
+    def test_begin(self):
         for closeable in (False, True):
             db = SteadyPgConnection(closeable=closeable)
             db.begin()
@@ -291,7 +287,7 @@ def test8_Begin(self):
             self.assertEqual(db.begin('select sql:begin'), 'sql:begin')
             self.assertEqual(db.num_queries, 2)
 
-    def test9_End(self):
+    def test_end(self):
         for closeable in (False, True):
             db = SteadyPgConnection(closeable=closeable)
             db.begin()
diff --git a/tests/test_threading_local.py b/tests/test_threading_local.py
index 424ea2f..b44c613 100644
--- a/tests/test_threading_local.py
+++ b/tests/test_threading_local.py
@@ -3,41 +3,39 @@
 import unittest
 from threading import Thread
 
-from DBUtils.PersistentDB import local
-
-__version__ = '1.4'
+from dbutils.persistent_db import local
 
 
 class TestThreadingLocal(unittest.TestCase):
 
-    def test0_GetAttr(self):
-        mydata = local()
-        mydata.number = 42
-        self.assertEqual(mydata.number, 42)
+    def test_getattr(self):
+        my_data = local()
+        my_data.number = 42
+        self.assertEqual(my_data.number, 42)
 
-    def test1_Dict(self):
-        mydata = local()
-        mydata.number = 42
-        self.assertEqual(mydata.__dict__, {'number': 42})
-        mydata.__dict__.setdefault('widgets', [])
-        self.assertEqual(mydata.widgets, [])
+    def test_dict(self):
+        my_data = local()
+        my_data.number = 42
+        self.assertEqual(my_data.__dict__, {'number': 42})
+        my_data.__dict__.setdefault('widgets', [])
+        self.assertEqual(my_data.widgets, [])
 
-    def test2_ThreadLocal(self):
+    def test_threadlocal(self):
         def f():
-            items = sorted(mydata.__dict__.items())
+            items = sorted(my_data.__dict__.items())
             log.append(items)
-            mydata.number = 11
-            log.append(mydata.number)
-        mydata = local()
-        mydata.number = 42
+            my_data.number = 11
+            log.append(my_data.number)
+        my_data = local()
+        my_data.number = 42
         log = []
         thread = Thread(target=f)
         thread.start()
         thread.join()
         self.assertEqual(log, [[], 11])
-        self.assertEqual(mydata.number, 42)
+        self.assertEqual(my_data.number, 42)
 
-    def test3_SubClass(self):
+    def test_subclass(self):
 
         class MyLocal(local):
             number = 2
@@ -52,36 +50,36 @@ def __init__(self, **kw):
             def squared(self):
                 return self.number ** 2
 
-        mydata = MyLocal(color='red')
-        self.assertEqual(mydata.number, 2)
-        self.assertEqual(mydata.color, 'red')
-        del mydata.color
-        self.assertEqual(mydata.squared(), 4)
+        my_data = MyLocal(color='red')
+        self.assertEqual(my_data.number, 2)
+        self.assertEqual(my_data.color, 'red')
+        del my_data.color
+        self.assertEqual(my_data.squared(), 4)
 
         def f():
-            items = sorted(mydata.__dict__.items())
+            items = sorted(my_data.__dict__.items())
             log.append(items)
-            mydata.number = 7
-            log.append(mydata.number)
+            my_data.number = 7
+            log.append(my_data.number)
 
         log = []
         thread = Thread(target=f)
         thread.start()
         thread.join()
         self.assertEqual(log, [[('color', 'red'), ('initialized', 1)], 7])
-        self.assertEqual(mydata.number, 2)
-        self.assertFalse(hasattr(mydata, 'color'))
+        self.assertEqual(my_data.number, 2)
+        self.assertFalse(hasattr(my_data, 'color'))
 
         class MyLocal(local):
             __slots__ = 'number'
 
-        mydata = MyLocal()
-        mydata.number = 42
-        mydata.color = 'red'
+        my_data = MyLocal()
+        my_data.number = 42
+        my_data.color = 'red'
         thread = Thread(target=f)
         thread.start()
         thread.join()
-        self.assertEqual(mydata.number, 7)
+        self.assertEqual(my_data.number, 7)
 
 
 if __name__ == '__main__':
diff --git a/tox.ini b/tox.ini
index 78dd8dc..0e56291 100644
--- a/tox.ini
+++ b/tox.ini
@@ -15,4 +15,4 @@ basepython =
 deps =
     flake8
 commands =
-    flake8 *.py dbutils
+    flake8 dbutils tests docs setup.py

From f067391bb59ebd76b7e57ba7860ec9c42fce6c7f Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 18:18:01 +0200
Subject: [PATCH 06/84] Convert images from GIF to PNG

---
 MANIFEST.in       |   2 +-
 docs/dbdep.gif    | Bin 2653 -> 0 bytes
 docs/dbdep.png    | Bin 0 -> 3331 bytes
 docs/main.de.html |   8 ++++----
 docs/main.de.rst  |   8 ++++----
 docs/main.html    |   8 ++++----
 docs/main.rst     |   8 ++++----
 docs/persist.gif  | Bin 7378 -> 0 bytes
 docs/persist.png  | Bin 0 -> 7063 bytes
 docs/pgdep.gif    | Bin 2426 -> 0 bytes
 docs/pgdep.png    | Bin 0 -> 3129 bytes
 docs/pool.gif     | Bin 13454 -> 0 bytes
 docs/pool.png     | Bin 0 -> 11963 bytes
 13 files changed, 17 insertions(+), 17 deletions(-)
 delete mode 100644 docs/dbdep.gif
 create mode 100644 docs/dbdep.png
 delete mode 100644 docs/persist.gif
 create mode 100644 docs/persist.png
 delete mode 100644 docs/pgdep.gif
 create mode 100644 docs/pgdep.png
 delete mode 100644 docs/pool.gif
 create mode 100644 docs/pool.png

diff --git a/MANIFEST.in b/MANIFEST.in
index b4efc19..9896216 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,7 +3,7 @@ include MANIFEST.in
 include LICENSE
 include README.md
 
-recursive-include docs *.rst make.py *.html *.css *.png *.gif
+recursive-include docs *.rst make.py *.html *.css *.png
 
 prune docs/_build
 exclude release.md setversion.py
diff --git a/docs/dbdep.gif b/docs/dbdep.gif
deleted file mode 100644
index 8554b62b242c5f9e58916c99d1ea419c18701f18..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2653
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
L02qKg5CH%?zBcu_

diff --git a/docs/dbdep.png b/docs/dbdep.png
new file mode 100644
index 0000000000000000000000000000000000000000..666e3a78c5e9507e722a77a28f6d55f780f1333c
GIT binary patch
literal 3331
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
N002ovPDHLkV1mFbDR}?@

literal 0
HcmV?d00001

diff --git a/docs/main.de.html b/docs/main.de.html
index b26431d..3827321 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -117,10 +117,10 @@ <h1>Module</h1>
 </table>
 <p>Die Abhängigkeiten der Module in der Variante für beliebige DB-API-2-Adapter
 sind im folgenden Diagramm dargestellt:</p>
-<img alt="dbdep.gif" src="dbdep.gif" />
+<img alt="dbdep.png" src="dbdep.png" />
 <p>Die Abhängigkeiten der Module in der Variante für den klassischen
 PyGreSQL-Adapter sehen ähnlich aus:</p>
-<img alt="pgdep.gif" src="pgdep.gif" />
+<img alt="pgdep.png" src="pgdep.png" />
 </div>
 <div class="section" id="download">
 <h1>Download</h1>
@@ -202,7 +202,7 @@ <h2>PersistentDB</h2>
 geschlossen werden.</p>
 <p>Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
 <span class="docutils literal">PersistentDB</span>-Datenbankverbindungen einsetzen:</p>
-<img alt="persist.gif" src="persist.gif" />
+<img alt="persist.png" src="persist.png" />
 <p>Immer wenn ein Thread eine Datenbankverbindung zum ersten Mal öffnet, wird
 eine neue Datenbankverbindung geöffnet, die von da an immer wieder für genau
 diesen Thread verwendet wird. Wenn der Thread die Datenbankverbindung schließt,
@@ -227,7 +227,7 @@ <h2>PooledDB</h2>
 dies bemerkt, wiederverwendet werden.</p>
 <p>Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
 <span class="docutils literal">PooledDB</span>-Datenbankverbindungen einsetzen:</p>
-<img alt="pool.gif" src="pool.gif" />
+<img alt="pool.png" src="pool.png" />
 <p>Wie im Diagramm angedeutet, kann <span class="docutils literal">PooledDB</span> geöffnete Datenbankverbindungen
 den verschiedenen Threads beliebig zuteilen. Dies geschieht standardmäßig, wenn
 Sie den Verbindungspool mit einem positiven Wert für <span class="docutils literal">maxshared</span> einrichten
diff --git a/docs/main.de.rst b/docs/main.de.rst
index f0fda86..75580f7 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -54,12 +54,12 @@ DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen
 Die Abhängigkeiten der Module in der Variante für beliebige DB-API-2-Adapter
 sind im folgenden Diagramm dargestellt:
 
-.. image:: dbdep.gif
+.. image:: dbdep.png
 
 Die Abhängigkeiten der Module in der Variante für den klassischen
 PyGreSQL-Adapter sehen ähnlich aus:
 
-.. image:: pgdep.gif
+.. image:: pgdep.png
 
 
 Download
@@ -166,7 +166,7 @@ geschlossen werden.
 Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
 ``PersistentDB``-Datenbankverbindungen einsetzen:
 
-.. image:: persist.gif
+.. image:: persist.png
 
 Immer wenn ein Thread eine Datenbankverbindung zum ersten Mal öffnet, wird
 eine neue Datenbankverbindung geöffnet, die von da an immer wieder für genau
@@ -196,7 +196,7 @@ dies bemerkt, wiederverwendet werden.
 Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
 ``PooledDB``-Datenbankverbindungen einsetzen:
 
-.. image:: pool.gif
+.. image:: pool.png
 
 Wie im Diagramm angedeutet, kann ``PooledDB`` geöffnete Datenbankverbindungen
 den verschiedenen Threads beliebig zuteilen. Dies geschieht standardmäßig, wenn
diff --git a/docs/main.html b/docs/main.html
index a6625bf..52a6944 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -116,10 +116,10 @@ <h1>Modules</h1>
 </table>
 <p>The dependencies of the modules in the universal DB-API 2 variant
 are as indicated in the following diagram:</p>
-<img alt="dbdep.gif" src="dbdep.gif" />
+<img alt="dbdep.png" src="dbdep.png" />
 <p>The dependencies of the modules in the classic PyGreSQL variant
 are similar:</p>
-<img alt="pgdep.gif" src="pgdep.gif" />
+<img alt="pgdep.png" src="pgdep.png" />
 </div>
 <div class="section" id="download">
 <h1>Download</h1>
@@ -194,7 +194,7 @@ <h2>PersistentDB</h2>
 connections to a database, using any DB-API 2 database module.</p>
 <p>The following diagram shows the connection layers involved when you
 are using <span class="docutils literal">PersistentDB</span> connections:</p>
-<img alt="persist.gif" src="persist.gif" />
+<img alt="persist.png" src="persist.png" />
 <p>Whenever a thread opens a database connection for the first time, a new
 connection to the database will be opened that will be used from now on
 for this specific thread. When the thread closes the database connection,
@@ -216,7 +216,7 @@ <h2>PooledDB</h2>
 DB-API 2 database module.</p>
 <p>The following diagram shows the connection layers involved when you
 are using <span class="docutils literal">PooledDB</span> connections:</p>
-<img alt="pool.gif" src="pool.gif" />
+<img alt="pool.png" src="pool.png" />
 <p>As the diagram indicates, <span class="docutils literal">PooledDB</span> can share opened database connections
 between different threads. This will happen by default if you set up the
 connection pool with a positive value of <span class="docutils literal">maxshared</span> and the underlying
diff --git a/docs/main.rst b/docs/main.rst
index 43b7905..59b0b70 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -53,12 +53,12 @@ the other one for use with the classic PyGreSQL module.
 The dependencies of the modules in the universal DB-API 2 variant
 are as indicated in the following diagram:
 
-.. image:: dbdep.gif
+.. image:: dbdep.png
 
 The dependencies of the modules in the classic PyGreSQL variant
 are similar:
 
-.. image:: pgdep.gif
+.. image:: pgdep.png
 
 
 Download
@@ -157,7 +157,7 @@ connections to a database, using any DB-API 2 database module.
 The following diagram shows the connection layers involved when you
 are using ``PersistentDB`` connections:
 
-.. image:: persist.gif
+.. image:: persist.png
 
 Whenever a thread opens a database connection for the first time, a new
 connection to the database will be opened that will be used from now on
@@ -184,7 +184,7 @@ DB-API 2 database module.
 The following diagram shows the connection layers involved when you
 are using ``PooledDB`` connections:
 
-.. image:: pool.gif
+.. image:: pool.png
 
 As the diagram indicates, ``PooledDB`` can share opened database connections
 between different threads. This will happen by default if you set up the
diff --git a/docs/persist.gif b/docs/persist.gif
deleted file mode 100644
index 43c3d0176ab4fc7bfd38467e7cc95159ab46fa04..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 7378
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
EJG2-cng9R*

diff --git a/docs/persist.png b/docs/persist.png
new file mode 100644
index 0000000000000000000000000000000000000000..e76856d7be4aaee2d008839e503b5c7d513f279d
GIT binary patch
literal 7063
zcmZ{pby!qe*!K5;zyaxy?vw@vq|u=fBqb!JyM`LN2au8m>5y)u96)Jl1jzxBZl%M)
zcQ~GN-gDmX`u^D0-YcGKKeP5)zjfbhqSVzC@NlSb006*KQk2yM08j$zTKEH10d%P(
zb)s%}tRJgB27tQwyFbk^P-O;lMNL%zfU*HVP#6GQp{jzm0KkJE0Cr3PKr9^q$X(vF
zX-J?N&>)&>TEGo=DlzkI>7|N-EC7_pG7F+gSP&&uIjl8oDnbJ6#2Ln70H8`#l6|c0
zJ-?ql7DJ<u+TV$@@jf$@_BFnxL3_Cr?$L*Pq+qRC&i$N5v^u<9tHju?J_ex}J?_~d
z9NRi9dd$ZJF?tr=OoalWHUuyMn{$?Op3ks(G<{sVdj`GpBPWlA80jmK=yblYfy&Cg
z@}2Te0|UBqhwfXSMx&#hW&MtIE#ZEyFb0<=WC$RiULcI}<sT1v2(5nN2F-p%M`Hpr
z%oy|*5(i=k!x)DN!Rd?<{9s(O2#Sxdo<^j%X)2eQ_f08~Q=d9t!5=#lTq?Tzd#mn>
zfFM)Q#+G=OWrgXTuDH>|1r~Sv`TSRd7O<tF1GI^|*{ziRDF>9X@Lld-9S`4U&U9jZ
zM|2{wyS`8-HGLgA*q7Xe!K7mRsCFv9>{lXk*TK(^;Y7|kewbe?ith!zjg2Z7lE$0s
z8r{o8N?o6k>x-`sCA`yg?5eCD?Vg$zK!+r2iUh}LdVyHJEP2q^VLXMGX^TUH5kH?A
zm3eBvT>nw$DOue$m8Cofo|Z4vt*LTG8cJAPZ8}m^1)7bQZIvcWw=6}JHBacrW+vDO
znckzXcy~QhS$#(Pc5kA_=j4X~iPzjcNLBg4p*{ncZsP13$l;Oh|Kxx54&QI8U|+~Q
zwf!Ulv^alZB8w5kJ_Vom1p$?n8s~P}-$p;ih_)Xrhym}5_ICkOVTd6g(6GoVvE8&4
zN?+?i6^plbV`;dB?bu21kZxr<OWO1tG8HCU0rC$Fwo9CESl@(GWsxB+sJi;#;pAdF
zZd2Sd<;|Yy6mCS3Lcnmo)<h|9(~rD}o%;^iGd4D|_Jx6<7PKz8EiURGcL4~e(AJ1C
zHoz}Et)49cP^s;tJT~MrO}QzbDQNx`me#LviOIJdd)G+TqbxRQd9~d#G<LPF%dj9P
zpsCg7O|oX1GgxGvpE}k!Z8fA?hdt2rCF}qmP@Q}=Md!JPSrN}*OpS~&1|dw)gN#nn
zfFNDxkB)(^A3Eht;K0e4Zwk!~oyoMi&_;!Up(6SP2-@5rbRK(|Yfz8ve|j>-B*0X#
z2~fq?dazD>mBeUx-K~TqCej$;USb!+@FPf<?zdkJ3@!y0T{&I9SLJoy0cZj+zit~o
z+5Fy+LuHy0umi6$v9||0XD8rKFuPp8h8_k?9XcZGpXKR}nF{r1+!LnSmdE8B=ug<G
z290Lu%9|sx)uimryL<i`C!_Qiu@}A1CnCzSTEc=QmArCx8SMZysRy%H%RMjHJ0IcY
z{aAkM8!kMqUjuRubf>EMbrWk$^7>Ry1giq%?Tl2&o`p_gG{-eOxZ`y%r7gcgMR}#0
z7TL=;oeOy{d_(Sn5u~-Vl^JK^K&CrA(bN*%H-XoFklj(k+%^%n+k4e^^Q{!>gVWV^
z-F9(CPX%!Pf=KX&%{Y=p=F%#x6QJG&J<Xd5@xlza^tq-_&8Jpa0OMFK>EjatrQZVb
z1Y@$I(xPlS45<wDgU;~t8$5mYGAH#&tS)0#GHR~+Uee7%A9zhv9TzSkokZ=C(nD}8
z(WCg?D5Ih)u5gmEw>1vj!v&PAW*z!e?d6Q50RzNLR=*N-lu=ap&488a`W|v#8qUE}
z9ouRW%NbJ?4sib=2dWQ8IAgh=e}*e5pc7Y}{4FNGp#azDem|Wi4m(XTf6>$1E{Tlm
zk6cm)65Q1B!Jq2f0gf0(8QZ7#;~7~W<C@7U;ocwofm19{B#}x(Mcip~&Atn+W|w*C
zOUYOV=O+3$hk7v5x<GT_^txqmt|;}FX8VXKoGM}AYpL+8-3e|X4#uV@73(*T8R=2^
zD}Hnp#%=$_LHg-o#M4Y#&&88Ux<<qLJ{db&vUT5+{@=B%;6nB(1O3AN<Ih8?C042J
zyw3_1j-)MwrrV8=ui^@{%JO%JP1oa>rg-s+6a=XzpO;H=h&nLQI)w9H+3rvnOfLXA
z8-=N(zV^1yC7c~8@#@Mn&sic>OG2xz>`?8VFgr@wh>GIlCA6S2lZ#p1^EYIa$Cj;o
zwXT`d^XPd{9L18*(_U|bk)Z%Lt9^~olwGaQS=nuyq(+qI3C7Vgl7nWeklGFkl?tB^
zTCY1L?Y7@H-*ehbphFItk!+i~o3=bZu3ydoLG2Vb;Um~45hv`Ca!j<9ff4o@)Hg|{
z^4JzyKF($h5>!^~r*!^<W{HYeS?J&<VLE!9j1EeCU9i|8k2>;c+0}`@B36Tm;qwln
z7s#f_+mSs}STnwNyG8u7A(*$dRBnj5@CwDhu{?}tD8WykcC{ZZunfc<ED(w^P$;`}
zkfa{a_Jv&{iANEN!^|QfqZ;U2ohHkj7FJa$*HdsiK^xTqtJEl|_Kafd(2P$Bv6kt%
zcAj*t?w;cMIu$@QHQ+h@Y1IeN3w&Fgm@{B%!1G4yxVuU0&~A%MoU)Nk<8xNmU=923
zT&p?T=}Z!%$#fod1wgm8OFtf1jW(9LErfnth-s>e(D%j%RQNUk;86AYR5X9xj;=%p
zX$_c+T8?MR;<;$)TBsje!X|(K4;|Fj=D?kQl?k%5aWLI|804)(U-L}VSO3Y-^!o?O
zyH{9HXchMKQ{>0qLFn1na5dzq`2ZInld=^bBd)HD!~(vqiA+HBe{d48G(N4_Ff-Ux
zv&!l;XA**6GO09n5tc*^iQ(qClLU1fp>srIkJmH>HkKsyZ@ZEhh`-xt%{M3sU|#j(
zOezI+VTtFoVOvBEO=6Zx*`Z$??e{_bBxf(yZGrJ99Z6%g+pO$)HI5m08d{zY6$vF8
zSd*Q-5`xd0!e9e8v^`y32TiaoZkHkz$UPekuV7f)U8917kQZ_@YO@~^7)%A7)P-mM
zO<UaKROP3Hz&O=2t_CVeNO7MQbyk^EXI5bLiEa>_q#UnQvgo-@Q(eYRz1KHq@W#0o
z{UX>d{5f^lPZ=_K$DafnbDU<xYme`w{+=9l&K$Oi{U4oZC7OGYxY!w*3=UUlC4<D?
zPHb2d|0V_uH{(RZnvuPrrBaFB<DT0SeEnexQ^mvJuT#hU2f<%Uep$Z^O9w@q(EV``
zcrz<W=-@=?*l7Z3R~Z7p->^ao*+YKheL0t@eLQ{K(Dh^(07D7x9Qxnr(Fb>QmL_io
zJ!iB!8Pi6N(YIrToU(a1ub+nPgREY)RNidHBhWvbRlp4CHhZ>)mOB0{Uy&5YRg)nO
zCYe6#@Pj)j1b>Sj%&4K4Q7G7vdk|lKG^?)<eJ|CvcdFE-vel)#`*>|Fq4z%={I_1Z
zZOFmXXq_@>)K(N-M&neh0IOgm4}l+t`v&mkX)}l}d-<Y|O3`{3?8>;gX<FZEEJIvu
zZ$kna_0-NcdyK>19btPkl!N93L|TFjk=(Zn@VgU?rlvlzn~o{fVr4v`b-pG1gf{H-
z&H%djs{|iheWt(V+W*a~&iUY+=&7mIhB%0^>EjUVz{`OP#~-!yD0uRvnfQO4dMX{C
zpTPWE4Bo*{AUgw}5n8R;E4C~mOX!3Vo(g5=IS(2l2GUZx{Z_k^zt~*+wJbJSwW=&{
z)Hki$Dr&*0IkOzzYJyL@vt{gpmZnNaH|sza4|`Cet7(-vLUqY`N45A%Fpkm5+?2+j
z$h@M>43HGzdpsoqj40D(z1TpXA=xn&($g<I&@r_fIT+xxy%tkHMa&Wz0GTNU0Z_Kr
zF(;_?*WYoC<^9OQD6FG?qHHQp#4MnEj<(h8`10DS==t$ckyie*BJx2wC^TtXR2IFf
z+9=?ZEvVVkO+{8pxx&Zn!85pC^^8dBCSeyK)5}Z$r=GwcS=6oY5sjS7B!>EU@vt>8
zdYL&1mA5;lw@pYp7amJ=a#gGtY()G*7Ct-F37io-4jNo9%Go|A1RfQSPqs8%%))6d
z&GD-ib?sMwXaGE8)~8{{O`Z1BW29<pf)%Q%v&M4#G3JgLbLNp?yH@4C&&TBVcz$ng
zi9Zt7;Vf8H-`%1zEeLMa_Ftb*&|d&|+v+RN+aSDYW7+|5?r;M5PZnR~nkfu~q2>r7
zc$VRqqF(m5M!)E?+)=(SBwaczMbvhdv~OdUnK0ub(6y(u$0#eCTZPY2SePq#9+zbV
zmOUA4dvSpX3$@?D#$faq5Ni;3tekMJR?t}^u7;RWxZt}!Zm6g@w&A4yg45o3bdkOt
zUuNV(L!1Atwyq?mRxP5ZZPL}$frew3Q|J_8SgRuiBP1e1kw#XQfR8A9fAS1_hqtk`
zUB+{Q$Ai4|{7mvZ^_s=V-h&(f&S5av=)2PPamj@pF8}y<&w4ysn5v_-mFigu2){)4
z&=uV3&cZ*HZLlbSsYC8h%G2vi5WUP=z;Pl<M!c)MK&~6f^=;=_%HDl?ZcX;nO+ZRl
z*&jy+yleugJBJtdEFfiFQx9fSaH2el5iwQfB9}jIUSq@2`-0pDSeclp(Mv_je202e
z#LJqKAezA`%;1-fv?*)l+JiYqAoCi=X(RME4`d)36|2Rf>1zvsp^RWqd@3TuH_p;j
z(V;Vpr{26mv3IBM6IR@o-#4`p*(oDn4?CNV$)HhXW?cC_!ib>_(~H-6j-ahdS_3Z*
zQaJg8HOPMqZU`RgNaWZ)!6*HKbBUdXN6gVr{5Kng*HZYn@_H?CbKDbSqPrn@Nj17T
zJ1g~cK4Bn?_hI;cO?0wqqyKeg|97dc%n_!jRLQA?YY&-U-6gY1KvR}e22DvU8$x*S
zy~%l}kiF_|Ig)&bQ;2-o^`g<+_*M$dK6Kr$?kW8DK!!gY6)C;q^497ilRqcRXu1u&
zyq%GU5y^)P#LxL7C5tLk!-&KmuI1m!N%UVgeipQ5Xb~n++x^e4_+P2;mv?kHrcg7r
zKGQEnvm?vj9C%c>cHwSq>1?N1#dP9U`gGulaTI#kQx>P6I5Y{8e_B#E5f(!OudG&4
zitO#l#Y2UIh{`)EJZ<&z3!9;38SA|MyB_9u1@l#IvD0MpH+IIe+R}^>%ybU6XKWRU
z>rgCC$b`MSf{l(dy7!Kccfs0uqFueStGr<+i(s-^+T26s&r%FC;u>!PJPpF58n;$h
zr45i!L_-yPZab>&u0mVRRcdKpKjTo|m7ij@*Ws%vJL8V*$R#eWv{AJh5o8XSdk)Xl
zb{=aJdOmDZwSlpG{83LbtuVlO`A9s{!v1#W$w+KnLdK5&QMIv7z9fPUS3<mS5<TqD
zuc!Vt*%<%n^dMH_mMcC<l}m#N;irfb>$iu)=n?BmlE0Hn+)TRSsPYZ+|GcUssTM$A
zc4qxnI#Gi9nrJxv+W+U6CBbvNGM%uTu-8YwDe#~9FhG$;HaRxFpeC?TE@3`^D{3Lo
z{p%;=6SjYIhB~Cj5<7|M9Y3>KuSfCJh}k-nv8|*UI$`&@w>cu_JX;bPQTYpl2)R5W
zARhzUO7dv_-j0nmGX>h0rpbSJf_r(y6vSl`f7!Zh=yliH4W=qKaDwZz%L~AxQu_N}
z#lFN4JLCAGvBO&%KqB**cIq2NT}f;S9qA2~+z0ulj5bZj7ye@EUwZd&2Kn<Ex@wFT
zN9U^XOe7Zy_L&g}Ptp#uYeAAT2E5gq1{Li9(4rvAJ1<Oj8_j07%PXt#u<SKy|0bQ@
zS<jgi1Jq({Cp%i+qYO`?G#`rdVpE8Us%;`-uC#b~vBODPfd6MxaJN|XNJ?Fi3YB&)
zIoz>6?^nqA<8ym-AeXCHDqM0v0t9R^%d8T;tCp23Ojq{ZqawbsGcj~*S1P`F&1<Er
zNHLj#1t4ZYK`OGcLg~HNvTuV9LC4l#pWda8E%gp0EslLhOMJ6qSHUt<tURBND8LFu
zuVb7p)&WN7?Ssk=sJ_1Wz*SgxbB8^*sm=^|-+W_k(XBA#^g*golNL2B(|dLemY$|S
zUT0Cv91oOVZ(KZgF%*$rlfNFKzfI+GXb|%**o2R8!jn`tX6YlVuRIkhD!a3Aes5Q(
zaq-iQjDBOZRK>kv(mJ@6M#sc@KkDJdW{eZ$GJ|2KhnuQZaD+BW%q&zQt_!tn@y<A=
zz3_j$-ZLTkJcxC1es_SGm+R{#$5t6%LOM<T|DFQ<HMb<F3*5?@Y|7Bu)|)%{+l^$(
zf((3Mr7lq!=t@M|9EbjaS_J9$#Vs@sTnzq^HuslLdO*(Um|B_sXgQSDl@lL|(O#oW
zqP!NG@xa|mlz3V}BnZ;gJo&U<iz3f!b<M~KS!)5@YYrq7jm0gIh`4p&|BIO0#3<c5
zpMACbBK{!BL0IYl4P(zBlY8)@KUav${XzfXyIdnF(;3aozi@`?&xmdjE^y164mS3j
z6P_^x-ureOqL7jy2FcfeQlD-J>;8*dSGb~iCv%;5U7%gkO{l1B2`{Cw24xAaR@4}T
zWSn;lt(q0Azz)&t!M4d7`7y>>Voxfd)LmMg%&Ti}j00zaIOdAH>S<es-O`r$yFO8Q
z)Sz+MdnyDdEzAU$FQ>*$p^aBg)nntPHbB{Utar$z&K9H<-ud7fMXxj3CL`<PZ>=4~
zOKo&}KM`dLp}d!;^||1aL^D+51H%d*#>k&<t`w7Rd}L<4b8hoUv`vN!poGq4_+eF-
zWqftmmO+R^L<{Lq;UtHzd2(v&ivKTIy}}E)F4Aas7@aLQ3Cfgs^e`$A^~T4x$@j0s
zEl+*jaO20@u(~W?j#j4ZWPVoh*k^S4_-sZq`we%ZoAhcRhx^v|J7nJ2TKKa~5FIzZ
zn7_^u|1pUu^2?`Y#eha|;Rw>Y?0M&hN8{`d{!+|0)(V7zjJ~%gkHU9gL&R(f|I#mW
z7kcfak=34Y88!$%zD19DQ-i~m4ol{`NJ1k2dO98?FIDUE%@D!{VO`<6VjE(sN(-kv
z2>(!ouix29$CK@9reI~hz3Yu|dcSZ7zeMs;5$?8Jppa3~$rm4gOaHO{*B=@~LmUek
z^x7v=^nLt>)1i&LoRiu<5=#6HMbCDe_W^a~vQD4wYQ1(?bEm8GSwFb~DkRi#f9R8c
zyPIWfo9-d^`*9f>Hv5=#YT1QDa!Fi23h(P|0CMAr4TzD&?l}44<8(z?JhUf$D$X+d
zF1UL>09t0`4yr#3eU}%fLS>YqUur(-t4PDUjc?qEG`xRm62GSeAB*Qm_f)K{^1npY
z*cxI2wIpPXawo{%M;}dQ%@x_n=xv2zur`y!BI%jCCA5^YzEQB!dnq7bxEN<gb!Q3Z
z{5y%4W`tiO4bR+hZ=Vk^u1i-emHI~sE!pe~giu#22ZI+DCUA9ko{ue`t|=2#P&;{m
zX4-AOKZ%k=VhgO)&wW2)<h8jVWcl~IOcyx*_yXTFuF0P^a=}<-B3O;af0SYFi_xj*
zz^C%}iNGwwWxd`vr)-YE&wL!5(D_J-FU$jtbOWro9OFWeag|e-mp;R_S{>b{GRN*2
z!Zi+YVT+W<8Bm>+k=HJ358SlNvsw1$INw55))Z%{{aa&S*wSG8tqOITaYZrbJBMvP
zvMY9Bxga3%!8gFB!Ttr-s5TcqjwAivZPWo~Gf`w?+XQ9p^RdYNtk1so9;syxm!(>h
zHhi$FD3C}GlDC(qz0X0+-eWpa#4IN#k3m^M2`Q>oY~lyArgfT=*yLI5A2qkAkD9JN
z)QTgeFraoyw;D)f0%xPgy;e}ru8!q<cp|Z8wY!igcts@aHj%}ACq?BmS@apR3&;J-
zA@9Pkfmd>v0bz0b9wv%bn?LE^jDx!`?_x*N76mz`BxB9JkEn8y#u;oa4H^b#cHd`N
zQp6ErUEp@4gpQ<zLQ+z;9OYMNatd6?=qoEKWg~qKxWaS6Pw4Bvc1+iG_eYJq5Pt$T
zrb|4RhT{I@;BD~O7-H$vEsc((4X3H+Hm7OPE@@VXiEpk3LiQQ8a7_YK&8O1?+ypGt
z12Y_g_O91P5-dWk{lCZTxqE^O__SC0L&-WK%%I?ck<~o$iDw%c8GVE)sz);XrxdDA
zFk-GgtLVDSjh8vA*#xOiAE;~j39v|+*ozjHwA+hJi+Fru@5__0pvbm$&L({ATcn5X
zO%>05UJtFe+Y+~AOEbQ1ZB2JlzC6gjv#TNH<%=bxqpJSy5stHE%fexIThA_2+xL;e
z+GX$sE{M<DF*k(bBi`TQ_y3WN8G=P3o7X|j(N1sJtv)5Jy*ZSLGF0R@j{pWmmxl&$
zsWh`iwm6ENZqN5^h-^5g<&UJN<KDXGGFpujzHbVRH)w=g9iWz~6Os3mg+u16vC&DV
zg6&0X*tbA}jA$&GQ|({h<h0Y~n=bGQE5-`97h|0ES%$yS9YOF*s;5Y<O&Uu<KM9eW
zR<>TBTgQkW*eJd&82RSU)E?OrgW2`tLi$$CwL6KO>%YXryR@OKdDVAC0+ckj1dl>_
zuEvZBb0L4mS-~`w5vBQUMWj-T6S0Mk&40YFUYdKg6m%n7R%m}UTA(QcnTXAS-7i>X
z<uvnSqpze~0Dp@A#|M^p_o?Tc(L~zP3l4wRYm{hs@QRx`>4&I?j}?1Vytx8h1`U3y
zNRD`pLUqyyavaOr`(5VZD@T^9o|63y`XB=xJ+ub@KSXMjCL~oqOedhn_z772<uZQ}
z3~wS1=S($bQ2$r4Lge%y7G@AjF>^Od)C~~i7v$&R7v&KY))sgq#?LP%B*Minh<a$^
qAMpH7fTOd8t(DJz0v?J92#N8F{ths!DSd?s0F>m^WUHT;hWroam`vvY

literal 0
HcmV?d00001

diff --git a/docs/pgdep.gif b/docs/pgdep.gif
deleted file mode 100644
index 9bdfaced5b7a62636c713fa34879e583ba254cbc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2426
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

diff --git a/docs/pgdep.png b/docs/pgdep.png
new file mode 100644
index 0000000000000000000000000000000000000000..f235e436be4d796378650186b8fbd7fa1bc94e84
GIT binary patch
literal 3129
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
TVXvr+00000NkvXXu0mjfk(cn*

literal 0
HcmV?d00001

diff --git a/docs/pool.gif b/docs/pool.gif
deleted file mode 100644
index 105b3c8b17a13656184440407c4e139e9856ccb1..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 13454
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

diff --git a/docs/pool.png b/docs/pool.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6677b04881669f261033623168bbe5f49118b04
GIT binary patch
literal 11963
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
E2RKJmU;qFB

literal 0
HcmV?d00001


From d01143878ab5d2ce0dad0c359c0896077a0b1baf Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 20:30:52 +0200
Subject: [PATCH 07/84] Adapt documentation

---
 docs/changelog.html      |   1 +
 docs/changelog.rst       |   1 +
 docs/dbdep.png           | Bin 3331 -> 0 bytes
 docs/dependencies_db.png | Bin 0 -> 4541 bytes
 docs/dependencies_pg.png | Bin 0 -> 4305 bytes
 docs/main.de.html        | 281 +++++++++++++++++----------------------
 docs/main.de.rst         | 279 +++++++++++++++++---------------------
 docs/main.html           | 253 ++++++++++++++++-------------------
 docs/main.rst            | 247 +++++++++++++++-------------------
 docs/persist.png         | Bin 7063 -> 0 bytes
 docs/persistent.png      | Bin 0 -> 7038 bytes
 docs/pgdep.png           | Bin 3129 -> 0 bytes
 docs/pool.png            | Bin 11963 -> 0 bytes
 docs/pooled.png          | Bin 0 -> 12053 bytes
 14 files changed, 470 insertions(+), 592 deletions(-)
 delete mode 100644 docs/dbdep.png
 create mode 100644 docs/dependencies_db.png
 create mode 100644 docs/dependencies_pg.png
 delete mode 100644 docs/persist.png
 create mode 100644 docs/persistent.png
 delete mode 100644 docs/pgdep.png
 delete mode 100644 docs/pool.png
 create mode 100644 docs/pooled.png

diff --git a/docs/changelog.html b/docs/changelog.html
index 7db17cd..fc37f48 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -79,6 +79,7 @@ <h2>Changes:</h2>
 Particularly, you need to import <span class="docutils literal">dbutils</span> instead of <span class="docutils literal">DBUtils</span> now.</p></li>
 <li><p>The Webware <span class="docutils literal">Examples</span> folder has been removed.</p></li>
 <li><p>The internal naming conventions have been changed to comply with PEP8.</p></li>
+<li><p>The documentation has been adapted to reflect the changes in this version.</p></li>
 <li><p>This changelog file has been created from the former release notes.</p></li>
 </ul>
 </div>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index bf41adf..25b7d88 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -19,6 +19,7 @@ Changes:
   Particularly, you need to import ``dbutils`` instead of ``DBUtils`` now.
 * The Webware ``Examples`` folder has been removed.
 * The internal naming conventions have been changed to comply with PEP8.
+* The documentation has been adapted to reflect the changes in this version.
 * This changelog file has been created from the former release notes.
 
 1.4
diff --git a/docs/dbdep.png b/docs/dbdep.png
deleted file mode 100644
index 666e3a78c5e9507e722a77a28f6d55f780f1333c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3331
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
N002ovPDHLkV1mFbDR}?@

diff --git a/docs/dependencies_db.png b/docs/dependencies_db.png
new file mode 100644
index 0000000000000000000000000000000000000000..f460fc02d6bf86de7e2ff5caf79729dc3badcbf9
GIT binary patch
literal 4541
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
OE979~Y+Yp;koteBsk^NJ

literal 0
HcmV?d00001

diff --git a/docs/dependencies_pg.png b/docs/dependencies_pg.png
new file mode 100644
index 0000000000000000000000000000000000000000..380def9a0bba68c76ba73cd87e9d946f6c8147b6
GIT binary patch
literal 4305
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

literal 0
HcmV?d00001

diff --git a/docs/main.de.html b/docs/main.de.html
index 3827321..b744c1f 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -19,108 +19,108 @@ <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <div class="contents topic" id="inhalt">
 <p class="topic-title first">Inhalt</p>
 <ul class="simple">
-<li><p><a class="reference internal" href="#zusammenfassung" id="id4">Zusammenfassung</a></p></li>
-<li><p><a class="reference internal" href="#module" id="id5">Module</a></p></li>
-<li><p><a class="reference internal" href="#download" id="id6">Download</a></p></li>
-<li><p><a class="reference internal" href="#installation" id="id7">Installation</a></p>
+<li><p><a class="reference internal" href="#zusammenfassung" id="id5">Zusammenfassung</a></p></li>
+<li><p><a class="reference internal" href="#module" id="id6">Module</a></p></li>
+<li><p><a class="reference internal" href="#download" id="id7">Download</a></p></li>
+<li><p><a class="reference internal" href="#installation" id="id8">Installation</a></p>
 <ul>
-<li><p><a class="reference internal" href="#installation-als-eigenstandiges-paket" id="id8">Installation als eigenständiges Paket</a></p></li>
-<li><p><a class="reference internal" href="#installation-als-unterpaket-plug-in-von-webware-for-python" id="id9">Installation als Unterpaket (Plug-In) von Webware for Python</a></p></li>
+<li><p><a class="reference internal" href="#id1" id="id9">Installation</a></p></li>
 </ul>
 </li>
 <li><p><a class="reference internal" href="#anforderungen" id="id10">Anforderungen</a></p></li>
 <li><p><a class="reference internal" href="#funktionalitat" id="id11">Funktionalität</a></p>
 <ul>
-<li><p><a class="reference internal" href="#simplepooleddb" id="id12">SimplePooledDB</a></p></li>
-<li><p><a class="reference internal" href="#steadydb" id="id13">SteadyDB</a></p></li>
-<li><p><a class="reference internal" href="#persistentdb" id="id14">PersistentDB</a></p></li>
-<li><p><a class="reference internal" href="#pooleddb" id="id15">PooledDB</a></p></li>
+<li><p><a class="reference internal" href="#simplepooleddb-simple-pooled-db" id="id12">SimplePooledDB (simple_pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#steadydbconnection-steady-db" id="id13">SteadyDBConnection (steady_db)</a></p></li>
+<li><p><a class="reference internal" href="#persistentdb-persistent-db" id="id14">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#pooleddb-pooled-db" id="id15">PooledDB (pooled_db)</a></p></li>
 <li><p><a class="reference internal" href="#die-qual-der-wahl" id="id16">Die Qual der Wahl</a></p></li>
 </ul>
 </li>
 <li><p><a class="reference internal" href="#benutzung" id="id17">Benutzung</a></p>
 <ul>
-<li><p><a class="reference internal" href="#id1" id="id18">PersistentDB</a></p></li>
-<li><p><a class="reference internal" href="#id2" id="id19">PooledDB</a></p></li>
-<li><p><a class="reference internal" href="#benutzung-in-webware-for-python" id="id20">Benutzung in Webware for Python</a></p></li>
+<li><p><a class="reference internal" href="#id2" id="id18">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#id3" id="id19">PooledDB (pooled_db)</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#anmerkungen" id="id21">Anmerkungen</a></p></li>
-<li><p><a class="reference internal" href="#zukunft" id="id22">Zukunft</a></p></li>
-<li><p><a class="reference internal" href="#fehlermeldungen-und-feedback" id="id23">Fehlermeldungen und Feedback</a></p></li>
-<li><p><a class="reference internal" href="#links" id="id24">Links</a></p></li>
-<li><p><a class="reference internal" href="#autoren" id="id25">Autoren</a></p></li>
-<li><p><a class="reference internal" href="#copyright-und-lizenz" id="id26">Copyright und Lizenz</a></p></li>
+<li><p><a class="reference internal" href="#anmerkungen" id="id20">Anmerkungen</a></p></li>
+<li><p><a class="reference internal" href="#zukunft" id="id21">Zukunft</a></p></li>
+<li><p><a class="reference internal" href="#fehlermeldungen-und-feedback" id="id22">Fehlermeldungen und Feedback</a></p></li>
+<li><p><a class="reference internal" href="#links" id="id23">Links</a></p></li>
+<li><p><a class="reference internal" href="#autoren" id="id24">Autoren</a></p></li>
+<li><p><a class="reference internal" href="#copyright-und-lizenz" id="id25">Copyright und Lizenz</a></p></li>
 </ul>
 </div>
 <div class="section" id="zusammenfassung">
 <h1>Zusammenfassung</h1>
 <p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a> ist eine Sammlung von Python-Modulen, mit deren Hilfe man in <a class="reference external" href="https://www.python.org">Python</a>
 geschriebene Multithread-Anwendungen auf sichere und effiziente Weise an
-Datenbanken anbinden kann. DBUtils wurde mit Blick auf <a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware for Python</a>
-als Anwendung und <a class="reference external" href="http://www.pygresql.org/">PyGreSQL</a> als <a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a>-Datenbankadapter entwickelt,
-kann aber für beliebige Python-Anwendungen und beliebige auf <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a>
-beruhende Python-Datenbankadapter verwendet werden.</p>
+Datenbanken anbinden kann.</p>
+<p>DBUtils wurde ursprünglich speziell für <a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware for Python</a> als Anwendung
+und <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> als <a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a>-Datenbankadapter entwickelt,
+kann aber inzwischen für beliebige Python-Anwendungen und beliebige
+auf <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> beruhende Python-Datenbankadapter verwendet werden.</p>
 </div>
 <div class="section" id="module">
 <h1>Module</h1>
 <p>DBUtils ist als Python-Package realisiert worden, das aus zwei verschiedenen
 Gruppen von Modulen besteht: Einer Gruppe zur Verwendung mit beliebigen
-DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen PyGreSQL-Datenbankadapter-Modul.</p>
+DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen
+PyGreSQL-Datenbankadapter-Modul.</p>
 <table>
 <colgroup>
-<col style="width: 29%" />
-<col style="width: 71%" />
+<col style="width: 28%" />
+<col style="width: 72%" />
 </colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Allgemeine Variante für beliebige DB-API-2-Adapter</p></th>
 </tr>
 </thead>
 <tbody>
-<tr><td><p>SteadyDB.py</p></td>
+<tr><td><p>steady_db</p></td>
 <td><p>Gehärtete DB-API-2-Datenbankverbindungen</p></td>
 </tr>
-<tr><td><p>PooledDB.py</p></td>
+<tr><td><p>pooled_db</p></td>
 <td><p>Pooling für DB-API-2-Datenbankverbindungen</p></td>
 </tr>
-<tr><td><p>PersistentDB.py</p></td>
+<tr><td><p>persistent_db</p></td>
 <td><p>Persistente DB-API-2-Datenbankverbindungen</p></td>
 </tr>
-<tr><td><p>SimplePooledDB.py</p></td>
+<tr><td><p>simple_pooled_db</p></td>
 <td><p>Einfaches Pooling für DB-API 2</p></td>
 </tr>
 </tbody>
 </table>
 <table>
 <colgroup>
-<col style="width: 29%" />
-<col style="width: 71%" />
+<col style="width: 28%" />
+<col style="width: 72%" />
 </colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Variante speziell für den klassischen PyGreSQL-Adapter</p></th>
 </tr>
 </thead>
 <tbody>
-<tr><td><p>SteadyPg.py</p></td>
+<tr><td><p>steady_pg</p></td>
 <td><p>Gehärtete klassische PyGreSQL-Verbindungen</p></td>
 </tr>
-<tr><td><p>PooledPg.py</p></td>
+<tr><td><p>pooled_pg</p></td>
 <td><p>Pooling für klassische PyGreSQL-Verbindungen</p></td>
 </tr>
-<tr><td><p>PersistentPg.py</p></td>
+<tr><td><p>persistent_pg</p></td>
 <td><p>Persistente klassische PyGreSQL-Verbindungen</p></td>
 </tr>
-<tr><td><p>SimplePooledPg.py</p></td>
+<tr><td><p>simple_pooled_pg</p></td>
 <td><p>Einfaches Pooling für klassisches PyGreSQL</p></td>
 </tr>
 </tbody>
 </table>
 <p>Die Abhängigkeiten der Module in der Variante für beliebige DB-API-2-Adapter
 sind im folgenden Diagramm dargestellt:</p>
-<img alt="dbdep.png" src="dbdep.png" />
+<img alt="dependencies_db.png" src="dependencies_db.png" />
 <p>Die Abhängigkeiten der Module in der Variante für den klassischen
 PyGreSQL-Adapter sehen ähnlich aus:</p>
-<img alt="pgdep.png" src="pgdep.png" />
+<img alt="depdependencies_pg.png" src="depdependencies_pg.png" />
 </div>
 <div class="section" id="download">
 <h1>Download</h1>
@@ -132,31 +132,19 @@ <h1>Download</h1>
 </div>
 <div class="section" id="installation">
 <h1>Installation</h1>
-<div class="section" id="installation-als-eigenstandiges-paket">
-<h2>Installation als eigenständiges Paket</h2>
-<p>Wenn Sie DBUtils für andere Anwendungen als Webware for Python verwenden
-möchten, empfiehlt es sich, das Paket auf die übliche Weise zu installieren:</p>
+<div class="section" id="id1">
+<h2>Installation</h2>
+<p>Das Paket kann auf die übliche Weise installiert werden:</p>
 <pre class="literal-block">python setup.py install</pre>
-<p>Sie können auch <a class="reference external" href="https://pip.pypa.io/">pip</a> für Download und Installation verwenden:</p>
+<p>Noch einfacher ist, das Paket in einem Schritt mit <a class="reference external" href="https://pip.pypa.io/">pip</a> automatisch
+herunterzuladen und zu installieren:</p>
 <pre class="literal-block">pip install DBUtils</pre>
 </div>
-<div class="section" id="installation-als-unterpaket-plug-in-von-webware-for-python">
-<h2>Installation als Unterpaket (Plug-In) von Webware for Python</h2>
-<p>Wenn Sie DBUtils nur als Ergänzung für das Web-Framework Webware for Python
-verwenden wollen, sollten Sie DBUtils als Webware-Plug-In installieren:</p>
-<pre class="literal-block">python setup.py install --install-lib=/pfad/zu/Webware</pre>
-<p>Ersetzen Sie <span class="docutils literal">/pfad/zu/Webware</span> hierbei durch den Pfad zum Wurzelverzeichnis
-der Installation von Webware for Python. Sie müssen auch das Installationsskript
-von Webware for Python laufen lassen, wenn dies noch nicht geschehen ist, oder
-wenn Sie DBUtils in die Webware-Dokumentation integrieren wollen:</p>
-<pre class="literal-block">cd /pfad/zu/Webware
-python install.py</pre>
-</div>
 </div>
 <div class="section" id="anforderungen">
 <h1>Anforderungen</h1>
-<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 2.7 und 3.5 bis 3.8.</p>
-<p>Die Module in der Variante für klassisches PyGreSQL benötigen <a class="reference external" href="http://www.pygresql.org/">PyGreSQL</a>
+<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 2.7 und 3.5 bis 3.9.</p>
+<p>Die Module in der Variante für klassisches PyGreSQL benötigen <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a>
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
 für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten,
 das auf <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> basiert.</p>
@@ -165,44 +153,49 @@ <h1>Anforderungen</h1>
 <h1>Funktionalität</h1>
 <p>Dieser Abschnitt verwendet nur die Bezeichnungen der DB-API-2-Variante, aber
 Entsprechendes gilt auch für die PyGreSQL-Variante.</p>
-<div class="section" id="simplepooleddb">
-<h2>SimplePooledDB</h2>
-<p><span class="docutils literal">DBUtils.SimplePooledDB</span> ist eine sehr elementare Referenz-Implementierung
-eines Pools von Datenbankverbindungen. Hiermit ist ein Vorratsspeicher an
-Datenbankverbindungen gemeint, aus dem sich die Python-Anwendung bedienen kann.
-Diese Implementierung ist weit weniger ausgefeilt als das eigentliche
-<span class="docutils literal">PooledDB</span>-Modul und stellt insbesondere keine Ausfallsicherung zur Verfügung.
-<span class="docutils literal">DBUtils.SimplePooledDB</span> ist im Wesentlichen identisch mit dem zu Webware for
-Python gehörenden Modul <span class="docutils literal">MiscUtils.DBPool</span>. Es ist eher zur Verdeutlichung
-des Konzepts gedacht, als zum Einsatz im produktiven Betrieb.</p>
+<p>DBUtils installiert sich als Paket <span class="docutils literal">dbutils</span>, das alle hier beschriebenen
+Module enthält. Jedes dieser Modul enthält im Wesentlichen eine Klasse, die
+einen analogen Namen trägt und die jeweilige Funktionalität bereitstellt.
+So enthält z.B. das Modul <span class="docutils literal">dbutils.pooled_db</span> die Klasse <span class="docutils literal">PooledDB</span>.</p>
+<div class="section" id="simplepooleddb-simple-pooled-db">
+<h2>SimplePooledDB (simple_pooled_db)</h2>
+<p>Die Klasse <span class="docutils literal">SimplePooledDB</span> in <span class="docutils literal">dbutils.simple_pooled_db</span> ist eine sehr
+elementare Referenz-Implementierung eines Pools von Datenbankverbindungen.
+Hiermit ist ein Vorratsspeicher an Datenbankverbindungen gemeint, aus dem sich
+die Python-Anwendung bedienen kann. Diese Implementierung ist weit weniger
+ausgefeilt als das eigentliche <span class="docutils literal">pooled_db</span>-Modul und stellt insbesondere
+keine Ausfallsicherung zur Verfügung. <span class="docutils literal">dbutils.simple_pooled_db</span> ist im
+Wesentlichen identisch mit dem zu Webware for Python gehörenden Modul
+<span class="docutils literal">MiscUtils.DBPool</span>. Es ist eher zur Verdeutlichung des Konzepts gedacht,
+als zum Einsatz im produktiven Betrieb.</p>
 </div>
-<div class="section" id="steadydb">
-<h2>SteadyDB</h2>
-<p><span class="docutils literal">DBUtils.SteadyDB</span> ist ein Modul, das &quot;gehärtete&quot; Datenbankverbindungen
-bereitstellt, denen gewöhnlichen Verbindungen eines DB-API-2-Datenbankadapters
-zugrunde liegen. Eine &quot;gehärtete&quot; Verbindung wird bei Zugriff automatisch,
-ohne dass die Anwendung dies bemerkt, wieder geöffnet, wenn sie geschlossen
-wurde, die Datenbankverbindung unterbrochen wurde, oder wenn sie öfter als
-ein optionales Limit genutzt wurde.</p>
+<div class="section" id="steadydbconnection-steady-db">
+<h2>SteadyDBConnection (steady_db)</h2>
+<p>Die Klasse <span class="docutils literal">SteadyDBConnection</span> im Modul <span class="docutils literal">dbutils.steady_db</span> stellt
+&quot;gehärtete&quot; Datenbankverbindungen bereit, denen gewöhnlichen Verbindungen
+eines DB-API-2-Datenbankadapters zugrunde liegen. Eine &quot;gehärtete&quot; Verbindung
+wird bei Zugriff automatisch, ohne dass die Anwendung dies bemerkt, wieder
+geöffnet, wenn sie geschlossen wurde, die Datenbankverbindung unterbrochen
+wurde, oder wenn sie öfter als ein optionales Limit genutzt wurde.</p>
 <p>Ein typisches Beispiel wo dies benötig wird, ist, wenn die Datenbank neu
 gestartet wurde, während Ihre Anwendung immer noch läuft und Verbindungen
 zur Datenbank offen hat, oder wenn Ihre Anwendung auf eine entfernte Datenbank
 über ein Netzwerk zugreift, das durch eine Firewall geschützt ist, und die
 Firewall neu gestartet wurde und dabei ihren Verbindungsstatus verloren hat.</p>
-<p>Normalerweise benutzen Sie das <span class="docutils literal">SteadyDB</span>-Modul nicht direkt; es wird aber
-von den beiden nächsten Modulen benötigt, <span class="docutils literal">PersistentDB</span> und <span class="docutils literal">PooledDB</span>.</p>
+<p>Normalerweise benutzen Sie das <span class="docutils literal">steady_db</span>-Modul nicht direkt; es wird aber
+von den beiden nächsten Modulen benötigt, <span class="docutils literal">persistent_db</span> und <span class="docutils literal">pooled_db</span>.</p>
 </div>
-<div class="section" id="persistentdb">
-<h2>PersistentDB</h2>
-<p><span class="docutils literal">DBUtils.PersistentDB</span> stellt gehärtete,  thread-affine, persistente
-Datenbankverbindungen zur Verfügung, unter Benutzung eines beliebigen
-DB-API-2-Datenbankadapters. Mit &quot;thread-affin&quot; und &quot;persistent&quot; ist
-hierbei gemeint, dass die einzelnen Datenbankverbindungen den jeweiligen
-Threads fest zugeordnet bleiben und während der Laufzeit des Threads nicht
-geschlossen werden.</p>
+<div class="section" id="persistentdb-persistent-db">
+<h2>PersistentDB (persistent_db)</h2>
+<p>Die Klasse <span class="docutils literal">PersistentDB</span> im Modul <span class="docutils literal">dbutils.persistent_db</span> stellt
+gehärtete,  thread-affine, persistente Datenbankverbindungen zur Verfügung,
+unter Benutzung eines beliebigen DB-API-2-Datenbankadapters. Mit &quot;thread-affin&quot;
+und &quot;persistent&quot; ist hierbei gemeint, dass die einzelnen Datenbankverbindungen
+den jeweiligen Threads fest zugeordnet bleiben und während der Laufzeit des
+Threads nicht geschlossen werden.</p>
 <p>Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
-<span class="docutils literal">PersistentDB</span>-Datenbankverbindungen einsetzen:</p>
-<img alt="persist.png" src="persist.png" />
+<span class="docutils literal">persistent_db</span>-Datenbankverbindungen einsetzen:</p>
+<img alt="persistent.png" src="persistent.png" />
 <p>Immer wenn ein Thread eine Datenbankverbindung zum ersten Mal öffnet, wird
 eine neue Datenbankverbindung geöffnet, die von da an immer wieder für genau
 diesen Thread verwendet wird. Wenn der Thread die Datenbankverbindung schließt,
@@ -210,25 +203,25 @@ <h2>PersistentDB</h2>
 gleiche Thread wieder eine Datenbankverbindung anfordert, diese gleiche bereits
 geöffnete Datenbankverbindung wieder verwendet werden kann. Die Verbindung wird
 automatisch geschlossen, wenn der Thread beendet wird.</p>
-<p>Kurz gesagt versucht <span class="docutils literal">PersistentDB</span> Datenbankverbindungen wiederzuverwerten,
+<p>Kurz gesagt versucht <span class="docutils literal">persistent_db</span> Datenbankverbindungen wiederzuverwerten,
 um die Gesamteffizienz der Datenbankzugriffe Ihrer Multithread-Anwendungen zu
 steigern, aber es wird dabei sichergestellt, dass verschiedene Threads niemals
 die gleiche Verbindung benutzen.</p>
-<p>Daher arbeitet <span class="docutils literal">PersistentDB</span> sogar dann problemlos, wenn der zugrunde
+<p>Daher arbeitet <span class="docutils literal">persistent_db</span> sogar dann problemlos, wenn der zugrunde
 liegende DB-API-2-Datenbankadapter nicht thread-sicher auf der Verbindungsebene
 ist, oder wenn parallele Threads Parameter der Datenbank-Sitzung verändern
 oder Transaktionen mit mehreren SQL-Befehlen durchführen.</p>
 </div>
-<div class="section" id="pooleddb">
-<h2>PooledDB</h2>
-<p><span class="docutils literal">DBUtils.PooledDB</span> stellt, unter Benutzung eines beliebigen
-DB-API-2-Datenbankadapters, einen Pool von gehärteten, thread-sicheren
-Datenbankverbindungen zur Verfügung, die automatisch, ohne dass die Anwendung
-dies bemerkt, wiederverwendet werden.</p>
+<div class="section" id="pooleddb-pooled-db">
+<h2>PooledDB (pooled_db)</h2>
+<p>Die Klasse <span class="docutils literal">PooledDB</span> im Modul <span class="docutils literal">dbutils.pooled_db</span> stellt, unter Benutzung
+eines beliebigen DB-API-2-Datenbankadapters, einen Pool von gehärteten,
+thread-sicheren Datenbankverbindungen zur Verfügung, die automatisch, ohne dass
+die Anwendung dies bemerkt, wiederverwendet werden.</p>
 <p>Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
-<span class="docutils literal">PooledDB</span>-Datenbankverbindungen einsetzen:</p>
-<img alt="pool.png" src="pool.png" />
-<p>Wie im Diagramm angedeutet, kann <span class="docutils literal">PooledDB</span> geöffnete Datenbankverbindungen
+<span class="docutils literal">pooled_db</span>-Datenbankverbindungen einsetzen:</p>
+<img alt="pooled.png" src="pooled.png" />
+<p>Wie im Diagramm angedeutet, kann <span class="docutils literal">pooled_db</span> geöffnete Datenbankverbindungen
 den verschiedenen Threads beliebig zuteilen. Dies geschieht standardmäßig, wenn
 Sie den Verbindungspool mit einem positiven Wert für <span class="docutils literal">maxshared</span> einrichten
 und der zugrunde liegende DB-API-2-Datenbankadapter auf der Verbindungsebene
@@ -244,23 +237,23 @@ <h2>PooledDB</h2>
 Datenbankverbindungen zurückgegeben, damit sie wiederverwertet werden kann.</p>
 <p>Wenn der zugrunde liegende DB-API-Datenbankadapter nicht thread-sicher ist,
 werden Thread-Locks verwendet, um sicherzustellen, dass die
-<span class="docutils literal">PooledDB</span>-Verbindungen dennoch thread-sicher sind. Sie brauchen sich also
+<span class="docutils literal">pooled_db</span>-Verbindungen dennoch thread-sicher sind. Sie brauchen sich also
 hierum keine Sorgen zu machen, aber Sie sollten darauf achten, dedizierte
 Datenbankverbindungen zu verwenden, sobald Sie Parameter der Datenbanksitzung
 verändern oder Transaktionen mit mehreren SQL-Befehlen ausführen.</p>
 </div>
 <div class="section" id="die-qual-der-wahl">
 <h2>Die Qual der Wahl</h2>
-<p>Sowohl <span class="docutils literal">PersistentDB</span> als auch <span class="docutils literal">PooledDB</span> dienen dem gleichen Zweck,
+<p>Sowohl <span class="docutils literal">persistent_db</span> als auch <span class="docutils literal">pooled_db</span> dienen dem gleichen Zweck,
 nämlich die Effizienz des Datenbankzugriffs durch Wiederverwendung von
 Datenbankverbindungen zu steigern, und dabei gleichzeitig die Stabilität
 zu gewährleisten, selbst wenn die Datenbankverbindung unterbrochen wird.</p>
 <p>Welches der beiden Module sollte also verwendet werden? Nach den obigen
-Erklärungen ist es klar, dass <span class="docutils literal">PersistentDB</span> dann sinnvoller ist, wenn
+Erklärungen ist es klar, dass <span class="docutils literal">persistent_db</span> dann sinnvoller ist, wenn
 Ihre Anwendung eine gleich bleibende Anzahl Threads verwendet, die häufig
 auf die Datenbank zugreifen. In diesem Fall werden Sie ungefähr die gleiche
 Anzahl geöffneter Datenbankverbindungen erhalten. Wenn jedoch Ihre Anwendung
-häufig Threads beendet und neu startet, dann ist <span class="docutils literal">PooledDB</span> die bessere
+häufig Threads beendet und neu startet, dann ist <span class="docutils literal">pooled_db</span> die bessere
 Lösung, die auch mehr Möglichkeiten zur Feineinstellung zur Verbesserung
 der Effizienz erlaubt, insbesondere bei Verwendung eines thread-sicheren
 DB-API-2-Datenbankadapters.</p>
@@ -274,17 +267,17 @@ <h1>Benutzung</h1>
 der Initialisierung auch einige Unterschiede, sowohl zwischen den &quot;Pooled&quot;-
 und den &quot;Persistent&quot;-Varianten, als auch zwischen den DB-API-2- und den
 PyGreSQL-Varianten.</p>
-<p>Wir werden hier nur auf das <span class="docutils literal">PersistentDB</span>-Modul und das etwas kompliziertere
-<span class="docutils literal">PooledDB</span>-Modul eingehen. Einzelheiten zu den anderen Modulen finden Sie
+<p>Wir werden hier nur auf das <span class="docutils literal">persistent_db</span>-Modul und das etwas kompliziertere
+<span class="docutils literal">pooled_db</span>-Modul eingehen. Einzelheiten zu den anderen Modulen finden Sie
 in deren Docstrings. Unter Verwendung der Python-Interpreter-Konsole können Sie
-sich die Dokumentation des <span class="docutils literal">PooledDB</span>-Moduls wie folgt anzeigen lassen (dies
+sich die Dokumentation des <span class="docutils literal">pooled_db</span>-Moduls wie folgt anzeigen lassen (dies
 funktioniert entsprechend auch mit den anderen Modulen):</p>
-<pre class="literal-block">help(PooledDB)</pre>
-<div class="section" id="id1">
-<h2>PersistentDB</h2>
-<p>Wenn Sie das <span class="docutils literal">PersistentDB</span>-Modul einsetzen möchten, müssen Sie zuerst einen
+<pre class="literal-block">help(pooled_db)</pre>
+<div class="section" id="id2">
+<h2>PersistentDB (persistent_db)</h2>
+<p>Wenn Sie das <span class="docutils literal">persistent_db</span>-Modul einsetzen möchten, müssen Sie zuerst einen
 Generator für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
-indem Sie eine Instanz der Klasse <span class="docutils literal">PersistentDB</span> erzeugen, wobei Sie folgende
+indem Sie eine Instanz der Klasse <span class="docutils literal">persistent_db</span> erzeugen, wobei Sie folgende
 Parameter angeben müssen:</p>
 <ul>
 <li><p><span class="docutils literal">creator</span>: entweder eine Funktion, die neue DB-API-2-Verbindungen
@@ -319,14 +312,14 @@ <h2>PersistentDB</h2>
 möchten, dass jede Verbindung Ihrer lokalen Datenbank <span class="docutils literal">meinedb</span> 1000 mal
 wiederverwendet werden soll, sieht die Initialisierung so aus:</p>
 <pre class="literal-block">import pgdb  # importiere das verwendete DB-API-2-Modul
-from DBUtils.PersistentDB import PersistentDB
+from dbutils.persistent_db import PersistentDB
 persist = PersistentDB(pgdb, 1000, database='meinedb')</pre>
 <p>Nachdem Sie den Generator mit diesen Parametern eingerichtet haben, können
 Sie derartige Datenbankverbindungen von da an wie folgt anfordern:</p>
 <pre class="literal-block">db = persist.connection()</pre>
 <p>Sie können diese Verbindungen verwenden, als wären sie gewöhnliche
 DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die
-&quot;gehärtete&quot; <span class="docutils literal">SteadyDB</span>-Version der zugrunde liegenden DB-API-2-Verbindung.</p>
+&quot;gehärtete&quot; <span class="docutils literal">steady_db</span>-Version der zugrunde liegenden DB-API-2-Verbindung.</p>
 <p>Wenn Sie eine solche persistente Verbindung mit <span class="docutils literal">db.close()</span> schließen,
 wird dies stillschweigend ignoriert, denn sie würde beim nächsten Zugriff
 sowieso wieder geöffnet, und das wäre nicht im Sinne persistenter Verbindungen.
@@ -344,11 +337,11 @@ <h2>PersistentDB</h2>
 <span class="docutils literal">mod_wsgi</span> hier Probleme bereitet, da es Daten, die mit <span class="docutils literal">threading.local</span>
 gespeichert wurden, zwischen Requests löscht).</p>
 </div>
-<div class="section" id="id2">
-<h2>PooledDB</h2>
-<p>Wenn Sie das <span class="docutils literal">PooledDB</span>-Modul einsetzen möchten, müssen Sie zuerst einen
+<div class="section" id="id3">
+<h2>PooledDB (pooled_db)</h2>
+<p>Wenn Sie das <span class="docutils literal">pooled_db</span>-Modul einsetzen möchten, müssen Sie zuerst einen
 Pool für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
-indem Sie eine Instanz der Klasse <span class="docutils literal">PooledDB</span> erzeugen, wobei Sie folgende
+indem Sie eine Instanz der Klasse <span class="docutils literal">pooled_db</span> erzeugen, wobei Sie folgende
 Parameter angeben müssen:</p>
 <ul>
 <li><p><span class="docutils literal">creator</span>: entweder eine Funktion, die neue DB-API-2-Verbindungen
@@ -403,14 +396,14 @@ <h2>PooledDB</h2>
 und einen Pool von mindestens fünf Datenbankverbindungen zu Ihrer Datenbank
 <span class="docutils literal">meinedb</span> verwenden möchten, dann sieht die Initialisierung so aus:</p>
 <pre class="literal-block">import pgdb  # importiere das verwendete DB-API-2-Modul
-from DBUtils.PooledDB import PooledDB
+from dbutils.pooled_db import PooledDB
 pool = PooledDB(pgdb, 5, database='meinedb')</pre>
 <p>Nachdem Sie den Pool für Datenbankverbindungen so eingerichtet haben, können
 Sie Verbindungen daraus wie folgt anfordern:</p>
 <pre class="literal-block">db = pool.connection()</pre>
 <p>Sie können diese Verbindungen verwenden, als wären sie gewöhnliche
 DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die
-&quot;gehärtete&quot; <span class="docutils literal">SteadyDB</span>-Version der zugrunde liegenden DB-API-2-Verbindung.</p>
+&quot;gehärtete&quot; <span class="docutils literal">steady_db</span>-Version der zugrunde liegenden DB-API-2-Verbindung.</p>
 <p>Bitte beachten Sie, dass die Verbindung von anderen Threads mitgenutzt werden
 kann, wenn Sie den Parameter <span class="docutils literal">maxshared</span> auf einen Wert größer als Null
 gesetzt haben, und der zugrunde liegende DB-API-2-Datenbankadapter dies erlaubt.
@@ -441,48 +434,24 @@ <h2>PooledDB</h2>
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
 wieder an den Verbindungspool zurückgegeben wird.</p>
 </div>
-<div class="section" id="benutzung-in-webware-for-python">
-<h2>Benutzung in Webware for Python</h2>
-<p>Wenn Sie DBUtils verwenden, um von Servlets des Web-Frameworks <a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware
-for Python</a> auf eine Datenbank zuzugreifen, dann müssen Sie sicherstellen,
-dass die Generatoren zur Erzeugung von Datenbankverbindungen nur einmal
-eingerichtet werden, wenn die Anwendung startet, und nicht jedes Mal, wenn
-eine Servlet-Instanz erzeugt wird. Den hierfür nötigen Code können Sie
-bei der Basis-Servlet-Klasse einfügen, dort wo das Modul oder die Klasse
-initialisiert wird, oder Sie können die Funktion <span class="docutils literal">contextInitialize()</span>
-im <span class="docutils literal">__init__.py</span>-Skript Ihres Anwendungskontextes verwenden.</p>
-<p>Das zusammen mit DButils ausgelieferte Verzeichnis <span class="docutils literal">Examples</span> enthält
-einen Beispielkontext für Webware for Python, der eine kleine Demo-Datenbank
-verwendet, um Teilnehmer an einer Seminarreihe zu verwalten (die Idee für
-dieses Beispiel wurde dem Artikel &quot;<a class="reference external" href="http://www.linuxjournal.com/article/2605">The Python DB-API</a>&quot; von Andrew Kuchling
-entnommen).</p>
-<p>Der Beispielkontext kann konfiguriert werden, indem entweder eine Konfig-Datei
-<span class="docutils literal">Configs/Database.config</span> angelegt wird, oder indem die Standard-Parameter
-direkt im Beispielservlet <span class="docutils literal">Examples/DBUtilsExample.py</span> geändert werden.
-Auf diese Weise können Sie einen passenden Datenbanknutzer und sein Passwort
-festlegen, sowie den zugrunde liegenden Datenbankadapter auswählen (das
-klassische PyGreSQL-Modul oder irgendein DB-API-2-Modul). Wenn der Parameter
-<span class="docutils literal">maxcached</span> vorhanden ist, verwendet das Beispielservlet die
-<span class="docutils literal">Pooled</span>-Variante, andernfalls die <span class="docutils literal">Persistent</span>-Variante.</p>
-</div>
 </div>
 <div class="section" id="anmerkungen">
 <h1>Anmerkungen</h1>
-<p>Wenn Sie einen der bekannten &quot;Object-Relational Mapper&quot; <a class="reference external" href="http://www.sqlobject.org/">SQLObject</a> oder
-<a class="reference external" href="http://www.sqlalchemy.org">SQLAlchemy</a> verwenden, dann benötigen Sie DBUtils nicht, denn diese haben
+<p>Wenn Sie einen der bekannten &quot;Object-Relational Mapper&quot; <a class="reference external" href="http://sqlobject.org/">SQLObject</a> oder
+<a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a> verwenden, dann benötigen Sie DBUtils nicht, denn diese haben
 ihre eigenen Mechanismen zum Pooling von Datenbankverbindungen eingebaut.
 Tatsächlich hat SQLObject 2 (SQL-API) das Pooling in eine separate Schicht
 ausgelagert, in der Code von DBUtils verwendet wird.</p>
 <p>Wenn Sie eine Lösung verwenden wie den Apache-Webserver mit <a class="reference external" href="http://modpython.org/">mod_python</a>
 oder <a class="reference external" href="https://github.com/GrahamDumpleton/mod_wsgi">mod_wsgi</a>, dann sollten Sie bedenken, dass Ihr Python-Code normalerweise
 im Kontext der Kindprozesse des Webservers läuft. Wenn Sie also das
-<span class="docutils literal">PooledDB</span>-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann
+<span class="docutils literal">pooled_db</span>-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann
 werden Sie ebensoviele Pools mit Datenbankverbindungen erhalten. Wenn diese
 Prozesse viele Threads laufen lassen,  dann mag dies eine sinnvoller Ansatz
 sein, wenn aber diese Prozesse nicht mehr als einen Worker-Thread starten,
 wie im Fall des Multi-Processing Moduls &quot;prefork&quot; für den Apache-Webserver,
 dann sollten Sie auf eine Middleware für das Connection-Pooling zurückgreifen,
-die Multi-Processing unterstützt, wie zum Beispiel <a class="reference external" href="http://www.pgpool.net/">pgpool</a> oder <a class="reference external" href="https://pgbouncer.github.io/">pgbouncer</a>
+die Multi-Processing unterstützt, wie zum Beispiel <a class="reference external" href="https://www.pgpool.net/">pgpool</a> oder <a class="reference external" href="https://pgbouncer.github.io/">pgbouncer</a>
 für die PostgreSQL-Datenbank.</p>
 </div>
 <div class="section" id="zukunft">
@@ -491,7 +460,7 @@ <h1>Zukunft</h1>
 <ul class="simple">
 <li><p>Alternativ zur Obergrenze in der Anzahl der Nutzung einer Datenbankverbindung
 könnte eine maximale Lebensdauer für die Verbindung implementiert werden.</p></li>
-<li><p>Es könnten Module <span class="docutils literal">MonitorDB</span> und <span class="docutils literal">MonitorPg</span> hinzugefügt werden, die
+<li><p>Es könnten Module <span class="docutils literal">monitor_db</span> und <span class="docutils literal">monitor_pg</span> hinzugefügt werden, die
 in einem separaten Thread ständig den &quot;idle pool&quot; und eventuell auch den
 &quot;shared pool&quot; bzw. die persistenten Verbindungen überwachen. Wenn eine
 unterbrochene Datenbankverbindung entdeckt wird, wird diese automatisch durch
@@ -509,10 +478,8 @@ <h1>Zukunft</h1>
 </div>
 <div class="section" id="fehlermeldungen-und-feedback">
 <h1>Fehlermeldungen und Feedback</h1>
-<p>Bitte Senden Sie Fehlermeldungen, Patches und Feedback direkt an den
-Autor (unter Verwendung der unten angegebenen E-Mail-Adresse).</p>
-<p>Probleme, die Webware betreffen, können auch in der <a class="reference external" href="https://lists.sourceforge.net/lists/listinfo/webware-discuss">Webware for Python
-mailing list</a> diskutiert werden.</p>
+<p>Fehlermeldungen, Patches und Feedback können Sie als <a class="reference external" href="https://github.com/WebwareForPython/DBUtils/issues">Issues</a> oder
+<a class="reference external" href="https://github.com/WebwareForPython/DBUtils/pulls">Pull Requests</a> auf der <a class="reference external" href="https://github.com/WebwareForPython/DBUtils">GitHub-Projektseite</a> von DBUtils übermitteln.</p>
 </div>
 <div class="section" id="links">
 <h1>Links</h1>
@@ -523,18 +490,18 @@ <h1>Links</h1>
 <li><p><a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware for Python</a> Framework</p></li>
 <li><p>Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a></p></li>
 <li><p><a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a> Datenbank</p></li>
-<li><p><a class="reference external" href="http://www.pygresql.org/">PyGreSQL</a> Python-Adapter for PostgreSQL</p></li>
-<li><p><a class="reference external" href="http://www.pgpool.net/">pgpool</a> Middleware für Connection-Pooling mit PostgreSQL</p></li>
+<li><p><a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> Python-Adapter for PostgreSQL</p></li>
+<li><p><a class="reference external" href="https://www.pgpool.net/">pgpool</a> Middleware für Connection-Pooling mit PostgreSQL</p></li>
 <li><p><a class="reference external" href="https://pgbouncer.github.io/">pgbouncer</a> Middleware für Connection-Pooling mit PostgreSQL</p></li>
-<li><p><a class="reference external" href="http://www.sqlobject.org/">SQLObject</a> Objekt-relationaler Mapper</p></li>
-<li><p><a class="reference external" href="http://www.sqlalchemy.org">SQLAlchemy</a> Objekt-relationaler Mapper</p></li>
+<li><p><a class="reference external" href="http://sqlobject.org/">SQLObject</a> Objekt-relationaler Mapper</p></li>
+<li><p><a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a> Objekt-relationaler Mapper</p></li>
 </ul>
 </div>
 <div class="section" id="autoren">
 <h1>Autoren</h1>
 <dl class="field-list simple">
 <dt>Autor</dt>
-<dd><p>Christoph Zwerschke &lt;<a class="reference external" href="mailto:cito&#64;online.de">cito&#64;online.de</a>&gt;</p>
+<dd><p><a class="reference external" href="https://github.com/Cito">Christoph Zwerschke</a></p>
 </dd>
 <dt>Beiträge</dt>
 <dd><p>DBUtils benutzt Code, Anmerkungen und Vorschläge von
@@ -547,7 +514,7 @@ <h1>Autoren</h1>
 </div>
 <div class="section" id="copyright-und-lizenz">
 <h1>Copyright und Lizenz</h1>
-<p>Copyright © 2005-2018 Christoph Zwerschke.
+<p>Copyright © 2005-2020 Christoph Zwerschke.
 Alle Rechte vorbehalten.</p>
 <p>DBUtils ist freie und quelloffene Software,
 lizenziert unter der <a class="reference external" href="https://opensource.org/licenses/MIT">MIT-Lizenz</a>.</p>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 75580f7..3742b5b 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -14,10 +14,12 @@ Zusammenfassung
 
 DBUtils_ ist eine Sammlung von Python-Modulen, mit deren Hilfe man in Python_
 geschriebene Multithread-Anwendungen auf sichere und effiziente Weise an
-Datenbanken anbinden kann. DBUtils wurde mit Blick auf `Webware for Python`_
-als Anwendung und PyGreSQL_ als PostgreSQL_-Datenbankadapter entwickelt,
-kann aber für beliebige Python-Anwendungen und beliebige auf `DB-API 2`_
-beruhende Python-Datenbankadapter verwendet werden.
+Datenbanken anbinden kann.
+
+DBUtils wurde ursprünglich speziell für `Webware for Python`_ als Anwendung
+und PyGreSQL_ als PostgreSQL_-Datenbankadapter entwickelt,
+kann aber inzwischen für beliebige Python-Anwendungen und beliebige
+auf `DB-API 2`_ beruhende Python-Datenbankadapter verwendet werden.
 
 
 Module
@@ -25,41 +27,42 @@ Module
 
 DBUtils ist als Python-Package realisiert worden, das aus zwei verschiedenen
 Gruppen von Modulen besteht: Einer Gruppe zur Verwendung mit beliebigen
-DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen PyGreSQL-Datenbankadapter-Modul.
-
-+-------------------+----------------------------------------------+
-| Allgemeine Variante für beliebige DB-API-2-Adapter               |
-+===================+==============================================+
-| SteadyDB.py       | Gehärtete DB-API-2-Datenbankverbindungen     |
-+-------------------+----------------------------------------------+
-| PooledDB.py       | Pooling für DB-API-2-Datenbankverbindungen   |
-+-------------------+----------------------------------------------+
-| PersistentDB.py   | Persistente DB-API-2-Datenbankverbindungen   |
-+-------------------+----------------------------------------------+
-| SimplePooledDB.py | Einfaches Pooling für DB-API 2               |
-+-------------------+----------------------------------------------+
-
-+-------------------+----------------------------------------------+
-| Variante speziell für den klassischen PyGreSQL-Adapter           |
-+===================+==============================================+
-| SteadyPg.py       | Gehärtete klassische PyGreSQL-Verbindungen   |
-+-------------------+----------------------------------------------+
-| PooledPg.py       | Pooling für klassische PyGreSQL-Verbindungen |
-+-------------------+----------------------------------------------+
-| PersistentPg.py   | Persistente klassische PyGreSQL-Verbindungen |
-+-------------------+----------------------------------------------+
-| SimplePooledPg.py | Einfaches Pooling für klassisches PyGreSQL   |
-+-------------------+----------------------------------------------+
+DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen
+PyGreSQL-Datenbankadapter-Modul.
+
++------------------+----------------------------------------------+
+| Allgemeine Variante für beliebige DB-API-2-Adapter              |
++==================+==============================================+
+| steady_db        | Gehärtete DB-API-2-Datenbankverbindungen     |
++------------------+----------------------------------------------+
+| pooled_db        | Pooling für DB-API-2-Datenbankverbindungen   |
++------------------+----------------------------------------------+
+| persistent_db    | Persistente DB-API-2-Datenbankverbindungen   |
++------------------+----------------------------------------------+
+| simple_pooled_db | Einfaches Pooling für DB-API 2               |
++------------------+----------------------------------------------+
+
++------------------+----------------------------------------------+
+| Variante speziell für den klassischen PyGreSQL-Adapter          |
++==================+==============================================+
+| steady_pg        | Gehärtete klassische PyGreSQL-Verbindungen   |
++------------------+----------------------------------------------+
+| pooled_pg        | Pooling für klassische PyGreSQL-Verbindungen |
++------------------+----------------------------------------------+
+| persistent_pg    | Persistente klassische PyGreSQL-Verbindungen |
++------------------+----------------------------------------------+
+| simple_pooled_pg | Einfaches Pooling für klassisches PyGreSQL   |
++------------------+----------------------------------------------+
 
 Die Abhängigkeiten der Module in der Variante für beliebige DB-API-2-Adapter
 sind im folgenden Diagramm dargestellt:
 
-.. image:: dbdep.png
+.. image:: dependencies_db.png
 
 Die Abhängigkeiten der Module in der Variante für den klassischen
 PyGreSQL-Adapter sehen ähnlich aus:
 
-.. image:: pgdep.png
+.. image:: depdependencies_pg.png
 
 
 Download
@@ -78,39 +81,24 @@ Das Source-Code-Repository befindet sich hier auf GitHub::
 Installation
 ============
 
-Installation als eigenständiges Paket
--------------------------------------
-Wenn Sie DBUtils für andere Anwendungen als Webware for Python verwenden
-möchten, empfiehlt es sich, das Paket auf die übliche Weise zu installieren::
+Installation
+------------
+Das Paket kann auf die übliche Weise installiert werden::
 
   python setup.py install
 
-Sie können auch `pip`_ für Download und Installation verwenden::
+Noch einfacher ist, das Paket in einem Schritt mit `pip`_ automatisch
+herunterzuladen und zu installieren::
 
   pip install DBUtils
 
 .. _pip: https://pip.pypa.io/
 
-Installation als Unterpaket (Plug-In) von Webware for Python
-------------------------------------------------------------
-Wenn Sie DBUtils nur als Ergänzung für das Web-Framework Webware for Python
-verwenden wollen, sollten Sie DBUtils als Webware-Plug-In installieren::
-
-  python setup.py install --install-lib=/pfad/zu/Webware
-
-Ersetzen Sie ``/pfad/zu/Webware`` hierbei durch den Pfad zum Wurzelverzeichnis
-der Installation von Webware for Python. Sie müssen auch das Installationsskript
-von Webware for Python laufen lassen, wenn dies noch nicht geschehen ist, oder
-wenn Sie DBUtils in die Webware-Dokumentation integrieren wollen::
-
-  cd /pfad/zu/Webware
-  python install.py
-
 
 Anforderungen
 =============
 
-DBUtils unterstützt die Python_ Versionen 2.7 und 3.5 bis 3.8.
+DBUtils unterstützt die Python_ Versionen 2.7 und 3.5 bis 3.9.
 
 Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL_
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
@@ -124,26 +112,31 @@ Funktionalität
 Dieser Abschnitt verwendet nur die Bezeichnungen der DB-API-2-Variante, aber
 Entsprechendes gilt auch für die PyGreSQL-Variante.
 
-
-SimplePooledDB
---------------
-``DBUtils.SimplePooledDB`` ist eine sehr elementare Referenz-Implementierung
-eines Pools von Datenbankverbindungen. Hiermit ist ein Vorratsspeicher an
-Datenbankverbindungen gemeint, aus dem sich die Python-Anwendung bedienen kann.
-Diese Implementierung ist weit weniger ausgefeilt als das eigentliche
-``PooledDB``-Modul und stellt insbesondere keine Ausfallsicherung zur Verfügung.
-``DBUtils.SimplePooledDB`` ist im Wesentlichen identisch mit dem zu Webware for
-Python gehörenden Modul ``MiscUtils.DBPool``. Es ist eher zur Verdeutlichung
-des Konzepts gedacht, als zum Einsatz im produktiven Betrieb.
-
-SteadyDB
---------
-``DBUtils.SteadyDB`` ist ein Modul, das "gehärtete" Datenbankverbindungen
-bereitstellt, denen gewöhnlichen Verbindungen eines DB-API-2-Datenbankadapters
-zugrunde liegen. Eine "gehärtete" Verbindung wird bei Zugriff automatisch,
-ohne dass die Anwendung dies bemerkt, wieder geöffnet, wenn sie geschlossen
-wurde, die Datenbankverbindung unterbrochen wurde, oder wenn sie öfter als
-ein optionales Limit genutzt wurde.
+DBUtils installiert sich als Paket ``dbutils``, das alle hier beschriebenen
+Module enthält. Jedes dieser Modul enthält im Wesentlichen eine Klasse, die
+einen analogen Namen trägt und die jeweilige Funktionalität bereitstellt.
+So enthält z.B. das Modul ``dbutils.pooled_db`` die Klasse ``PooledDB``.
+
+SimplePooledDB (simple_pooled_db)
+---------------------------------
+Die Klasse ``SimplePooledDB`` in ``dbutils.simple_pooled_db`` ist eine sehr
+elementare Referenz-Implementierung eines Pools von Datenbankverbindungen.
+Hiermit ist ein Vorratsspeicher an Datenbankverbindungen gemeint, aus dem sich
+die Python-Anwendung bedienen kann. Diese Implementierung ist weit weniger
+ausgefeilt als das eigentliche ``pooled_db``-Modul und stellt insbesondere
+keine Ausfallsicherung zur Verfügung. ``dbutils.simple_pooled_db`` ist im
+Wesentlichen identisch mit dem zu Webware for Python gehörenden Modul
+``MiscUtils.DBPool``. Es ist eher zur Verdeutlichung des Konzepts gedacht,
+als zum Einsatz im produktiven Betrieb.
+
+SteadyDBConnection (steady_db)
+------------------------------
+Die Klasse ``SteadyDBConnection`` im Modul ``dbutils.steady_db`` stellt
+"gehärtete" Datenbankverbindungen bereit, denen gewöhnlichen Verbindungen
+eines DB-API-2-Datenbankadapters zugrunde liegen. Eine "gehärtete" Verbindung
+wird bei Zugriff automatisch, ohne dass die Anwendung dies bemerkt, wieder
+geöffnet, wenn sie geschlossen wurde, die Datenbankverbindung unterbrochen
+wurde, oder wenn sie öfter als ein optionales Limit genutzt wurde.
 
 Ein typisches Beispiel wo dies benötig wird, ist, wenn die Datenbank neu
 gestartet wurde, während Ihre Anwendung immer noch läuft und Verbindungen
@@ -151,22 +144,22 @@ zur Datenbank offen hat, oder wenn Ihre Anwendung auf eine entfernte Datenbank
 über ein Netzwerk zugreift, das durch eine Firewall geschützt ist, und die
 Firewall neu gestartet wurde und dabei ihren Verbindungsstatus verloren hat.
 
-Normalerweise benutzen Sie das ``SteadyDB``-Modul nicht direkt; es wird aber
-von den beiden nächsten Modulen benötigt, ``PersistentDB`` und ``PooledDB``.
+Normalerweise benutzen Sie das ``steady_db``-Modul nicht direkt; es wird aber
+von den beiden nächsten Modulen benötigt, ``persistent_db`` und ``pooled_db``.
 
-PersistentDB
-------------
-``DBUtils.PersistentDB`` stellt gehärtete,  thread-affine, persistente
-Datenbankverbindungen zur Verfügung, unter Benutzung eines beliebigen
-DB-API-2-Datenbankadapters. Mit "thread-affin" und "persistent" ist
-hierbei gemeint, dass die einzelnen Datenbankverbindungen den jeweiligen
-Threads fest zugeordnet bleiben und während der Laufzeit des Threads nicht
-geschlossen werden.
+PersistentDB (persistent_db)
+----------------------------
+Die Klasse ``PersistentDB`` im Modul ``dbutils.persistent_db`` stellt
+gehärtete,  thread-affine, persistente Datenbankverbindungen zur Verfügung,
+unter Benutzung eines beliebigen DB-API-2-Datenbankadapters. Mit "thread-affin"
+und "persistent" ist hierbei gemeint, dass die einzelnen Datenbankverbindungen
+den jeweiligen Threads fest zugeordnet bleiben und während der Laufzeit des
+Threads nicht geschlossen werden.
 
 Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
-``PersistentDB``-Datenbankverbindungen einsetzen:
+``persistent_db``-Datenbankverbindungen einsetzen:
 
-.. image:: persist.png
+.. image:: persistent.png
 
 Immer wenn ein Thread eine Datenbankverbindung zum ersten Mal öffnet, wird
 eine neue Datenbankverbindung geöffnet, die von da an immer wieder für genau
@@ -176,29 +169,29 @@ gleiche Thread wieder eine Datenbankverbindung anfordert, diese gleiche bereits
 geöffnete Datenbankverbindung wieder verwendet werden kann. Die Verbindung wird
 automatisch geschlossen, wenn der Thread beendet wird.
 
-Kurz gesagt versucht ``PersistentDB`` Datenbankverbindungen wiederzuverwerten,
+Kurz gesagt versucht ``persistent_db`` Datenbankverbindungen wiederzuverwerten,
 um die Gesamteffizienz der Datenbankzugriffe Ihrer Multithread-Anwendungen zu
 steigern, aber es wird dabei sichergestellt, dass verschiedene Threads niemals
 die gleiche Verbindung benutzen.
 
-Daher arbeitet ``PersistentDB`` sogar dann problemlos, wenn der zugrunde
+Daher arbeitet ``persistent_db`` sogar dann problemlos, wenn der zugrunde
 liegende DB-API-2-Datenbankadapter nicht thread-sicher auf der Verbindungsebene
 ist, oder wenn parallele Threads Parameter der Datenbank-Sitzung verändern
 oder Transaktionen mit mehreren SQL-Befehlen durchführen.
 
-PooledDB
---------
-``DBUtils.PooledDB`` stellt, unter Benutzung eines beliebigen
-DB-API-2-Datenbankadapters, einen Pool von gehärteten, thread-sicheren
-Datenbankverbindungen zur Verfügung, die automatisch, ohne dass die Anwendung
-dies bemerkt, wiederverwendet werden.
+PooledDB (pooled_db)
+--------------------
+Die Klasse ``PooledDB`` im Modul ``dbutils.pooled_db`` stellt, unter Benutzung
+eines beliebigen DB-API-2-Datenbankadapters, einen Pool von gehärteten,
+thread-sicheren Datenbankverbindungen zur Verfügung, die automatisch, ohne dass
+die Anwendung dies bemerkt, wiederverwendet werden.
 
 Das folgende Diagramm zeigt die beteiligten Verbindungsschichten, wenn Sie
-``PooledDB``-Datenbankverbindungen einsetzen:
+``pooled_db``-Datenbankverbindungen einsetzen:
 
-.. image:: pool.png
+.. image:: pooled.png
 
-Wie im Diagramm angedeutet, kann ``PooledDB`` geöffnete Datenbankverbindungen
+Wie im Diagramm angedeutet, kann ``pooled_db`` geöffnete Datenbankverbindungen
 den verschiedenen Threads beliebig zuteilen. Dies geschieht standardmäßig, wenn
 Sie den Verbindungspool mit einem positiven Wert für ``maxshared`` einrichten
 und der zugrunde liegende DB-API-2-Datenbankadapter auf der Verbindungsebene
@@ -215,24 +208,24 @@ Datenbankverbindungen zurückgegeben, damit sie wiederverwertet werden kann.
 
 Wenn der zugrunde liegende DB-API-Datenbankadapter nicht thread-sicher ist,
 werden Thread-Locks verwendet, um sicherzustellen, dass die
-``PooledDB``-Verbindungen dennoch thread-sicher sind. Sie brauchen sich also
+``pooled_db``-Verbindungen dennoch thread-sicher sind. Sie brauchen sich also
 hierum keine Sorgen zu machen, aber Sie sollten darauf achten, dedizierte
 Datenbankverbindungen zu verwenden, sobald Sie Parameter der Datenbanksitzung
 verändern oder Transaktionen mit mehreren SQL-Befehlen ausführen.
 
 Die Qual der Wahl
 -----------------
-Sowohl ``PersistentDB`` als auch ``PooledDB`` dienen dem gleichen Zweck,
+Sowohl ``persistent_db`` als auch ``pooled_db`` dienen dem gleichen Zweck,
 nämlich die Effizienz des Datenbankzugriffs durch Wiederverwendung von
 Datenbankverbindungen zu steigern, und dabei gleichzeitig die Stabilität
 zu gewährleisten, selbst wenn die Datenbankverbindung unterbrochen wird.
 
 Welches der beiden Module sollte also verwendet werden? Nach den obigen
-Erklärungen ist es klar, dass ``PersistentDB`` dann sinnvoller ist, wenn
+Erklärungen ist es klar, dass ``persistent_db`` dann sinnvoller ist, wenn
 Ihre Anwendung eine gleich bleibende Anzahl Threads verwendet, die häufig
 auf die Datenbank zugreifen. In diesem Fall werden Sie ungefähr die gleiche
 Anzahl geöffneter Datenbankverbindungen erhalten. Wenn jedoch Ihre Anwendung
-häufig Threads beendet und neu startet, dann ist ``PooledDB`` die bessere
+häufig Threads beendet und neu startet, dann ist ``pooled_db`` die bessere
 Lösung, die auch mehr Möglichkeiten zur Feineinstellung zur Verbesserung
 der Effizienz erlaubt, insbesondere bei Verwendung eines thread-sicheren
 DB-API-2-Datenbankadapters.
@@ -249,19 +242,19 @@ der Initialisierung auch einige Unterschiede, sowohl zwischen den "Pooled"-
 und den "Persistent"-Varianten, als auch zwischen den DB-API-2- und den
 PyGreSQL-Varianten.
 
-Wir werden hier nur auf das ``PersistentDB``-Modul und das etwas kompliziertere
-``PooledDB``-Modul eingehen. Einzelheiten zu den anderen Modulen finden Sie
+Wir werden hier nur auf das ``persistent_db``-Modul und das etwas kompliziertere
+``pooled_db``-Modul eingehen. Einzelheiten zu den anderen Modulen finden Sie
 in deren Docstrings. Unter Verwendung der Python-Interpreter-Konsole können Sie
-sich die Dokumentation des ``PooledDB``-Moduls wie folgt anzeigen lassen (dies
+sich die Dokumentation des ``pooled_db``-Moduls wie folgt anzeigen lassen (dies
 funktioniert entsprechend auch mit den anderen Modulen)::
 
-  help(PooledDB)
+  help(pooled_db)
 
-PersistentDB
-------------
-Wenn Sie das ``PersistentDB``-Modul einsetzen möchten, müssen Sie zuerst einen
+PersistentDB (persistent_db)
+----------------------------
+Wenn Sie das ``persistent_db``-Modul einsetzen möchten, müssen Sie zuerst einen
 Generator für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
-indem Sie eine Instanz der Klasse ``PersistentDB`` erzeugen, wobei Sie folgende
+indem Sie eine Instanz der Klasse ``persistent_db`` erzeugen, wobei Sie folgende
 Parameter angeben müssen:
 
 * ``creator``: entweder eine Funktion, die neue DB-API-2-Verbindungen
@@ -304,7 +297,7 @@ möchten, dass jede Verbindung Ihrer lokalen Datenbank ``meinedb`` 1000 mal
 wiederverwendet werden soll, sieht die Initialisierung so aus::
 
   import pgdb  # importiere das verwendete DB-API-2-Modul
-  from DBUtils.PersistentDB import PersistentDB
+  from dbutils.persistent_db import PersistentDB
   persist = PersistentDB(pgdb, 1000, database='meinedb')
 
 Nachdem Sie den Generator mit diesen Parametern eingerichtet haben, können
@@ -314,7 +307,7 @@ Sie derartige Datenbankverbindungen von da an wie folgt anfordern::
 
 Sie können diese Verbindungen verwenden, als wären sie gewöhnliche
 DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die
-"gehärtete" ``SteadyDB``-Version der zugrunde liegenden DB-API-2-Verbindung.
+"gehärtete" ``steady_db``-Version der zugrunde liegenden DB-API-2-Verbindung.
 
 Wenn Sie eine solche persistente Verbindung mit ``db.close()`` schließen,
 wird dies stillschweigend ignoriert, denn sie würde beim nächsten Zugriff
@@ -335,11 +328,11 @@ einigen Umgebungen nicht funktionieren (es ist zum Beispiel bekannt, dass
 ``mod_wsgi`` hier Probleme bereitet, da es Daten, die mit ``threading.local``
 gespeichert wurden, zwischen Requests löscht).
 
-PooledDB
---------
-Wenn Sie das ``PooledDB``-Modul einsetzen möchten, müssen Sie zuerst einen
+PooledDB (pooled_db)
+--------------------
+Wenn Sie das ``pooled_db``-Modul einsetzen möchten, müssen Sie zuerst einen
 Pool für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
-indem Sie eine Instanz der Klasse ``PooledDB`` erzeugen, wobei Sie folgende
+indem Sie eine Instanz der Klasse ``pooled_db`` erzeugen, wobei Sie folgende
 Parameter angeben müssen:
 
 * ``creator``: entweder eine Funktion, die neue DB-API-2-Verbindungen
@@ -406,7 +399,7 @@ und einen Pool von mindestens fünf Datenbankverbindungen zu Ihrer Datenbank
 ``meinedb`` verwenden möchten, dann sieht die Initialisierung so aus::
 
   import pgdb  # importiere das verwendete DB-API-2-Modul
-  from DBUtils.PooledDB import PooledDB
+  from dbutils.pooled_db import PooledDB
   pool = PooledDB(pgdb, 5, database='meinedb')
 
 Nachdem Sie den Pool für Datenbankverbindungen so eingerichtet haben, können
@@ -416,7 +409,7 @@ Sie Verbindungen daraus wie folgt anfordern::
 
 Sie können diese Verbindungen verwenden, als wären sie gewöhnliche
 DB-API-2-Datenbankverbindungen. Genauer genommen erhalten Sie die
-"gehärtete" ``SteadyDB``-Version der zugrunde liegenden DB-API-2-Verbindung.
+"gehärtete" ``steady_db``-Version der zugrunde liegenden DB-API-2-Verbindung.
 
 Bitte beachten Sie, dass die Verbindung von anderen Threads mitgenutzt werden
 kann, wenn Sie den Parameter ``maxshared`` auf einen Wert größer als Null
@@ -457,32 +450,6 @@ transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
 wieder an den Verbindungspool zurückgegeben wird.
 
-Benutzung in Webware for Python
--------------------------------
-Wenn Sie DBUtils verwenden, um von Servlets des Web-Frameworks `Webware
-for Python`_ auf eine Datenbank zuzugreifen, dann müssen Sie sicherstellen,
-dass die Generatoren zur Erzeugung von Datenbankverbindungen nur einmal
-eingerichtet werden, wenn die Anwendung startet, und nicht jedes Mal, wenn
-eine Servlet-Instanz erzeugt wird. Den hierfür nötigen Code können Sie
-bei der Basis-Servlet-Klasse einfügen, dort wo das Modul oder die Klasse
-initialisiert wird, oder Sie können die Funktion ``contextInitialize()``
-im ``__init__.py``-Skript Ihres Anwendungskontextes verwenden.
-
-Das zusammen mit DButils ausgelieferte Verzeichnis ``Examples`` enthält
-einen Beispielkontext für Webware for Python, der eine kleine Demo-Datenbank
-verwendet, um Teilnehmer an einer Seminarreihe zu verwalten (die Idee für
-dieses Beispiel wurde dem Artikel "`The Python DB-API`_" von Andrew Kuchling
-entnommen).
-
-Der Beispielkontext kann konfiguriert werden, indem entweder eine Konfig-Datei
-``Configs/Database.config`` angelegt wird, oder indem die Standard-Parameter
-direkt im Beispielservlet ``Examples/DBUtilsExample.py`` geändert werden.
-Auf diese Weise können Sie einen passenden Datenbanknutzer und sein Passwort
-festlegen, sowie den zugrunde liegenden Datenbankadapter auswählen (das
-klassische PyGreSQL-Modul oder irgendein DB-API-2-Modul). Wenn der Parameter
-``maxcached`` vorhanden ist, verwendet das Beispielservlet die
-``Pooled``-Variante, andernfalls die ``Persistent``-Variante.
-
 
 Anmerkungen
 ===========
@@ -495,7 +462,7 @@ ausgelagert, in der Code von DBUtils verwendet wird.
 Wenn Sie eine Lösung verwenden wie den Apache-Webserver mit mod_python_
 oder mod_wsgi_, dann sollten Sie bedenken, dass Ihr Python-Code normalerweise
 im Kontext der Kindprozesse des Webservers läuft. Wenn Sie also das
-``PooledDB``-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann
+``pooled_db``-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann
 werden Sie ebensoviele Pools mit Datenbankverbindungen erhalten. Wenn diese
 Prozesse viele Threads laufen lassen,  dann mag dies eine sinnvoller Ansatz
 sein, wenn aber diese Prozesse nicht mehr als einen Worker-Thread starten,
@@ -511,7 +478,7 @@ Einige Ideen für zukünftige Verbesserungen:
 
 * Alternativ zur Obergrenze in der Anzahl der Nutzung einer Datenbankverbindung
   könnte eine maximale Lebensdauer für die Verbindung implementiert werden.
-* Es könnten Module ``MonitorDB`` und ``MonitorPg`` hinzugefügt werden, die
+* Es könnten Module ``monitor_db`` und ``monitor_pg`` hinzugefügt werden, die
   in einem separaten Thread ständig den "idle pool" und eventuell auch den
   "shared pool" bzw. die persistenten Verbindungen überwachen. Wenn eine
   unterbrochene Datenbankverbindung entdeckt wird, wird diese automatisch durch
@@ -529,12 +496,12 @@ Einige Ideen für zukünftige Verbesserungen:
 
 Fehlermeldungen und Feedback
 ============================
-Bitte Senden Sie Fehlermeldungen, Patches und Feedback direkt an den
-Autor (unter Verwendung der unten angegebenen E-Mail-Adresse).
-
-Probleme, die Webware betreffen, können auch in der `Webware for Python
-mailing list`_ diskutiert werden.
+Fehlermeldungen, Patches und Feedback können Sie als Issues_ oder
+`Pull Requests`_ auf der `GitHub-Projektseite`_ von DBUtils übermitteln.
 
+.. _GitHub-Projektseite: https://github.com/WebwareForPython/DBUtils
+.. _Issues: https://github.com/WebwareForPython/DBUtils/issues
+.. _Pull Requests: https://github.com/WebwareForPython/DBUtils/pulls
 
 Links
 =====
@@ -554,24 +521,23 @@ Einige Links zu verwandter und alternativer Software:
 .. _DBUtils: https://github.com/WebwareForPython/DBUtils
 .. _Python: https://www.python.org
 .. _Webware for Python: https://webwareforpython.github.io/w4py/
-.. _Webware for Python mailing list: https://lists.sourceforge.net/lists/listinfo/webware-discuss
 .. _DB-API 2: https://www.python.org/dev/peps/pep-0249/
 .. _The Python DB-API: http://www.linuxjournal.com/article/2605
 .. _PostgresQL: https://www.postgresql.org/
-.. _PyGreSQL: http://www.pygresql.org/
-.. _SQLObject: http://www.sqlobject.org/
-.. _SQLAlchemy: http://www.sqlalchemy.org
-.. _Apache: http://httpd.apache.org/
+.. _PyGreSQL: https://www.pygresql.org/
+.. _SQLObject: http://sqlobject.org/
+.. _SQLAlchemy: https://www.sqlalchemy.org
+.. _Apache: https://httpd.apache.org/
 .. _mod_python: http://modpython.org/
 .. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
-.. _pgpool: http://www.pgpool.net/
+.. _pgpool: https://www.pgpool.net/
 .. _pgbouncer: https://pgbouncer.github.io/
 
 
 Autoren
 =======
 
-:Autor: Christoph Zwerschke <cito@online.de>
+:Autor: `Christoph Zwerschke`_
 
 :Beiträge: DBUtils benutzt Code, Anmerkungen und Vorschläge von
   Ian Bicking, Chuck Esterbrook (Webware for Python), Dan Green (DBTools),
@@ -579,11 +545,12 @@ Autoren
   Warren Smith (DbConnectionPool), Ezio Vernacotola, Jehiah Czebotar,
   Matthew Harriger, Gregory Piñero und Josef van Eenbergen.
 
+.. _Christoph Zwerschke: https://github.com/Cito
 
 Copyright und Lizenz
 ====================
 
-Copyright © 2005-2018 Christoph Zwerschke.
+Copyright © 2005-2020 Christoph Zwerschke.
 Alle Rechte vorbehalten.
 
 DBUtils ist freie und quelloffene Software,
diff --git a/docs/main.html b/docs/main.html
index 52a6944..5023149 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -19,47 +19,46 @@ <h1 class="title">DBUtils User's Guide</h1>
 <div class="contents topic" id="contents">
 <p class="topic-title first">Contents</p>
 <ul class="simple">
-<li><p><a class="reference internal" href="#synopsis" id="id4">Synopsis</a></p></li>
-<li><p><a class="reference internal" href="#modules" id="id5">Modules</a></p></li>
-<li><p><a class="reference internal" href="#download" id="id6">Download</a></p></li>
-<li><p><a class="reference internal" href="#installation" id="id7">Installation</a></p>
+<li><p><a class="reference internal" href="#synopsis" id="id5">Synopsis</a></p></li>
+<li><p><a class="reference internal" href="#modules" id="id6">Modules</a></p></li>
+<li><p><a class="reference internal" href="#download" id="id7">Download</a></p></li>
+<li><p><a class="reference internal" href="#installation" id="id8">Installation</a></p>
 <ul>
-<li><p><a class="reference internal" href="#installation-as-a-standalone-top-level-package" id="id8">Installation as a standalone (top-level) package</a></p></li>
-<li><p><a class="reference internal" href="#installation-as-a-webware-for-python-subpackage-plug-in" id="id9">Installation as a Webware for Python subpackage (plug-in)</a></p></li>
+<li><p><a class="reference internal" href="#id1" id="id9">Installation</a></p></li>
 </ul>
 </li>
 <li><p><a class="reference internal" href="#requirements" id="id10">Requirements</a></p></li>
 <li><p><a class="reference internal" href="#functionality" id="id11">Functionality</a></p>
 <ul>
-<li><p><a class="reference internal" href="#simplepooleddb" id="id12">SimplePooledDB</a></p></li>
-<li><p><a class="reference internal" href="#steadydb" id="id13">SteadyDB</a></p></li>
-<li><p><a class="reference internal" href="#persistentdb" id="id14">PersistentDB</a></p></li>
-<li><p><a class="reference internal" href="#pooleddb" id="id15">PooledDB</a></p></li>
+<li><p><a class="reference internal" href="#simplepooleddb-simple-pooled-db" id="id12">SimplePooledDB (simple_pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#steadydbconnection-steady-db" id="id13">SteadyDBConnection (steady_db)</a></p></li>
+<li><p><a class="reference internal" href="#persistentdb-persistent-db" id="id14">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#pooleddb-pooled-db" id="id15">PooledDB (pooled_db)</a></p></li>
 <li><p><a class="reference internal" href="#which-one-to-use" id="id16">Which one to use?</a></p></li>
 </ul>
 </li>
 <li><p><a class="reference internal" href="#usage" id="id17">Usage</a></p>
 <ul>
-<li><p><a class="reference internal" href="#id1" id="id18">PersistentDB</a></p></li>
-<li><p><a class="reference internal" href="#id2" id="id19">PooledDB</a></p></li>
-<li><p><a class="reference internal" href="#usage-in-webware-for-python" id="id20">Usage in Webware for Python</a></p></li>
+<li><p><a class="reference internal" href="#id2" id="id18">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#id3" id="id19">PooledDB (pooled_db)</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#notes" id="id21">Notes</a></p></li>
-<li><p><a class="reference internal" href="#future" id="id22">Future</a></p></li>
-<li><p><a class="reference internal" href="#bug-reports-and-feedback" id="id23">Bug reports and feedback</a></p></li>
-<li><p><a class="reference internal" href="#links" id="id24">Links</a></p></li>
-<li><p><a class="reference internal" href="#credits" id="id25">Credits</a></p></li>
-<li><p><a class="reference internal" href="#copyright-and-license" id="id26">Copyright and License</a></p></li>
+<li><p><a class="reference internal" href="#notes" id="id20">Notes</a></p></li>
+<li><p><a class="reference internal" href="#future" id="id21">Future</a></p></li>
+<li><p><a class="reference internal" href="#bug-reports-and-feedback" id="id22">Bug reports and feedback</a></p></li>
+<li><p><a class="reference internal" href="#links" id="id23">Links</a></p></li>
+<li><p><a class="reference internal" href="#credits" id="id24">Credits</a></p></li>
+<li><p><a class="reference internal" href="#copyright-and-license" id="id25">Copyright and License</a></p></li>
 </ul>
 </div>
 <div class="section" id="synopsis">
 <h1>Synopsis</h1>
 <p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a> is a suite of Python modules allowing to connect in a safe and
-efficient way between a threaded <a class="reference external" href="https://www.python.org">Python</a> application and a database. DBUtils
-has been written in view of <a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware for Python</a> as the application and
-<a class="reference external" href="http://www.pygresql.org/">PyGreSQL</a> as the adapter to a <a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a> database, but it can be used
-for any other Python application and <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> conformant database adapter.</p>
+efficient way between a threaded <a class="reference external" href="https://www.python.org">Python</a> application and a database.</p>
+<p>DBUtils has been originally written particularly for <a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware for Python</a> as
+the application and <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> as the adapter to a <a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a> database, but it
+can meanwhile be used for any other Python application and <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a>
+conformant database adapter.</p>
 </div>
 <div class="section" id="modules">
 <h1>Modules</h1>
@@ -68,58 +67,58 @@ <h1>Modules</h1>
 the other one for use with the classic PyGreSQL module.</p>
 <table>
 <colgroup>
-<col style="width: 31%" />
-<col style="width: 69%" />
+<col style="width: 30%" />
+<col style="width: 70%" />
 </colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Universal DB-API 2 variant</p></th>
 </tr>
 </thead>
 <tbody>
-<tr><td><p>SteadyDB.py</p></td>
+<tr><td><p>steady_db</p></td>
 <td><p>Hardened DB-API 2 connections</p></td>
 </tr>
-<tr><td><p>PooledDB.py</p></td>
+<tr><td><p>pooled_db</p></td>
 <td><p>Pooling for DB-API 2 connections</p></td>
 </tr>
-<tr><td><p>PersistentDB.py</p></td>
+<tr><td><p>persistent_db</p></td>
 <td><p>Persistent DB-API 2 connections</p></td>
 </tr>
-<tr><td><p>SimplePooledDB.py</p></td>
+<tr><td><p>simple_pooled_db</p></td>
 <td><p>Simple pooling for DB-API 2</p></td>
 </tr>
 </tbody>
 </table>
 <table>
 <colgroup>
-<col style="width: 31%" />
-<col style="width: 69%" />
+<col style="width: 30%" />
+<col style="width: 70%" />
 </colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Classic PyGreSQL variant</p></th>
 </tr>
 </thead>
 <tbody>
-<tr><td><p>SteadyPg.py</p></td>
+<tr><td><p>steady_pg</p></td>
 <td><p>Hardened classic PyGreSQL connections</p></td>
 </tr>
-<tr><td><p>PooledPg.py</p></td>
+<tr><td><p>pooled_pg</p></td>
 <td><p>Pooling for classic PyGreSQL connections</p></td>
 </tr>
-<tr><td><p>PersistentPg.py</p></td>
+<tr><td><p>persistent_pg</p></td>
 <td><p>Persistent classic PyGreSQL connections</p></td>
 </tr>
-<tr><td><p>SimplePooledPg.py</p></td>
+<tr><td><p>simple_pooled_pg</p></td>
 <td><p>Simple pooling for classic PyGreSQL</p></td>
 </tr>
 </tbody>
 </table>
 <p>The dependencies of the modules in the universal DB-API 2 variant
 are as indicated in the following diagram:</p>
-<img alt="dbdep.png" src="dbdep.png" />
+<img alt="dependencies_db.png" src="dependencies_db.png" />
 <p>The dependencies of the modules in the classic PyGreSQL variant
 are similar:</p>
-<img alt="pgdep.png" src="pgdep.png" />
+<img alt="dependencies_pg.png" src="dependencies_pg.png" />
 </div>
 <div class="section" id="download">
 <h1>Download</h1>
@@ -131,31 +130,18 @@ <h1>Download</h1>
 </div>
 <div class="section" id="installation">
 <h1>Installation</h1>
-<div class="section" id="installation-as-a-standalone-top-level-package">
-<h2>Installation as a standalone (top-level) package</h2>
-<p>If you intend to use DBUtils from other applications than Webware for Python,
-it is recommended to install the package in the usual way:</p>
+<div class="section" id="id1">
+<h2>Installation</h2>
+<p>The package can be installed in the usual way:</p>
 <pre class="literal-block">python setup.py install</pre>
-<p>You can also use <a class="reference external" href="https://pip.pypa.io/">pip</a> for download and installation:</p>
+<p>It is even easier to download and install the package in one go using <a class="reference external" href="https://pip.pypa.io/">pip</a>:</p>
 <pre class="literal-block">pip install DBUtils</pre>
 </div>
-<div class="section" id="installation-as-a-webware-for-python-subpackage-plug-in">
-<h2>Installation as a Webware for Python subpackage (plug-in)</h2>
-<p>If you want to use DBUtils as a supplement for the Webware for Python
-framework only, you should install it as a Webware plug-in:</p>
-<pre class="literal-block">python setup.py install --install-lib=/path/to/Webware</pre>
-<p>Replace <span class="docutils literal">/path/to/Webware</span> with the path to the root directory of
-your Webware for Python installation. You will also need to run the
-Webware installer if this has not been done already or if you want to
-integrate the DBUtils documentation into the Webware documentation:</p>
-<pre class="literal-block">cd path/to/Webware
-python install.py</pre>
-</div>
 </div>
 <div class="section" id="requirements">
 <h1>Requirements</h1>
-<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> version 2.6 and Python versions 3.5 to 3.8.</p>
-<p>The modules in the classic PyGreSQL variant need <a class="reference external" href="http://www.pygresql.org/">PyGreSQL</a> version 4.0
+<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> version 2.7 and Python versions 3.5 to 3.9.</p>
+<p>The modules in the classic PyGreSQL variant need <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
 any Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> compliant database interface module.</p>
 </div>
@@ -163,61 +149,68 @@ <h1>Requirements</h1>
 <h1>Functionality</h1>
 <p>This section will refer to the names in the DB-API 2 variant only,
 but the same applies to the classic PyGreSQL variant.</p>
-<div class="section" id="simplepooleddb">
-<h2>SimplePooledDB</h2>
-<p><span class="docutils literal">DBUtils.SimplePooledDB</span> is a very basic reference implementation of
-a pooled database connection. It is much less sophisticated than the
-regular <span class="docutils literal">PooledDB</span> module and is particularly lacking the failover
-functionality. <span class="docutils literal">DBUtils.SimplePooledDB</span> is essentially the same as
-the <span class="docutils literal">MiscUtils.DBPool</span> module that is part of Webware for Python.
+<p>DBUtils installs itself as a package <span class="docutils literal">dbutils</span> containing all the modules
+that are described in this guide. Each of these modules contains essentially
+one class with an analogous name that provides the corresponding functionality.
+For instance, the module <span class="docutils literal">dbutils.pooled_db</span> contains the class <span class="docutils literal">PooledDB</span>.</p>
+<div class="section" id="simplepooleddb-simple-pooled-db">
+<h2>SimplePooledDB (simple_pooled_db)</h2>
+<p>The class <span class="docutils literal">SimplePooledDB</span> in <span class="docutils literal">dbutils.simple_pooled_db</span> is a very basic
+reference implementation of a pooled database connection. It is much less
+sophisticated than the regular <span class="docutils literal">pooled_db</span> module and is particularly lacking
+the failover functionality. <span class="docutils literal">dbutils.simple_pooled_db</span> is essentially the
+same as the <span class="docutils literal">MiscUtils.DBPool</span> module that is part of Webware for Python.
 You should consider it a demonstration of concept rather than something
 that should go into production.</p>
 </div>
-<div class="section" id="steadydb">
-<h2>SteadyDB</h2>
-<p><span class="docutils literal">DBUtils.SteadyDB</span> is a module implementing &quot;hardened&quot; connections
-to a database, based on ordinary connections made by any DB-API 2
-database module. A &quot;hardened&quot; connection will transparently reopen upon
-access when it has been closed or the database connection has been lost
+<div class="section" id="steadydbconnection-steady-db">
+<h2>SteadyDBConnection (steady_db)</h2>
+<p>The class <span class="docutils literal">SteadyDBConnection</span> in the module <span class="docutils literal">dbutils.steady_db</span> implements
+&quot;hardened&quot; connections to a database, based on ordinary connections made by any
+DB-API 2 database module. A &quot;hardened&quot; connection will transparently reopen
+upon access when it has been closed or the database connection has been lost
 or when it is used more often than an optional usage limit.</p>
 <p>A typical example where this is needed is when the database has been
 restarted while your application is still running and has open connections
 to the database, or when your application accesses a remote database in
 a network that is separated by a firewall and the firewall has been
 restarted and lost its state.</p>
-<p>Usually, you will not use the <span class="docutils literal">SteadyDB</span> module directly; it merely serves
-as a basis for the next two modules, <span class="docutils literal">PersistentDB</span> and <span class="docutils literal">PooledDB</span>.</p>
+<p>Usually, you will not use the <span class="docutils literal">steady_db</span> module directly; it merely serves
+as a basis for the next two modules, <span class="docutils literal">persistent_db</span> and <span class="docutils literal">Pooled_db</span>.</p>
 </div>
-<div class="section" id="persistentdb">
-<h2>PersistentDB</h2>
-<p><span class="docutils literal">DBUtils.PersistentDB</span> implements steady, thread-affine, persistent
-connections to a database, using any DB-API 2 database module.</p>
+<div class="section" id="persistentdb-persistent-db">
+<h2>PersistentDB (persistent_db)</h2>
+<p>The class <span class="docutils literal">PersistentDB</span> in the module <span class="docutils literal">dbutils.persistent_db</span> implements
+steady, thread-affine, persistent connections to a database, using any DB-API 2
+database module. &quot;Thread-affine&quot; and &quot;persistent&quot; means that the individual
+database connections stay assigned to the respective threads and will not be
+closed during the lifetime of the threads.</p>
 <p>The following diagram shows the connection layers involved when you
-are using <span class="docutils literal">PersistentDB</span> connections:</p>
-<img alt="persist.png" src="persist.png" />
+are using <span class="docutils literal">persistent_db</span> connections:</p>
+<img alt="persistent.png" src="persistent.png" />
 <p>Whenever a thread opens a database connection for the first time, a new
 connection to the database will be opened that will be used from now on
 for this specific thread. When the thread closes the database connection,
 it will still be kept open so that the next time when a connection is
 requested by the same thread, this already opened connection can be used.
 The connection will be closed automatically when the thread dies.</p>
-<p>In short: <span class="docutils literal">PersistentDB</span> tries to recycle database connections to
+<p>In short: <span class="docutils literal">persistent_db</span> tries to recycle database connections to
 increase the overall database access performance of your threaded application,
 but it makes sure that connections are never shared between threads.</p>
-<p>Therefore, <span class="docutils literal">PersistentDB</span> will work perfectly even if the underlying
+<p>Therefore, <span class="docutils literal">persistent_db</span> will work perfectly even if the underlying
 DB-API module is not thread-safe at the connection level, and it will
 avoid problems when other threads change the database session or perform
 transactions spreading over more than one SQL command.</p>
 </div>
-<div class="section" id="pooleddb">
-<h2>PooledDB</h2>
-<p><span class="docutils literal">DBUtils.PooledDB</span> implements a pool of steady, thread-safe cached
-connections to a database which are transparently reused, using any
-DB-API 2 database module.</p>
+<div class="section" id="pooleddb-pooled-db">
+<h2>PooledDB (pooled_db)</h2>
+<p>The class <span class="docutils literal">PooledDB</span> in the module <span class="docutils literal">dbutils.pooled_db</span> implements a pool
+of steady, thread-safe cached connections to a database which are transparently
+reused, using any DB-API 2 database module.</p>
 <p>The following diagram shows the connection layers involved when you
-are using <span class="docutils literal">PooledDB</span> connections:</p>
-<img alt="pool.png" src="pool.png" />
-<p>As the diagram indicates, <span class="docutils literal">PooledDB</span> can share opened database connections
+are using <span class="docutils literal">pooled_db</span> connections:</p>
+<img alt="pooled.png" src="pooled.png" />
+<p>As the diagram indicates, <span class="docutils literal">pooled_db</span> can share opened database connections
 between different threads. This will happen by default if you set up the
 connection pool with a positive value of <span class="docutils literal">maxshared</span> and the underlying
 DB-API 2 is thread-safe at the connection level, but you can also request
@@ -229,22 +222,22 @@ <h2>PooledDB</h2>
 connection that is not shared any more, it is returned back to the pool of
 idle connections so that it can be recycled again.</p>
 <p>If the underlying DB-API module is not thread-safe, thread locks will be
-used to ensure that the <span class="docutils literal">PooledDB</span> connections are thread-safe. So you
+used to ensure that the <span class="docutils literal">pooled_db</span> connections are thread-safe. So you
 don't need to worry about that, but you should be careful to use dedicated
 connections whenever you change the database session or perform transactions
 spreading over more than one SQL command.</p>
 </div>
 <div class="section" id="which-one-to-use">
 <h2>Which one to use?</h2>
-<p>Both <span class="docutils literal">PersistentDB</span> and <span class="docutils literal">PooledDB</span> serve the same purpose to improve
+<p>Both <span class="docutils literal">persistent_db</span> and <span class="docutils literal">pooled_db</span> serve the same purpose to improve
 the database access performance by recycling database connections, while
 preserving stability even if database connection will be disrupted.</p>
 <p>So which of these two modules should you use? From the above explanations
-it is clear that <span class="docutils literal">PersistentDB</span> will make more sense if your application
+it is clear that <span class="docutils literal">persistent_db</span> will make more sense if your application
 keeps a constant number of threads which frequently use the database. In
 this case, you will always have the same amount of open database connections.
 However, if your application frequently starts and ends threads, then it
-will be better to use <span class="docutils literal">PooledDB</span>. The latter will also allow more
+will be better to use <span class="docutils literal">pooled_db</span>. The latter will also allow more
 fine-tuning, particularly if you are using a thread-safe DB-API 2 module.</p>
 <p>Since the interface of both modules is similar, you can easily switch from
 one to the other and check which one will suit better.</p>
@@ -255,17 +248,17 @@ <h1>Usage</h1>
 <p>The usage of all the modules is similar, but there are also some differences
 in the initialization between the &quot;Pooled&quot; and &quot;Persistent&quot; variants and also
 between the universal DB-API 2 and the classic PyGreSQL variants.</p>
-<p>We will cover here only the <span class="docutils literal">PersistentDB</span> module and the more complex
-<span class="docutils literal">PooledDB</span> module. For the details of the other modules, have a look
+<p>We will cover here only the <span class="docutils literal">persistent_db</span> module and the more complex
+<span class="docutils literal">pooled_db</span> module. For the details of the other modules, have a look
 at their module docstrings. Using the Python interpreter console, you can
-display the documentation of the <span class="docutils literal">PooledDB</span> module as follows (this
+display the documentation of the <span class="docutils literal">pooled_db</span> module as follows (this
 works analogously for the other modules):</p>
-<pre class="literal-block">help(PooledDB)</pre>
-<div class="section" id="id1">
-<h2>PersistentDB</h2>
-<p>In order to make use of the <span class="docutils literal">PersistentDB</span> module, you first need to set
+<pre class="literal-block">help(pooled_db)</pre>
+<div class="section" id="id2">
+<h2>PersistentDB (persistent_db)</h2>
+<p>In order to make use of the <span class="docutils literal">persistent_db</span> module, you first need to set
 up a generator for your kind of database connections by creating an instance
-of <span class="docutils literal">PersistentDB</span>, passing the following parameters:</p>
+of <span class="docutils literal">persistent_db</span>, passing the following parameters:</p>
 <ul>
 <li><p><span class="docutils literal">creator</span>: either an arbitrary function returning new DB-API 2
 connection objects or a DB-API 2 compliant database module</p></li>
@@ -297,13 +290,13 @@ <h2>PersistentDB</h2>
 <p>For instance, if you are using <span class="docutils literal">pgdb</span> as your DB-API 2 database module and
 want every connection to your local database <span class="docutils literal">mydb</span> to be reused 1000 times:</p>
 <pre class="literal-block">import pgdb  # import used DB-API 2 module
-from DBUtils.PersistentDB import PersistentDB
+from dbutils.persistent_db import PersistentDB
 persist = PersistentDB(pgdb, 1000, database='mydb')</pre>
 <p>Once you have set up the generator with these parameters, you can request
 database connections of that kind:</p>
 <pre class="literal-block">db = persist.connection()</pre>
 <p>You can use these connections just as if they were ordinary DB-API 2
-connections. Actually what you get is the hardened <span class="docutils literal">SteadyDB</span> version of
+connections. Actually what you get is the hardened <span class="docutils literal">steady_db</span> version of
 the underlying DB-API 2 connection.</p>
 <p>Closing a persistent connection with <span class="docutils literal">db.close()</span> will be silently
 ignored since it would be reopened at the next usage anyway and
@@ -319,10 +312,10 @@ <h2>PersistentDB</h2>
 environments (for instance, <span class="docutils literal">mod_wsgi</span> is known to cause problems
 since it clears the <span class="docutils literal">threading.local</span> data between requests).</p>
 </div>
-<div class="section" id="id2">
-<h2>PooledDB</h2>
-<p>In order to make use of the <span class="docutils literal">PooledDB</span> module, you first need to set up the
-database connection pool by creating an instance of <span class="docutils literal">PooledDB</span>, passing the
+<div class="section" id="id3">
+<h2>PooledDB (pooled_db)</h2>
+<p>In order to make use of the <span class="docutils literal">pooled_db</span> module, you first need to set up the
+database connection pool by creating an instance of <span class="docutils literal">pooled_db</span>, passing the
 following parameters:</p>
 <ul>
 <li><p><span class="docutils literal">creator</span>: either an arbitrary function returning new DB-API 2
@@ -369,13 +362,13 @@ <h2>PooledDB</h2>
 <p>For instance, if you are using <span class="docutils literal">pgdb</span> as your DB-API 2 database module and
 want a pool of at least five connections to your local database <span class="docutils literal">mydb</span>:</p>
 <pre class="literal-block">import pgdb  # import used DB-API 2 module
-from DBUtils.PooledDB import PooledDB
+from dbutils.pooled_db import PooledDB
 pool = PooledDB(pgdb, 5, database='mydb')</pre>
 <p>Once you have set up the connection pool you can request database connections
 from that pool:</p>
 <pre class="literal-block">db = pool.connection()</pre>
 <p>You can use these connections just as if they were ordinary DB-API 2
-connections. Actually what you get is the hardened <span class="docutils literal">SteadyDB</span> version of
+connections. Actually what you get is the hardened <span class="docutils literal">steady_db</span> version of
 the underlying DB-API 2 connection.</p>
 <p>Please note that the connection may be shared with other threads by default
 if you set a non-zero <span class="docutils literal">maxshared</span> parameter and the DB-API 2 module allows
@@ -402,45 +395,23 @@ <h2>PooledDB</h2>
 until the end of the transaction, and that the connection will be rolled
 back before being given back to the connection pool.</p>
 </div>
-<div class="section" id="usage-in-webware-for-python">
-<h2>Usage in Webware for Python</h2>
-<p>If you are using DBUtils in order to access a database from <a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware
-for Python</a> servlets, you need to make sure that you set up your
-database connection generators only once when the application starts,
-and not every time a servlet instance is created. For this purpose,
-you can add the necessary code to the module or class initialization
-code of your base servlet class, or you can use the <span class="docutils literal">contextInitialize()</span>
-function in the <span class="docutils literal">__init__.py</span> script of your application context.</p>
-<p>The directory <span class="docutils literal">Examples</span> that is part of the DButils distribution
-contains an example context for Webware for Python that uses a small
-demo database designed to track the attendees for a series of seminars
-(the idea for this example has been taken from the article
-&quot;<a class="reference external" href="http://www.linuxjournal.com/article/2605">The Python DB-API</a>&quot; by Andrew Kuchling).</p>
-<p>The example context can be configured by either creating a config file
-<span class="docutils literal">Configs/Database.config</span> or by directly changing the default parameters
-in the example servlet <span class="docutils literal">Examples/DBUtilsExample.py</span>. This way you can
-set an appropriate database user and password, and you can choose the
-underlying database module (PyGreSQL classic or any DB-API 2 module).
-If the setting <span class="docutils literal">maxcached</span> is present, then the example servlet will use
-the &quot;Pooled&quot; variant, otherwise it will use the &quot;Persistent&quot; variant.</p>
-</div>
 </div>
 <div class="section" id="notes">
 <h1>Notes</h1>
 <p>If you are using one of the popular object-relational mappers <a class="reference external" href="http://www.sqlobject.org/">SQLObject</a>
-or <a class="reference external" href="http://www.sqlalchemy.org">SQLAlchemy</a>, you won't need DBUtils, since they come with their own
+or <a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a>, you won't need DBUtils, since they come with their own
 connection pools. SQLObject 2 (SQL-API) is actually borrowing some code
 from DBUtils to split the pooling out into a separate layer.</p>
 <p>Also note that when you are using a solution like the Apache webserver
 with <a class="reference external" href="http://modpython.org/">mod_python</a> or <a class="reference external" href="https://github.com/GrahamDumpleton/mod_wsgi">mod_wsgi</a>, then your Python code will be usually run
 in the context of the webserver's child processes. So if you are using
-the <span class="docutils literal">PooledDB</span> module, and several of these child processes are running,
+the <span class="docutils literal">pooled_db</span> module, and several of these child processes are running,
 you will have as much database connection pools. If these processes are
 running many threads, this may still be a reasonable approach, but if these
 processes don't spawn more than one worker thread, as in the case of Apache's
 &quot;prefork&quot; multi-processing module, this approach does not make sense.
 If you're running such a configuration, you should resort to a middleware
-for connection pooling that supports multi-processing, such as <a class="reference external" href="http://www.pgpool.net/">pgpool</a>
+for connection pooling that supports multi-processing, such as <a class="reference external" href="https://www.pgpool.net/">pgpool</a>
 or <a class="reference external" href="https://pgbouncer.github.io/">pgbouncer</a> for the PostgreSQL database.</p>
 </div>
 <div class="section" id="future">
@@ -449,7 +420,7 @@ <h1>Future</h1>
 <ul class="simple">
 <li><p>Alternatively to the maximum number of uses of a connection,
 implement a maximum time to live for connections.</p></li>
-<li><p>Create modules <span class="docutils literal">MonitorDB</span> and <span class="docutils literal">MonitorPg</span> that will run in a separate
+<li><p>Create modules <span class="docutils literal">monitor_db</span> and <span class="docutils literal">monitor_pg</span> that will run in a separate
 thread, monitoring the pool of the idle connections and maybe also the
 shared connections respectively the thread-affine connections. If a
 disrupted connection is detected, then it will be reestablished automatically
@@ -466,10 +437,8 @@ <h1>Future</h1>
 </div>
 <div class="section" id="bug-reports-and-feedback">
 <h1>Bug reports and feedback</h1>
-<p>Please send bug reports, patches and feedback directly to the author
-(using the email address given below).</p>
-<p>If there are Webware related problems, these can also be discussed in
-the <a class="reference external" href="https://lists.sourceforge.net/lists/listinfo/webware-discuss">Webware for Python mailing list</a>.</p>
+<p>You can transmit bug reports, patches and feedback by creating <a class="reference external" href="https://github.com/WebwareForPython/DBUtils/issues">issues</a> or
+<a class="reference external" href="https://github.com/WebwareForPython/DBUtils/pulls">pull requests</a> on the GitHub project page for DBUtils.</p>
 </div>
 <div class="section" id="links">
 <h1>Links</h1>
@@ -480,18 +449,18 @@ <h1>Links</h1>
 <li><p><a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware for Python</a> framework</p></li>
 <li><p>Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a></p></li>
 <li><p><a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a> database</p></li>
-<li><p><a class="reference external" href="http://www.pygresql.org/">PyGreSQL</a> Python adapter for PostgreSQL</p></li>
-<li><p><a class="reference external" href="http://www.pgpool.net/">pgpool</a> middleware for PostgreSQL connection pooling</p></li>
+<li><p><a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> Python adapter for PostgreSQL</p></li>
+<li><p><a class="reference external" href="https://www.pgpool.net/">pgpool</a> middleware for PostgreSQL connection pooling</p></li>
 <li><p><a class="reference external" href="https://pgbouncer.github.io/">pgbouncer</a> lightweight PostgreSQL connection pooling</p></li>
 <li><p><a class="reference external" href="http://www.sqlobject.org/">SQLObject</a> object-relational mapper</p></li>
-<li><p><a class="reference external" href="http://www.sqlalchemy.org">SQLAlchemy</a> object-relational mapper</p></li>
+<li><p><a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a> object-relational mapper</p></li>
 </ul>
 </div>
 <div class="section" id="credits">
 <h1>Credits</h1>
 <dl class="field-list simple">
 <dt>Author</dt>
-<dd><p>Christoph Zwerschke &lt;<a class="reference external" href="mailto:cito&#64;online.de">cito&#64;online.de</a>&gt;</p>
+<dd><p><a class="reference external" href="https://github.com/Cito">Christoph Zwerschke</a></p>
 </dd>
 <dt>Contributions</dt>
 <dd><p>DBUtils uses code, input and suggestions made by
@@ -504,7 +473,7 @@ <h1>Credits</h1>
 </div>
 <div class="section" id="copyright-and-license">
 <h1>Copyright and License</h1>
-<p>Copyright © 2005-2018 by Christoph Zwerschke.
+<p>Copyright © 2005-2020 by Christoph Zwerschke.
 All Rights Reserved.</p>
 <p>DBUtils is free and open source software,
 licensed under the <a class="reference external" href="https://opensource.org/licenses/MIT">MIT license</a>.</p>
diff --git a/docs/main.rst b/docs/main.rst
index 59b0b70..96f9804 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -13,10 +13,12 @@ Synopsis
 ========
 
 DBUtils_ is a suite of Python modules allowing to connect in a safe and
-efficient way between a threaded Python_ application and a database. DBUtils
-has been written in view of `Webware for Python`_ as the application and
-PyGreSQL_ as the adapter to a PostgreSQL_ database, but it can be used
-for any other Python application and `DB-API 2`_ conformant database adapter.
+efficient way between a threaded Python_ application and a database.
+
+DBUtils has been originally written particularly for `Webware for Python`_ as
+the application and PyGreSQL_ as the adapter to a PostgreSQL_ database, but it
+can meanwhile be used for any other Python application and `DB-API 2`_
+conformant database adapter.
 
 
 Modules
@@ -26,39 +28,39 @@ The DBUtils suite is realized as a Python package containing
 two subsets of modules, one for use with arbitrary DB-API 2 modules,
 the other one for use with the classic PyGreSQL module.
 
-+-------------------+------------------------------------------+
-| Universal DB-API 2 variant                                   |
-+===================+==========================================+
-| SteadyDB.py       | Hardened DB-API 2 connections            |
-+-------------------+------------------------------------------+
-| PooledDB.py       | Pooling for DB-API 2 connections         |
-+-------------------+------------------------------------------+
-| PersistentDB.py   | Persistent DB-API 2 connections          |
-+-------------------+------------------------------------------+
-| SimplePooledDB.py | Simple pooling for DB-API 2              |
-+-------------------+------------------------------------------+
-
-+-------------------+------------------------------------------+
-| Classic PyGreSQL variant                                     |
-+===================+==========================================+
-| SteadyPg.py       | Hardened classic PyGreSQL connections    |
-+-------------------+------------------------------------------+
-| PooledPg.py       | Pooling for classic PyGreSQL connections |
-+-------------------+------------------------------------------+
-| PersistentPg.py   | Persistent classic PyGreSQL connections  |
-+-------------------+------------------------------------------+
-| SimplePooledPg.py | Simple pooling for classic PyGreSQL      |
-+-------------------+------------------------------------------+
++------------------+------------------------------------------+
+| Universal DB-API 2 variant                                  |
++==================+==========================================+
+| steady_db        | Hardened DB-API 2 connections            |
++------------------+------------------------------------------+
+| pooled_db        | Pooling for DB-API 2 connections         |
++------------------+------------------------------------------+
+| persistent_db    | Persistent DB-API 2 connections          |
++------------------+------------------------------------------+
+| simple_pooled_db | Simple pooling for DB-API 2              |
++------------------+------------------------------------------+
+
++------------------+------------------------------------------+
+| Classic PyGreSQL variant                                    |
++==================+==========================================+
+| steady_pg        | Hardened classic PyGreSQL connections    |
++------------------+------------------------------------------+
+| pooled_pg        | Pooling for classic PyGreSQL connections |
++------------------+------------------------------------------+
+| persistent_pg    | Persistent classic PyGreSQL connections  |
++------------------+------------------------------------------+
+| simple_pooled_pg | Simple pooling for classic PyGreSQL      |
++------------------+------------------------------------------+
 
 The dependencies of the modules in the universal DB-API 2 variant
 are as indicated in the following diagram:
 
-.. image:: dbdep.png
+.. image:: dependencies_db.png
 
 The dependencies of the modules in the classic PyGreSQL variant
 are similar:
 
-.. image:: pgdep.png
+.. image:: dependencies_pg.png
 
 
 Download
@@ -77,39 +79,23 @@ The source code repository can be found here on GitHub::
 Installation
 ============
 
-Installation as a standalone (top-level) package
-------------------------------------------------
-If you intend to use DBUtils from other applications than Webware for Python,
-it is recommended to install the package in the usual way::
+Installation
+------------
+The package can be installed in the usual way::
 
   python setup.py install
 
-You can also use `pip`_ for download and installation::
+It is even easier to download and install the package in one go using `pip`_::
 
   pip install DBUtils
 
 .. _pip: https://pip.pypa.io/
 
-Installation as a Webware for Python subpackage (plug-in)
----------------------------------------------------------
-If you want to use DBUtils as a supplement for the Webware for Python
-framework only, you should install it as a Webware plug-in::
-
-  python setup.py install --install-lib=/path/to/Webware
-
-Replace ``/path/to/Webware`` with the path to the root directory of
-your Webware for Python installation. You will also need to run the
-Webware installer if this has not been done already or if you want to
-integrate the DBUtils documentation into the Webware documentation::
-
-  cd path/to/Webware
-  python install.py
-
 
 Requirements
 ============
 
-DBUtils supports Python_ version 2.6 and Python versions 3.5 to 3.8.
+DBUtils supports Python_ version 2.7 and Python versions 3.5 to 3.9.
 
 The modules in the classic PyGreSQL variant need PyGreSQL_ version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
@@ -122,22 +108,27 @@ Functionality
 This section will refer to the names in the DB-API 2 variant only,
 but the same applies to the classic PyGreSQL variant.
 
-SimplePooledDB
---------------
-``DBUtils.SimplePooledDB`` is a very basic reference implementation of
-a pooled database connection. It is much less sophisticated than the
-regular ``PooledDB`` module and is particularly lacking the failover
-functionality. ``DBUtils.SimplePooledDB`` is essentially the same as
-the ``MiscUtils.DBPool`` module that is part of Webware for Python.
+DBUtils installs itself as a package ``dbutils`` containing all the modules
+that are described in this guide. Each of these modules contains essentially
+one class with an analogous name that provides the corresponding functionality.
+For instance, the module ``dbutils.pooled_db`` contains the class ``PooledDB``.
+
+SimplePooledDB (simple_pooled_db)
+---------------------------------
+The class ``SimplePooledDB`` in ``dbutils.simple_pooled_db`` is a very basic
+reference implementation of a pooled database connection. It is much less
+sophisticated than the regular ``pooled_db`` module and is particularly lacking
+the failover functionality. ``dbutils.simple_pooled_db`` is essentially the
+same as the ``MiscUtils.DBPool`` module that is part of Webware for Python.
 You should consider it a demonstration of concept rather than something
 that should go into production.
 
-SteadyDB
---------
-``DBUtils.SteadyDB`` is a module implementing "hardened" connections
-to a database, based on ordinary connections made by any DB-API 2
-database module. A "hardened" connection will transparently reopen upon
-access when it has been closed or the database connection has been lost
+SteadyDBConnection (steady_db)
+------------------------------
+The class ``SteadyDBConnection`` in the module ``dbutils.steady_db`` implements
+"hardened" connections to a database, based on ordinary connections made by any
+DB-API 2 database module. A "hardened" connection will transparently reopen
+upon access when it has been closed or the database connection has been lost
 or when it is used more often than an optional usage limit.
 
 A typical example where this is needed is when the database has been
@@ -146,18 +137,21 @@ to the database, or when your application accesses a remote database in
 a network that is separated by a firewall and the firewall has been
 restarted and lost its state.
 
-Usually, you will not use the ``SteadyDB`` module directly; it merely serves
-as a basis for the next two modules, ``PersistentDB`` and ``PooledDB``.
+Usually, you will not use the ``steady_db`` module directly; it merely serves
+as a basis for the next two modules, ``persistent_db`` and ``Pooled_db``.
 
-PersistentDB
-------------
-``DBUtils.PersistentDB`` implements steady, thread-affine, persistent
-connections to a database, using any DB-API 2 database module.
+PersistentDB (persistent_db)
+----------------------------
+The class ``PersistentDB`` in the module ``dbutils.persistent_db`` implements
+steady, thread-affine, persistent connections to a database, using any DB-API 2
+database module. "Thread-affine" and "persistent" means that the individual
+database connections stay assigned to the respective threads and will not be
+closed during the lifetime of the threads.
 
 The following diagram shows the connection layers involved when you
-are using ``PersistentDB`` connections:
+are using ``persistent_db`` connections:
 
-.. image:: persist.png
+.. image:: persistent.png
 
 Whenever a thread opens a database connection for the first time, a new
 connection to the database will be opened that will be used from now on
@@ -166,27 +160,27 @@ it will still be kept open so that the next time when a connection is
 requested by the same thread, this already opened connection can be used.
 The connection will be closed automatically when the thread dies.
 
-In short: ``PersistentDB`` tries to recycle database connections to
+In short: ``persistent_db`` tries to recycle database connections to
 increase the overall database access performance of your threaded application,
 but it makes sure that connections are never shared between threads.
 
-Therefore, ``PersistentDB`` will work perfectly even if the underlying
+Therefore, ``persistent_db`` will work perfectly even if the underlying
 DB-API module is not thread-safe at the connection level, and it will
 avoid problems when other threads change the database session or perform
 transactions spreading over more than one SQL command.
 
-PooledDB
---------
-``DBUtils.PooledDB`` implements a pool of steady, thread-safe cached
-connections to a database which are transparently reused, using any
-DB-API 2 database module.
+PooledDB (pooled_db)
+--------------------
+The class ``PooledDB`` in the module ``dbutils.pooled_db`` implements a pool
+of steady, thread-safe cached connections to a database which are transparently
+reused, using any DB-API 2 database module.
 
 The following diagram shows the connection layers involved when you
-are using ``PooledDB`` connections:
+are using ``pooled_db`` connections:
 
-.. image:: pool.png
+.. image:: pooled.png
 
-As the diagram indicates, ``PooledDB`` can share opened database connections
+As the diagram indicates, ``pooled_db`` can share opened database connections
 between different threads. This will happen by default if you set up the
 connection pool with a positive value of ``maxshared`` and the underlying
 DB-API 2 is thread-safe at the connection level, but you can also request
@@ -199,23 +193,23 @@ connection that is not shared any more, it is returned back to the pool of
 idle connections so that it can be recycled again.
 
 If the underlying DB-API module is not thread-safe, thread locks will be
-used to ensure that the ``PooledDB`` connections are thread-safe. So you
+used to ensure that the ``pooled_db`` connections are thread-safe. So you
 don't need to worry about that, but you should be careful to use dedicated
 connections whenever you change the database session or perform transactions
 spreading over more than one SQL command.
 
 Which one to use?
 -----------------
-Both ``PersistentDB`` and ``PooledDB`` serve the same purpose to improve
+Both ``persistent_db`` and ``pooled_db`` serve the same purpose to improve
 the database access performance by recycling database connections, while
 preserving stability even if database connection will be disrupted.
 
 So which of these two modules should you use? From the above explanations
-it is clear that ``PersistentDB`` will make more sense if your application
+it is clear that ``persistent_db`` will make more sense if your application
 keeps a constant number of threads which frequently use the database. In
 this case, you will always have the same amount of open database connections.
 However, if your application frequently starts and ends threads, then it
-will be better to use ``PooledDB``. The latter will also allow more
+will be better to use ``pooled_db``. The latter will also allow more
 fine-tuning, particularly if you are using a thread-safe DB-API 2 module.
 
 Since the interface of both modules is similar, you can easily switch from
@@ -229,19 +223,19 @@ The usage of all the modules is similar, but there are also some differences
 in the initialization between the "Pooled" and "Persistent" variants and also
 between the universal DB-API 2 and the classic PyGreSQL variants.
 
-We will cover here only the ``PersistentDB`` module and the more complex
-``PooledDB`` module. For the details of the other modules, have a look
+We will cover here only the ``persistent_db`` module and the more complex
+``pooled_db`` module. For the details of the other modules, have a look
 at their module docstrings. Using the Python interpreter console, you can
-display the documentation of the ``PooledDB`` module as follows (this
+display the documentation of the ``pooled_db`` module as follows (this
 works analogously for the other modules)::
 
-  help(PooledDB)
+  help(pooled_db)
 
-PersistentDB
-------------
-In order to make use of the ``PersistentDB`` module, you first need to set
+PersistentDB (persistent_db)
+----------------------------
+In order to make use of the ``persistent_db`` module, you first need to set
 up a generator for your kind of database connections by creating an instance
-of ``PersistentDB``, passing the following parameters:
+of ``persistent_db``, passing the following parameters:
 
 * ``creator``: either an arbitrary function returning new DB-API 2
   connection objects or a DB-API 2 compliant database module
@@ -281,7 +275,7 @@ For instance, if you are using ``pgdb`` as your DB-API 2 database module and
 want every connection to your local database ``mydb`` to be reused 1000 times::
 
   import pgdb  # import used DB-API 2 module
-  from DBUtils.PersistentDB import PersistentDB
+  from dbutils.persistent_db import PersistentDB
   persist = PersistentDB(pgdb, 1000, database='mydb')
 
 Once you have set up the generator with these parameters, you can request
@@ -290,7 +284,7 @@ database connections of that kind::
   db = persist.connection()
 
 You can use these connections just as if they were ordinary DB-API 2
-connections. Actually what you get is the hardened ``SteadyDB`` version of
+connections. Actually what you get is the hardened ``steady_db`` version of
 the underlying DB-API 2 connection.
 
 Closing a persistent connection with ``db.close()`` will be silently
@@ -309,10 +303,10 @@ connections may become a bit faster, but this may not work in all
 environments (for instance, ``mod_wsgi`` is known to cause problems
 since it clears the ``threading.local`` data between requests).
 
-PooledDB
---------
-In order to make use of the ``PooledDB`` module, you first need to set up the
-database connection pool by creating an instance of ``PooledDB``, passing the
+PooledDB (pooled_db)
+--------------------
+In order to make use of the ``pooled_db`` module, you first need to set up the
+database connection pool by creating an instance of ``pooled_db``, passing the
 following parameters:
 
 * ``creator``: either an arbitrary function returning new DB-API 2
@@ -371,7 +365,7 @@ For instance, if you are using ``pgdb`` as your DB-API 2 database module and
 want a pool of at least five connections to your local database ``mydb``::
 
   import pgdb  # import used DB-API 2 module
-  from DBUtils.PooledDB import PooledDB
+  from dbutils.pooled_db import PooledDB
   pool = PooledDB(pgdb, 5, database='mydb')
 
 Once you have set up the connection pool you can request database connections
@@ -380,7 +374,7 @@ from that pool::
   db = pool.connection()
 
 You can use these connections just as if they were ordinary DB-API 2
-connections. Actually what you get is the hardened ``SteadyDB`` version of
+connections. Actually what you get is the hardened ``steady_db`` version of
 the underlying DB-API 2 connection.
 
 Please note that the connection may be shared with other threads by default
@@ -417,30 +411,6 @@ with other threads, that the transparent reopening will be suspended
 until the end of the transaction, and that the connection will be rolled
 back before being given back to the connection pool.
 
-Usage in Webware for Python
----------------------------
-If you are using DBUtils in order to access a database from `Webware
-for Python`_ servlets, you need to make sure that you set up your
-database connection generators only once when the application starts,
-and not every time a servlet instance is created. For this purpose,
-you can add the necessary code to the module or class initialization
-code of your base servlet class, or you can use the ``contextInitialize()``
-function in the ``__init__.py`` script of your application context.
-
-The directory ``Examples`` that is part of the DButils distribution
-contains an example context for Webware for Python that uses a small
-demo database designed to track the attendees for a series of seminars
-(the idea for this example has been taken from the article
-"`The Python DB-API`_" by Andrew Kuchling).
-
-The example context can be configured by either creating a config file
-``Configs/Database.config`` or by directly changing the default parameters
-in the example servlet ``Examples/DBUtilsExample.py``. This way you can
-set an appropriate database user and password, and you can choose the
-underlying database module (PyGreSQL classic or any DB-API 2 module).
-If the setting ``maxcached`` is present, then the example servlet will use
-the "Pooled" variant, otherwise it will use the "Persistent" variant.
-
 
 Notes
 =====
@@ -452,7 +422,7 @@ from DBUtils to split the pooling out into a separate layer.
 Also note that when you are using a solution like the Apache webserver
 with mod_python_ or mod_wsgi_, then your Python code will be usually run
 in the context of the webserver's child processes. So if you are using
-the ``PooledDB`` module, and several of these child processes are running,
+the ``pooled_db`` module, and several of these child processes are running,
 you will have as much database connection pools. If these processes are
 running many threads, this may still be a reasonable approach, but if these
 processes don't spawn more than one worker thread, as in the case of Apache's
@@ -468,7 +438,7 @@ Some ideas for future improvements:
 
 * Alternatively to the maximum number of uses of a connection,
   implement a maximum time to live for connections.
-* Create modules ``MonitorDB`` and ``MonitorPg`` that will run in a separate
+* Create modules ``monitor_db`` and ``monitor_pg`` that will run in a separate
   thread, monitoring the pool of the idle connections and maybe also the
   shared connections respectively the thread-affine connections. If a
   disrupted connection is detected, then it will be reestablished automatically
@@ -485,11 +455,12 @@ Some ideas for future improvements:
 
 Bug reports and feedback
 ========================
-Please send bug reports, patches and feedback directly to the author
-(using the email address given below).
+You can transmit bug reports, patches and feedback by creating issues_ or
+`pull requests`_ on the GitHub project page for DBUtils.
 
-If there are Webware related problems, these can also be discussed in
-the `Webware for Python mailing list`_.
+.. _GitHub-Projektseite: https://github.com/WebwareForPython/DBUtils
+.. _Issues: https://github.com/WebwareForPython/DBUtils/issues
+.. _Pull Requests: https://github.com/WebwareForPython/DBUtils/pulls
 
 
 Links
@@ -514,20 +485,20 @@ Some links to related and alternative software:
 .. _DB-API 2: https://www.python.org/dev/peps/pep-0249/
 .. _The Python DB-API: http://www.linuxjournal.com/article/2605
 .. _PostgresQL: https://www.postgresql.org/
-.. _PyGreSQL: http://www.pygresql.org/
+.. _PyGreSQL: https://www.pygresql.org/
 .. _SQLObject: http://www.sqlobject.org/
-.. _SQLAlchemy: http://www.sqlalchemy.org
-.. _Apache: http://httpd.apache.org/
+.. _SQLAlchemy: https://www.sqlalchemy.org
+.. _Apache: https://httpd.apache.org/
 .. _mod_python: http://modpython.org/
 .. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
-.. _pgpool: http://www.pgpool.net/
+.. _pgpool: https://www.pgpool.net/
 .. _pgbouncer: https://pgbouncer.github.io/
 
 
 Credits
 =======
 
-:Author: Christoph Zwerschke <cito@online.de>
+:Author: `Christoph Zwerschke`_
 
 :Contributions: DBUtils uses code, input and suggestions made by
   Ian Bicking, Chuck Esterbrook (Webware for Python), Dan Green (DBTools),
@@ -535,11 +506,13 @@ Credits
   Warren Smith (DbConnectionPool), Ezio Vernacotola, Jehiah Czebotar,
   Matthew Harriger, Gregory Piñero and Josef van Eenbergen.
 
+.. _Christoph Zwerschke: https://github.com/Cito
+
 
 Copyright and License
 =====================
 
-Copyright © 2005-2018 by Christoph Zwerschke.
+Copyright © 2005-2020 by Christoph Zwerschke.
 All Rights Reserved.
 
 DBUtils is free and open source software,
diff --git a/docs/persist.png b/docs/persist.png
deleted file mode 100644
index e76856d7be4aaee2d008839e503b5c7d513f279d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 7063
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

diff --git a/docs/persistent.png b/docs/persistent.png
new file mode 100644
index 0000000000000000000000000000000000000000..3eb2782a8e463375382dc482d77927a1e3366390
GIT binary patch
literal 7038
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

literal 0
HcmV?d00001

diff --git a/docs/pgdep.png b/docs/pgdep.png
deleted file mode 100644
index f235e436be4d796378650186b8fbd7fa1bc94e84..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3129
zcmV-9494?`P)<h;3K|Lk000e1NJLTq00CYA005Q<00000vy8v100004XF*Lt006O%
z3;baP0000WV@Og>004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002
zbW%=J0RAkN5+eWr00Lr5M??Sss*NKu00007bV*G`2jmGF5Dy8F*CC|<01GooL_t(|
z+U;FWXdBrX|K+i_lEbp09y^Iol7oAS54K7V3HHGlEww4xLop_YZjBBm1hNe-7~vGt
z!y+)4;)8IyhsB6J)Q3%p2?od5hvFy{2MP^q`Q+`&FOX%SW_x_}F#q02GuHfh|E$$_
z(2Qo@e82De>C>B;_eMWmVd$AWBf10VZtKx2)q3<wwGUd}3!%G1e=Y(e;VDB|6}7|_
zpqC^LJ2DxTW}hGkNswu<u5fRNz#yW03B-u&N@Q9|@i=<Lh7szG2rZ*{-c++2h|BMk
z1c$*wyE~y6v7D)AmygRML3kJ;{EEh4ozGYr*yZDLS0sedmb`e3j{It2my0Vjz9J!n
z^waho<)OgaT0f0$8*UdhZ}Fb+=s3_r(J0*JD3lc0hkyL@jIoiCcxfbQ8@oAO58sp!
zEI`f)W4V&(lsYtZ=neT2J$`o@JM{3jEni(9Bf|B{vAafR{@1$G-^3r?p?BcZ`sX~b
zUFA_yq!hQQ8&`+Ut4v6VUjjgpkP3hzDgD4VEs~-21mD{tAr;U<r8wk+c&qEL7l&M+
zVriX_nzJb_8USoKDGkv=rA$b@NJt&DP$_%gZYQ%e*+&EvO>ZSPhT796`?dsc2>`{L
ztogSJ$|tP)oNHS?VaLxA&nmew#|t*LkN5t3xVupY0Jy6Qqn{tUKzMeKEj_U}d4VNC
zb8PLz=V)W4IXd$NTbcu~y1+)3`OG|&1ldOg6i)93A>`-)bnTIaxo6Z}0zl)%U04q{
z=*~Pa=ePCWcQCuT;{_N8fc<ec=Ir)Yy(ZHZhR<m*u()WP<=N(jTb%Q-KAN`dbuYEL
z3+Sd4Kl{jlqEBsS7J%zO|GQIv3H%3hgv_0v;yTdJw{_olFuS?uB?pWzEfQi^FSqIf
zHKGGcMlUeSmR3Xi6FapZZ9p1KDQ5N&0d?qmzXQ(t2K^;)1DlMovq5|ZoV7NMovG&q
z09M#H*hlNC(<C1qSW<d{iSYB4o!ZZAKr2=D5dk^$hM$40hpQBu)0Vpgfc(@xP%K%K
z8^9*lbaDf9t;Kina)iflyZ``dq<(euIsjnZS+zb2Hi5mV9j(J{0|(h472suVtgVfg
z+E@@UN%n2&yK!Ayy|cd`AUCW25&*WpCFEumD>*XsG1qh@NABE5Y;(K-!0gg52X4;-
z02bWUxy63UHG#dUkroX=es__Orw8pj9<7s;YHLhMvyTeM9WfJf_gMSvi2G`_`4oJI
zTKnwC>g2K`B%S%>vB-d0aNX*SF*<Uw75gdPPG7`sc8oT(j_e79Op?AAmp5C%GS=4G
ztpW5MaT`MXWyNSki{VZjWP<d4%-(Ric;n*gO$G_Kh{>SdRHpd2uCTV<*{wGb@7k5b
zpR0=Rnx;tx@wgu(K7xroKZRx~*?%Og@WhhSD<=+x?jupnaLQ!G;zXpUb_ryh0F(E<
zPAIY}Oir4yGLnFss&jB6lEH$O(shRo#d}Uwz5YmroJ7@Due4;4wpv)GqdP^Thg~U_
zzH@M56FZ2Y+<ZtWA8NYJA;5&Nxa4(#F|v>YE1+Nm)trV4GU@E5?i`rti!-8Z<eN*v
zMwW<>+BbvHxd5MC)fc_?ID^Ei`1B@>UjhQAWgJH;yBX+Z01?J7V|vJyJvNtt9{Xb#
z*mDsE<rlroPeqFVtPaHSOPHp9S0+G)W1UZ4)P<SUPVfRqnV^}t5zDV~lh1DIySC6c
zb^9<6QOwk3b}!w@uAo~MrfG^HOX5mCPa&N{_HdOy>Y?ZyDvaJt4C#E3MlO&rPZ>Jk
zu4Px`aM7i?KvVc>ilihhzlw`jC7jTTOUG{)vnzNk`4aRGYClZDe5Bx4alV&(8agq*
zUCpleiKc}7b~n2c=J5>ff7K6ch`MXj@WgrY7}d`=WqeA<iCg{eCDORpFq<c$dNuso
z$1K;;mYrT5zg0`)SkE+nb{FcU-_qo%X`bJ-xaC}UX#1&=*ti|7un4R9T0YSNU^4jC
z#jfD%URNtDM24)@GANQIPg0veyjXf2t*{UovQSGRm6jz(ee(Mm>1~bN)(Ycg$Y{ao
z1d62e1J27sK*Lod0uUkqCc1af$gbe(b+p2G8M5Df!q~_%9ObX86DM`Nwq<`(E0QJ5
zQ0HGqh^3d)3Ug)1wjY<ti#<>1Yj||umi@_~NR})?t$)lR`ZVong}E|hMKa8$09WYC
zn%tn@-$h16viRxgWj8CmA^oJI73L^@;l<&st+nR}eceCbI-TI#w+Vm=y~o!4r!qpT
z*VPIO$dK)0@5LTG<*)q6176T5O#e(=a@zl~M<~6XR+ukCmfu_?rQLJtD}T-5E`LmN
zMY8z0X=gY4kWS(pYIBFJQg{LVGhw6{z^;a;(>H@cgRS~!UB#~Q0m;TK1dF$E3+VHU
zSJ;505$tyDaJm`n3VJ%-xXB<x*zMA(OjFnu_<XNHDI3GCN(lvJKM*j7U4`^ARbU3O
zt5^U;MFE?{u1b2D5<H{WRWDy7xq!`LS1G-*)bKN=V-~wwWZC3ln#Qie>t28(Rt~Ch
z?CR5UmEqJlb`@8z6!|mHw|VSp{1bEG0*MB)tGasSY110Xu9oWUs)zJCgryiyxO|kI
zg`w<<`E7-6GnHHoor8q(HJ4poR}R9}<qS1lvZF%P<;*qJ7iPW*qQz)-HQrW;sb90%
z)kv=pehp_=C%pprHJx3p^kxs<bawU9o3VV&XIFFeX5#k}u&egMO#WwS8sqB59F+WH
zrOLm>jAP2GSDkF5N&I%-b6-Yj`6!A-Wl5RDZ#O<wXAr+iB2nEPn!~R#fpn+!=#^?J
z=uO@?&_bo`sz9`;J;X9&dIwYDXtmV^R|P_@+Jj^>mEK1d=AKdMY7er_RC)pQzdMzz
z_8{L3ruVFGFhuR)a+#_0u2O7HTT;~?RG6vsK3XRyRjFzZO3YMxvBHi4V4EF(s5_Jl
zW-Pt7wW>W_HZ%MYzO`Ee=sVIKMS4>)oUUPj+mtbN!#jt|ZasRXT900-rjp*h8Z87x
zk!gWAl9Qgf0?Q3TNFAVs;O^G<fsM4JFq>GWkY4)LEWj8$SoXHee9hQ@YkZ^P6UM3~
zpz$$d>`6VIXX}--{B14X0m@7vy)Qqtm-+Xo)j><Z%fDE_!aiuJJOmWU5a{<~!1#NL
zFAo5G@z;QZqFwm3Ku8_fBcz|&ue9Lo9N*H-XNxJMcgxW)Yh1(V3C`~VTY~_sD)8pl
z$0*OS=Ot^h1YpaeI~D-zJwK3MeqtB?!q=?%0Q8TH9W2|g{?2<b>s^(pqnBO-07A%}
zKOz4XuO|R_w_{_VJG03DoUZ)A?woG}0Di$AzcYq>6~LAS>jtbF6#1(C>hJ77(M@R3
z)X{qm0B}`%8*eQD6tv)->IflEfjnbJGZb$C0RQG4+;&%Lg!~IwpCP2~ymC9uG8OdN
zGhbKvO%prA0PdZ_dc}U_He>AUyaK>_cxp{jH0tykKicrdSr&7w`5|DvV$BE6tH0C9
z%8;8jh1F}l=iUr|IRRke0R<ZWuy;?^KnwZ3I)JS~+`ohT-v>VBkDsiA7GR(KKpQ0h
zsF#8}&vIJXY&WLBY>>@E)*N*=E!m4HAjh1|Q@|l(Q$SynG4>76w~Ni@C;)u!H12CQ
zN7*hgzjVkpfxf2w+8niaGWHuD&;V2Ogd|}A=yKKDK(`iT;Ow2vdT>UVn$Cd{1TOmb
zM()Xgp<fxsuCnKQf07E4>P~Iyo|`d=U6l%`y@&thCokEI;ubo8vi*~=-f+59>(MLK
z{tqKd^VrEB9##MV03~!qSaf7zbY(hYa%Ew3WdJfTGB7PLIW00aR53U@FfckbH!CnQ
zIxsM`FvxZQ001R)MObuXVRU6WZEs|0W_bWIFfuSLFgYzUHdHY*Ix#gmFgYtQGCD9Y
TVXvr+00000NkvXXu0mjfk(cn*

diff --git a/docs/pool.png b/docs/pool.png
deleted file mode 100644
index c6677b04881669f261033623168bbe5f49118b04..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 11963
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
E2RKJmU;qFB

diff --git a/docs/pooled.png b/docs/pooled.png
new file mode 100644
index 0000000000000000000000000000000000000000..dc7ea83afdf91caff6ed0a06912f338aa980549a
GIT binary patch
literal 12053
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
LdK$%dpTGHkm?OQ0

literal 0
HcmV?d00001


From 9bf992981d943ebfb31b719685189ecb9d31fc69 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 20:39:47 +0200
Subject: [PATCH 08/84] Update README

---
 README.md | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 3697d59..b35abf9 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,11 @@ DBUtils
 =======
 
 DBUtils is a suite of tools providing solid, persistent and pooled connections
-to a database that can be used in all kinds of multi-threaded environments
-like Webware for Python or other web application servers. The suite supports
-DB-API 2 compliant database interfaces and the classic PyGreSQL interface.
+to a database that can be used in all kinds of multi-threaded environments.
 
-The current version 2.0 of DBUtils supports Python versions 2.7 and 3.5 - 3.8.
+The suite supports DB-API 2 compliant database interfaces
+and the classic PyGreSQL interface.
 
-The DBUtils home page can be found here: https://webwareforpython.github.io/DBUtils/
+The current version 2.0 of DBUtils supports Python versions 2.7 and 3.5 to 3.9.
+
+The DBUtils home page can be found at https://webwareforpython.github.io/DBUtils/

From 25e64c4074ea3e4f699bed5dea271a62e88dd4be Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 20:40:21 +0200
Subject: [PATCH 09/84] Remove outdated "creating a release" notes

---
 release.md | 58 ------------------------------------------------------
 1 file changed, 58 deletions(-)
 delete mode 100644 release.md

diff --git a/release.md b/release.md
deleted file mode 100644
index aec81f3..0000000
--- a/release.md
+++ /dev/null
@@ -1,58 +0,0 @@
-Create a new DBUtils release:
-=============================
-
-* Check the documentation. If possible, update all translations.
-  (Chinese translation was too old and has been removed for the time being.)
-
-* Use tox to run all tests in DBUtils/Tests with all supported Python versions
-  and to run flake8 in order to check the code style and quality.
-
-* Check the examples in DBUtils/Examples with the current Webware version.
-
-* Update and check the Release Notes and copyright information.
-
-* Set version number and release date with `setversion.py`.
-
-* Revert to old version number for translations that have not been updated.
-
-* Build html pages using `buildhtml.py`.
-
-* Create a tag in the Git repository.
-
-* Create a source tarball with:
-
-        python setup.py sdist
-
-  You will find the tarball in the "dist" folder.
-
-  Generally, it is better to create the release under Unix to avoid
-  problems with DOS line feeds and wrong file permission.
-
-* Upload to the Python Package Index:
-
-    Create a .pypirc file in your home directory as follows:
-
-        echo "[pypi]
-        repository: https://upload.pypi.org/legacy/
-        username: your username
-        password: your password
-        
-        [pypitest]
-        repository: https://test.pypi.org/legacy/
-        username: your username
-        password: your password     
-        " > ~.pypirc
-
-
-* Upload the source package to the test PyPI with:
-
-        twine upload -r pypitest dist/*.tar.gz
- 
-* Register and upload the project to the real PyPI with:
-
-        twine upload -r pypi dist/*.tar.gz
-
-* Don't forget to update the home page:
-
-    * https://webwareforpython.github.io/DBUtils/
-    * https://webwareforpython.github.io/w4py/

From 765a0fed6564acbe0ca0be7a1d15ac7bfafa1290 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 20:40:44 +0200
Subject: [PATCH 10/84] Check manifest (create proper source distribution)

---
 .pylintrc   | 317 ----------------------------------------------------
 MANIFEST.in |   7 +-
 tox.ini     |  19 ++--
 3 files changed, 16 insertions(+), 327 deletions(-)
 delete mode 100644 .pylintrc

diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 4c3d6ec..0000000
--- a/.pylintrc
+++ /dev/null
@@ -1,317 +0,0 @@
-# lint Python modules using external checkers.
-#
-# This is the main checker controling the other ones and the reports
-# generation. It is itself both a raw checker and an astng checker in order
-# to:
-# * handle message activation / deactivation at the module level
-# * handle some basic but necessary stats'data (number of classes, methods...)
-
-#
-[MASTER]
-
-# Specify a configuration file.
-#rcfile=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Profiled execution.
-profile=no
-
-# Add <file or directory> to the black list. It should be a base name, not a
-# path. You may set this option multiple times.
-ignore=CVS
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# Set the cache size for astng objects.
-cache-size=500
-
-# List of plugins (as comma separated values of python modules names) to load,
-
-# usually to register additional checkers.
-load-plugins=
-
-
-[MESSAGES CONTROL]
-
-# Enable only checker(s) with the given id(s). This option conflict with the
-# disable-checker option
-#enable-checker=
-
-# Enable all checker(s) except those with the given id(s). This option conflict
-# with the disable-checker option
-#disable-checker=
-
-# Enable all messages in the listed categories.
-#enable-msg-cat=
-
-# Disable all messages in the listed categories.
-#disable-msg-cat=
-
-# Enable the message(s) with the given id(s).
-#enable-msg=
-
-# Disable the message(s) with the given id(s).
-#disable-msg=
-
-
-[REPORTS]
-
-# set the output format. Available formats are text, parseable, colorized, msvs
-# (visual studio) and html
-output-format=text
-
-# Include message's id in output
-include-ids=no
-
-# Put messages in a separate file for each module / package specified on the
-# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]".
-files-output=no
-
-# Tells wether to display a full report or only the messages
-reports=yes
-
-# Python expression which should return a note less than 10 (10 is the highest
-# note).You have access to the variables errors warning, statement which
-# respectivly contain the number of errors / warnings messages and the total
-# number of statements analyzed. This is used by the global evaluation report
-# (R0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-
-# Add a comment according to your evaluation note. This is used by the global
-# evaluation report (R0004).
-comment=no
-
-# Enable the report(s) with the given id(s).
-#enable-report=
-
-# Disable the report(s) with the given id(s).
-#disable-report=
-
-
-# checks for :
-
-# * doc strings
-# * modules / classes / functions / methods / arguments / variables name
-# * number of arguments, local variables, branchs, returns and statements in
-# functions, methods
-# * required module attributes
-# * dangerous default values as arguments
-# * redefinition of function / method / class
-# * uses of the global statement
-#
-[BASIC]
-
-# Required attributes for module, separated by a comma
-required-attributes=
-
-# Regular expression which should only match functions or classes name which do
-
-# not require a docstring
-no-docstring-rgx=__.*__
-
-# Regular expression which should only match correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression which should only match correct module level names
-const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$
-
-# Regular expression which should only match correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression which should only match correct function names
-function-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct instance attribute names
-attr-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct argument names
-argument-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct variable names
-variable-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match correct list comprehension /
-
-# generator expression variable names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Good variable names which should always be accepted, separated by a comma
-good-names=i,j,k,ex,Run,_
-
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
-
-# List of builtins function names that should not be used, separated by a comma
-bad-functions=map,filter,apply,input
-
-
-# try to find bugs in the code using type inference
-#
-[TYPECHECK]
-
-# Tells wether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# When zope mode is activated, consider the acquired-members option to ignore
-# access to some undefined attributes.
-zope=no
-
-# List of members which are usually get through zope's acquisition mecanism and
-# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
-acquired-members=REQUEST,acl_users,aq_parent
-
-
-# checks for
-# * unused variables / imports
-# * undefined variables
-# * redefinition of variable from builtins or from an outer scope
-# * use of variable before assigment
-#
-[VARIABLES]
-
-# Tells wether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching names used for dummy variables (i.e. not used).
-dummy-variables-rgx=_|dummy
-
-# List of additional names supposed to be defined in builtins. Remember that
-
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-
-# checks for :
-# * methods without self as first argument
-# * overridden methods signature
-# * access only to existant members via self
-# * attributes not defined in the __init__ method
-# * supported interfaces implementation
-# * unreachable code
-#
-[CLASSES]
-
-# List of interface methods to ignore, separated by a comma. This is used for
-# instance to not check methods defines in Zope's Interface base class.
-ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
-
-
-# checks for sign of poor/misdesign:
-# * number of methods, attributes, local variables...
-# * size, complexity of functions, methods
-#
-[DESIGN]
-
-# Maximum number of arguments for function / method
-#max-args=5
-max-args=15
-
-# Maximum number of locals for function / method body
-max-locals=15
-
-# Maximum number of return / yield for function / method body
-max-returns=6
-
-# Maximum number of branch for function / method body
-#max-branchs=12
-max-branchs=25
-
-# Maximum number of statements in function / method body
-#max-statements=50
-max-statements=75
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of attributes for a class (see R0902).
-#max-attributes=7
-max-attributes=20
-
-# Minimum number of public methods for a class (see R0903).
-#min-public-methods=2
-min-public-methods=1
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-
-# checks for
-# * external modules dependencies
-# * relative / wildcard imports
-# * cyclic imports
-# * uses of deprecated modules
-#
-[IMPORTS]
-
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
-
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report R0402 must not be disabled)
-import-graph=
-
-# Create a graph of external dependencies in the given file (report R0402 must
-# not be disabled)
-ext-import-graph=
-
-# Create a graph of internal dependencies in the given file (report R0402 must
-# not be disabled)
-int-import-graph=
-
-
-# checks for :
-# * unauthorized constructions
-# * strict indentation
-# * line length
-# * use of <> instead of !=
-#
-[FORMAT]
-
-# Maximum number of characters on a single line.
-max-line-length=80
-
-# Maximum number of lines in a module
-max-module-lines=1000
-
-# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
-# tab).
-indent-string='    '
-
-
-# checks for:
-# * warning notes in the code like FIXME, XXX
-# * PEP 263: source code with non ascii character but no encoding declaration
-#
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
-
-
-# checks for similarities and duplicated code. This computation may be
-# memory / CPU intensive, so you should disable it if you experiments some
-# problems.
-
-#
-[SIMILARITIES]
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-# Ignore comments when computing similarities.
-ignore-comments=yes
-
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
diff --git a/MANIFEST.in b/MANIFEST.in
index 9896216..2d8e122 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,9 +3,12 @@ include MANIFEST.in
 include LICENSE
 include README.md
 
-recursive-include docs *.rst make.py *.html *.css *.png
+include .bumpversion.cfg
+include tox.ini
+
+recursive-include tests *.py
 
+recursive-include docs *.rst make.py *.html *.css *.png
 prune docs/_build
-exclude release.md setversion.py
 
 global-exclude *.py[co] __pycache__
diff --git a/tox.ini b/tox.ini
index 0e56291..c0155ef 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,18 +1,21 @@
 [tox]
-envlist = py{27,35,36,37,38,39}, flake8
+envlist = py{27,35,36,37,38,39}, flake8, manifest
 
 [testenv]
 setenv =
     PYTHONPATH = {toxinidir}
-deps =
-    pytest
+deps = pytest
 commands =
-    pytest
+    pytest {posargs}
 
 [testenv:flake8]
-basepython =
-    python3.8
-deps =
-    flake8
+basepython = python3.8
+deps = flake8
 commands =
     flake8 dbutils tests docs setup.py
+
+[testenv:manifest]
+basepython = python3.8
+deps = check-manifest
+commands =
+    check-manifest -v

From f136802f907ac35b431089e641ab4e0ba1fd0fd8 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 22:40:31 +0200
Subject: [PATCH 11/84] Test that docs can be built properly

---
 .travis.yml         | 39 +++++++++++++++++++++++++++------------
 docs/changelog.html |  4 ++--
 docs/main.de.html   |  4 ++--
 docs/main.html      |  4 ++--
 docs/make.py        | 18 +++++++++---------
 tox.ini             |  9 ++++++++-
 6 files changed, 50 insertions(+), 28 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 5da2af9..e705d3c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,16 +2,31 @@
 # see https://docs.travis-ci.com/user/languages/python
 
 language: python
-
-python:
-  - "2.7"
-  - "3.5"
-  - "3.6"
-  - "3.7"
-  - "3.8"
-
+matrix:
+  include:
+    - name: Code quality tests
+      env: TOXENV=flake8,manifest,docs
+      python: 3.8
+    - name: Unit tests with Python 3.8
+      env: TOXENV=py38
+      python: 3.8
+    - name: Unit tests with Python 3.7
+      env: TOXENV=py37
+      python: 3.7
+    - name: Unit tests with Python 3.6
+      env: TOXENV=py36
+      python: 3.6
+    - name: Unit tests with Python 3.5
+      env: TOXENV=py35
+      python: 3.5
+    - name: Unit tests with Python 2.7
+      env: TOXENV=py27
+      python: 2.7
+cache:
+  directories:
+    - "$HOME/.cache/pip"
+    - "$TRAVIS_BUILD_DIR/.tox"
 install:
-  - pip install pytest
-  - pip install .
-
-script: pytest DBUtils/Tests/Test*.py
+  - python setup.py install
+script:
+  - tox -e $TOXENV
diff --git a/docs/changelog.html b/docs/changelog.html
index fc37f48..946cda8 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -2,7 +2,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8"/>
-<meta name="generator" content="Docutils 0.15.2: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
 <title>Changelog for DBUtils</title>
 <link rel="stylesheet" href="Doc.css" type="text/css" />
 </head>
@@ -11,7 +11,7 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <div class="contents topic" id="contents">
-<p class="topic-title first">Contents</p>
+<p class="topic-title">Contents</p>
 <ul class="simple">
 <li><p><a class="reference internal" href="#id1" id="id21">2.0</a></p>
 <ul>
diff --git a/docs/main.de.html b/docs/main.de.html
index b744c1f..3d63022 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -2,7 +2,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
 <head>
 <meta charset="utf-8"/>
-<meta name="generator" content="Docutils 0.15.2: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
 <title>Benutzeranleitung für DBUtils</title>
 <link rel="stylesheet" href="Doc.css" type="text/css" />
 </head>
@@ -17,7 +17,7 @@ <h1 class="title">Benutzeranleitung für DBUtils</h1>
 </dd>
 </dl>
 <div class="contents topic" id="inhalt">
-<p class="topic-title first">Inhalt</p>
+<p class="topic-title">Inhalt</p>
 <ul class="simple">
 <li><p><a class="reference internal" href="#zusammenfassung" id="id5">Zusammenfassung</a></p></li>
 <li><p><a class="reference internal" href="#module" id="id6">Module</a></p></li>
diff --git a/docs/main.html b/docs/main.html
index 5023149..22a6ea9 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -2,7 +2,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8"/>
-<meta name="generator" content="Docutils 0.15.2: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
 <title>DBUtils User's Guide</title>
 <link rel="stylesheet" href="Doc.css" type="text/css" />
 </head>
@@ -17,7 +17,7 @@ <h1 class="title">DBUtils User's Guide</h1>
 </dd>
 </dl>
 <div class="contents topic" id="contents">
-<p class="topic-title first">Contents</p>
+<p class="topic-title">Contents</p>
 <ul class="simple">
 <li><p><a class="reference internal" href="#synopsis" id="id5">Synopsis</a></p></li>
 <li><p><a class="reference internal" href="#modules" id="id6">Modules</a></p></li>
diff --git a/docs/make.py b/docs/make.py
index f2153df..d81cfab 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -2,8 +2,6 @@
 
 """Build HMTL from reST files."""
 
-from __future__ import print_function
-
 from glob import glob
 from os.path import splitext
 from docutils.core import publish_file
@@ -24,12 +22,14 @@
 
     with open(rst_file, encoding='utf-8-sig') as source:
         with open(html_file, 'w', encoding='utf-8') as destination:
-            publish_file(writer_name='html5',
-                         source=source, destination=destination,
-                         settings_overrides=dict(
-                            stylesheet_path='Doc.css',
-                            embed_stylesheet=False,
-                            toc_backlinks=False,
-                            language_code=lang))
+            output = publish_file(
+                writer_name='html5', source=source, destination=destination,
+                enable_exit_status=True,
+                settings_overrides=dict(
+                    stylesheet_path='Doc.css',
+                    embed_stylesheet=False,
+                    toc_backlinks=False,
+                    language_code=lang,
+                    exit_status_level=2))
 
 print("Done.")
diff --git a/tox.ini b/tox.ini
index c0155ef..2de6636 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py{27,35,36,37,38,39}, flake8, manifest
+envlist = py{27,35,36,37,38,39}, flake8, manifest, docs
 
 [testenv]
 setenv =
@@ -19,3 +19,10 @@ basepython = python3.8
 deps = check-manifest
 commands =
     check-manifest -v
+
+[testenv:docs]
+basepython = python3.8
+deps = docutils
+changedir = docs
+commands =
+    python make.py

From 78f7b23b91a420d8aed31c0e9a0cbe570d1f5417 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 23:02:34 +0200
Subject: [PATCH 12/84] Add project URLs to setup.py

---
 docs/changelog.html | 132 +++++++++-----------------------------------
 docs/changelog.rst  |  27 +++------
 setup.py            |  14 ++++-
 3 files changed, 44 insertions(+), 129 deletions(-)

diff --git a/docs/changelog.html b/docs/changelog.html
index 946cda8..d473594 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -10,132 +10,65 @@
 <div class="document" id="changelog-for-dbutils">
 <h1 class="title">Changelog for DBUtils</h1>
 
-<div class="contents topic" id="contents">
-<p class="topic-title">Contents</p>
-<ul class="simple">
-<li><p><a class="reference internal" href="#id1" id="id21">2.0</a></p>
-<ul>
-<li><p><a class="reference internal" href="#changes" id="id22">Changes:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id2" id="id23">1.4</a></p>
-<ul>
-<li><p><a class="reference internal" href="#improvements" id="id24">Improvements:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id3" id="id25">1.3</a></p>
-<ul>
-<li><p><a class="reference internal" href="#id4" id="id26">Improvements:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id5" id="id27">1.2</a></p></li>
-<li><p><a class="reference internal" href="#id6" id="id28">1.1.1</a></p>
-<ul>
-<li><p><a class="reference internal" href="#id7" id="id29">Improvements:</a></p></li>
-<li><p><a class="reference internal" href="#bugfixes" id="id30">Bugfixes:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id8" id="id31">1.1</a></p>
-<ul>
-<li><p><a class="reference internal" href="#id9" id="id32">Improvements:</a></p></li>
-<li><p><a class="reference internal" href="#id10" id="id33">Bugfixes:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id11" id="id34">1.0</a></p>
-<ul>
-<li><p><a class="reference internal" href="#id12" id="id35">Changes:</a></p></li>
-<li><p><a class="reference internal" href="#bugfixes-and-improvements" id="id36">Bugfixes and Improvements:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id13" id="id37">0.9.4</a></p></li>
-<li><p><a class="reference internal" href="#id14" id="id38">0.9.3</a></p>
-<ul>
-<li><p><a class="reference internal" href="#id15" id="id39">Changes:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id16" id="id40">0.9.2</a></p>
-<ul>
-<li><p><a class="reference internal" href="#id17" id="id41">Changes:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id18" id="id42">0.9.1</a></p>
-<ul>
-<li><p><a class="reference internal" href="#id19" id="id43">Changes:</a></p></li>
-</ul>
-</li>
-<li><p><a class="reference internal" href="#id20" id="id44">0.8.1 - 2005-09-13</a></p></li>
-</ul>
-</div>
 <div class="section" id="id1">
 <h1>2.0</h1>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
-<div class="section" id="changes">
-<h2>Changes:</h2>
+<p>Changes:</p>
 <ul class="simple">
 <li><p>DBUtils does not act as a Webware plugin anymore, it is now just an ordinary
 Python package (of course it could be used as such also before).</p></li>
-<li><p>The top-level packages and folders have been renamed to lower-case.
-Particularly, you need to import <span class="docutils literal">dbutils</span> instead of <span class="docutils literal">DBUtils</span> now.</p></li>
 <li><p>The Webware <span class="docutils literal">Examples</span> folder has been removed.</p></li>
-<li><p>The internal naming conventions have been changed to comply with PEP8.</p></li>
+<li><p>Folders, packages and modules have been renamed to lower-case.
+Particularly, you need to import <span class="docutils literal">dbutils</span> instead of <span class="docutils literal">DBUtils</span> now.</p></li>
+<li><p>The internal naming conventions have also been changed to comply with PEP8.</p></li>
 <li><p>The documentation has been adapted to reflect the changes in this version.</p></li>
-<li><p>This changelog file has been created from the former release notes.</p></li>
+<li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </div>
-</div>
 <div class="section" id="id2">
 <h1>1.4</h1>
-<p>DBUtils 1.4 was released on August 26, 2020.</p>
+<p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
-<div class="section" id="improvements">
-<h2>Improvements:</h2>
+<p>Improvements:</p>
 <ul class="simple">
 <li><p>The <span class="docutils literal">SteadyDB</span> and <span class="docutils literal">SteadyPg</span> classes only reconnect after the
 <span class="docutils literal">maxusage</span> limit has been reached when the connection is not currently
 inside a transaction.</p></li>
 </ul>
 </div>
-</div>
 <div class="section" id="id3">
 <h1>1.3</h1>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
-<div class="section" id="id4">
-<h2>Improvements:</h2>
+<p>Improvements:</p>
 <ul class="simple">
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </div>
-</div>
-<div class="section" id="id5">
+<div class="section" id="id4">
 <h1>1.2</h1>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </div>
-<div class="section" id="id6">
+<div class="section" id="id5">
 <h1>1.1.1</h1>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
-<div class="section" id="id7">
-<h2>Improvements:</h2>
+<p>Improvements:</p>
 <ul class="simple">
 <li><p>Reopen <span class="docutils literal">SteadyDB</span> connections when commit or rollback fails
 (suggested by Ben Hoyt).</p></li>
 </ul>
-</div>
-<div class="section" id="bugfixes">
-<h2>Bugfixes:</h2>
+<p>Bugfixes:</p>
 <ul class="simple">
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </div>
-</div>
-<div class="section" id="id8">
+<div class="section" id="id6">
 <h1>1.1</h1>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
-<div class="section" id="id9">
-<h2>Improvements:</h2>
+<p>Improvements:</p>
 <ul class="simple">
 <li><p>The transparent reopening of connections is actually an undesired behavior
 if it happens during database transactions. In these cases, the transaction
@@ -154,22 +87,18 @@ <h2>Improvements:</h2>
 <li><p><span class="docutils literal">PooledDB</span> has got another new parameter <span class="docutils literal">reset</span> for controlling how
 connections are reset before being put back into the pool.</p></li>
 </ul>
-</div>
-<div class="section" id="id10">
-<h2>Bugfixes:</h2>
+<p>Bugfixes:</p>
 <ul class="simple">
 <li><p>Fixed propagation of error messages when the connection was lost.</p></li>
 <li><p>Fixed an issue with the <span class="docutils literal">setoutputsize()</span>  cursor method.</p></li>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </div>
-</div>
-<div class="section" id="id11">
+<div class="section" id="id7">
 <h1>1.0</h1>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
-<div class="section" id="id12">
-<h2>Changes:</h2>
+<p>Changes:</p>
 <ul class="simple">
 <li><p>Added a <span class="docutils literal">failures</span> parameter for configuring the exception classes for
 which the failover mechanisms is applied (as suggested by Matthew Harriger).</p></li>
@@ -188,9 +117,7 @@ <h2>Changes:</h2>
 A new parameter <span class="docutils literal">threadlocal</span> allows you to pass an arbitrary class
 such as <span class="docutils literal">threading.local</span> if you know it works in your environment.</p></li>
 </ul>
-</div>
-<div class="section" id="bugfixes-and-improvements">
-<h2>Bugfixes and Improvements:</h2>
+<p>Bugfixes and improvements:</p>
 <ul class="simple">
 <li><p>In some cases, when instance initialization failed or referenced objects
 were already destroyed, finalizers could throw exceptions or create infinite
@@ -200,8 +127,7 @@ <h2>Bugfixes and Improvements:</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </div>
-</div>
-<div class="section" id="id13">
+<div class="section" id="id8">
 <h1>0.9.4</h1>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -210,11 +136,10 @@ <h1>0.9.4</h1>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </div>
-<div class="section" id="id14">
+<div class="section" id="id9">
 <h1>0.9.3</h1>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
-<div class="section" id="id15">
-<h2>Changes:</h2>
+<p>Changes:</p>
 <ul class="simple">
 <li><p>Support custom creator functions for database connections.
 These can now be used as the first parameter instead of an DB-API module
@@ -226,25 +151,21 @@ <h2>Changes:</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </div>
-</div>
-<div class="section" id="id16">
+<div class="section" id="id10">
 <h1>0.9.2</h1>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
-<div class="section" id="id17">
-<h2>Changes:</h2>
+<p>Changes:</p>
 <ul class="simple">
 <li><p>Renamed <span class="docutils literal">SolidDB</span> to <span class="docutils literal">SteadyDB</span> to avoid confusion with the &quot;solidDB&quot;
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </div>
-</div>
-<div class="section" id="id18">
+<div class="section" id="id11">
 <h1>0.9.1</h1>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
-<div class="section" id="id19">
-<h2>Changes:</h2>
+<p>Changes:</p>
 <ul class="simple">
 <li><p>Added <span class="docutils literal">_closeable</span> attribute and made persistent connections not closeable
 by default. This allows <span class="docutils literal">PersistentDB</span>  to be used in the same way as you
@@ -254,8 +175,7 @@ <h2>Changes:</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </div>
-</div>
-<div class="section" id="id20">
+<div class="section" id="id12">
 <h1>0.8.1 - 2005-09-13</h1>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 25b7d88..fad22c0 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,8 +1,6 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
-.. contents:: Contents
-
 2.0
 ===
 
@@ -11,26 +9,24 @@ DBUtils 2.0 was released on September 26, 2020.
 It is intended to be used with Python versions 2.7 and 3.5 to 3.9.
 
 Changes:
---------
 
 * DBUtils does not act as a Webware plugin anymore, it is now just an ordinary
   Python package (of course it could be used as such also before).
-* The top-level packages and folders have been renamed to lower-case.
-  Particularly, you need to import ``dbutils`` instead of ``DBUtils`` now.
 * The Webware ``Examples`` folder has been removed.
-* The internal naming conventions have been changed to comply with PEP8.
+* Folders, packages and modules have been renamed to lower-case.
+  Particularly, you need to import ``dbutils`` instead of ``DBUtils`` now.
+* The internal naming conventions have also been changed to comply with PEP8.
 * The documentation has been adapted to reflect the changes in this version.
-* This changelog file has been created from the former release notes.
+* This changelog has been compiled from the former release notes.
 
 1.4
 ===
 
-DBUtils 1.4 was released on August 26, 2020.
+DBUtils 1.4 was released on September 26, 2020.
 
 It is intended to be used with Python versions 2.7 and 3.5 to 3.9.
 
 Improvements:
--------------
 
 * The ``SteadyDB`` and ``SteadyPg`` classes only reconnect after the
   ``maxusage`` limit has been reached when the connection is not currently
@@ -44,7 +40,6 @@ DBUtils 1.3 was released on March 3, 2018.
 It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.
 
 Improvements:
--------------
 
 * This version now supports context handlers for connections and cursors.
 
@@ -63,13 +58,11 @@ DBUtils 1.1.1 was released on February 4, 2017.
 It is intended to be used with Python versions 2.3 to 2.7.
 
 Improvements:
--------------
 
 * Reopen ``SteadyDB`` connections when commit or rollback fails
   (suggested by Ben Hoyt).
 
 Bugfixes:
----------
 
 * Fixed a problem when running under Jython (reported by Vitaly Kruglikov).
 
@@ -79,7 +72,6 @@ Bugfixes:
 DBUtils 1.1 was released on August 14, 2011.
 
 Improvements:
--------------
 
 * The transparent reopening of connections is actually an undesired behavior
   if it happens during database transactions. In these cases, the transaction
@@ -99,7 +91,6 @@ Improvements:
   connections are reset before being put back into the pool.
 
 Bugfixes:
----------
 
 * Fixed propagation of error messages when the connection was lost.
 * Fixed an issue with the ``setoutputsize()``  cursor method.
@@ -114,7 +105,6 @@ DBUtils 1.0 was released on November 29, 2008.
 It is intended to be used with Python versions 2.2 to 2.6.
 
 Changes:
---------
 
 * Added a ``failures`` parameter for configuring the exception classes for
   which the failover mechanisms is applied (as suggested by Matthew Harriger).
@@ -133,8 +123,7 @@ Changes:
   A new parameter ``threadlocal`` allows you to pass an arbitrary class
   such as ``threading.local`` if you know it works in your environment.
 
-Bugfixes and Improvements:
---------------------------
+Bugfixes and improvements:
 
 * In some cases, when instance initialization failed or referenced objects
   were already destroyed, finalizers could throw exceptions or create infinite
@@ -161,7 +150,6 @@ for database connections instead of DB-API 2 modules.
 DBUtils 0.9.3 was released on May 21, 2007.
 
 Changes:
---------
 
 * Support custom creator functions for database connections.
   These can now be used as the first parameter instead of an DB-API module
@@ -182,7 +170,6 @@ DBUtils 0.9.2 was released on September 22, 2006.
 It is intended to be used with Python versions 2.2 to 2.5.
 
 Changes:
---------
 
 * Renamed ``SolidDB`` to ``SteadyDB`` to avoid confusion with the "solidDB"
   storage engine. Accordingly, renamed ``SolidPg`` to ``SteadyPg``.
@@ -195,7 +182,7 @@ DBUtils 0.9.1 was released on May 8, 2006.
 It is intended to be used with Python versions 2.2 to 2.4.
 
 Changes:
---------
+
 * Added ``_closeable`` attribute and made persistent connections not closeable
   by default. This allows ``PersistentDB``  to be used in the same way as you
   would use ``PooledDB``.
diff --git a/setup.py b/setup.py
index 5f54dfe..82f6bba 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,10 @@
 """Setup Script for DBUtils"""
 
 import warnings
-from distutils.core import setup
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
 from sys import version_info
 
 py_version = version_info[:2]
@@ -41,8 +44,13 @@
     author='Christoph Zwerschke',
     author_email='cito@online.de',
     url='https://webwareforpython.github.io/DBUtils/',
+    download_url="https://pypi.org/project/DBUtils/",
+    project_urls={
+        "Documentation": "https://webwareforpython.github.io/DBUtils/main.html",
+        "Changelog": "https://webwareforpython.github.io/DBUtils/changelog.html",
+        "Issue Tracker": "https://github.com/WebwareForPython/DBUtils/issues",
+        "Source Code": "https://github.com/WebwareForPython/DBUtils"},
     platforms=['any'],
     license='MIT License',
-    packages=['dbutils'],
-    zip_safe=False,
+    packages=['dbutils']
 )

From 03181b0a37d1e0e8ca269b99acd16930c157c42b Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 23:09:31 +0200
Subject: [PATCH 13/84] Remove blank lines in docstrings

---
 dbutils/persistent_db.py       |  4 ----
 dbutils/persistent_pg.py       |  3 ---
 dbutils/pooled_db.py           |  7 -------
 dbutils/pooled_pg.py           |  4 ----
 dbutils/simple_pooled_db.py    |  6 ------
 dbutils/simple_pooled_pg.py    |  4 ----
 dbutils/steady_db.py           | 10 ----------
 dbutils/steady_pg.py           |  9 ---------
 tests/test_persistent_db.py    |  1 -
 tests/test_persistent_pg.py    |  1 -
 tests/test_pooled_db.py        |  1 -
 tests/test_pooled_pg.py        |  1 -
 tests/test_simple_pooled_db.py |  1 -
 tests/test_simple_pooled_pg.py |  1 -
 tests/test_steady_db.py        |  1 -
 tests/test_steady_pg.py        |  1 -
 16 files changed, 55 deletions(-)

diff --git a/dbutils/persistent_db.py b/dbutils/persistent_db.py
index ac26c11..cc93720 100644
--- a/dbutils/persistent_db.py
+++ b/dbutils/persistent_db.py
@@ -108,7 +108,6 @@
   by Geoffrey Talvola in July 2005
 
 Licensed under the MIT license.
-
 """
 
 from . import __version__
@@ -137,7 +136,6 @@ class PersistentDB:
 
     After you have created the connection pool, you can use
     connection() to get thread-affine, steady DB-API 2 connections.
-
     """
 
     version = __version__
@@ -169,7 +167,6 @@ def __init__(
             (threading.local is faster, but cannot be used in all cases)
         args, kwargs: the parameters that shall be passed to the creator
             function or the connection constructor of the DB-API 2 module
-
         """
         try:
             threadsafety = creator.threadsafety
@@ -205,7 +202,6 @@ def connection(self, shareable=False):
         The shareable parameter exists only for compatibility with the
         PooledDB connection method.  In reality, persistent connections
         are of course never shared with other threads.
-
         """
         try:
             con = self.thread.connection
diff --git a/dbutils/persistent_pg.py b/dbutils/persistent_pg.py
index b14960e..eb53e22 100644
--- a/dbutils/persistent_pg.py
+++ b/dbutils/persistent_pg.py
@@ -99,7 +99,6 @@
   by Geoffrey Talvola in July 2005
 
 Licensed under the MIT license.
-
 """
 
 from . import __version__
@@ -120,7 +119,6 @@ class PersistentPg:
 
     After you have created the connection pool, you can use
     connection() to get thread-affine, steady PostgreSQL connections.
-
     """
 
     version = __version__
@@ -143,7 +141,6 @@ def __init__(
             (threading.local is faster, but cannot be used in all cases)
         args, kwargs: the parameters that shall be used to establish
             the PostgreSQL connections using class PyGreSQL pg.DB()
-
         """
         self._maxusage = maxusage
         self._setsession = setsession
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 0b13c96..04b4f20 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -136,7 +136,6 @@
   by Dan Green in December 2000
 
 Licensed under the MIT license.
-
 """
 
 from threading import Condition
@@ -166,7 +165,6 @@ class PooledDB:
 
     After you have created the connection pool, you can use
     connection() to get pooled, steady DB-API 2 connections.
-
     """
 
     version = __version__
@@ -212,7 +210,6 @@ def __init__(
             7 = always, and all other bit combinations of these values)
         args, kwargs: the parameters that shall be passed to the creator
             function or the connection constructor of the DB-API 2 module
-
         """
         try:
             threadsafety = creator.threadsafety
@@ -278,7 +275,6 @@ def connection(self, shareable=True):
 
         If shareable is set and the underlying DB-API 2 allows it,
         then the connection may be shared with other threads.
-
         """
         if shareable and self._maxshared:
             self._lock.acquire()
@@ -413,7 +409,6 @@ def __init__(self, pool, con):
 
         pool: the corresponding PooledDB instance
         con: the underlying SteadyDB connection
-
         """
         # basic initialization to make finalizer work
         self._con = None
@@ -453,7 +448,6 @@ def __init__(self, con):
         """Create a shared connection.
 
         con: the underlying SteadyDB connection
-
         """
         self.con = con
         self.shared = 1
@@ -500,7 +494,6 @@ def __init__(self, pool, shared_con):
 
         pool: the corresponding PooledDB instance
         con: the underlying SharedDBConnection
-
         """
         # basic initialization to make finalizer work
         self._con = None
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index 2f8b937..a14cf6e 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -105,7 +105,6 @@
   by Dan Green in December 2000
 
 Licensed under the MIT license.
-
 """
 
 try:
@@ -134,7 +133,6 @@ class PooledPg:
 
     After you have created the connection pool, you can use
     connection() to get pooled, steady PostgreSQL connections.
-
     """
 
     version = __version__
@@ -166,7 +164,6 @@ def __init__(
             1 to always issue a rollback, 2 for a complete reset)
         args, kwargs: the parameters that shall be used to establish
             the PostgreSQL connections using class PyGreSQL pg.DB()
-
         """
         self._args, self._kwargs = args, kwargs
         self._maxusage = maxusage
@@ -261,7 +258,6 @@ def __init__(self, pool, con):
 
         pool: the corresponding PooledPg instance
         con: the underlying SteadyPg connection
-
         """
         self._pool = pool
         self._con = con
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index 3e1b406..cfd2c8b 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -70,7 +70,6 @@
   by Christoph Zwerschke in September 2005
 
 Licensed under the MIT license.
-
 """
 
 from . import __version__
@@ -89,7 +88,6 @@ class PooledDBConnection:
 
     You don't normally deal with this class directly,
     but use PooledDB to get new connections.
-
     """
 
     def __init__(self, pool, con):
@@ -117,7 +115,6 @@ class PooledDB:
 
     After you have created the connection pool,
     you can get connections using getConnection().
-
     """
 
     version = __version__
@@ -129,7 +126,6 @@ def __init__(self, dbapi, maxconnections, *args, **kwargs):
         maxconnections: the number of connections cached in the pool
         args, kwargs: the parameters that shall be used to establish
             the database connections using connect()
-
         """
         try:
             threadsafety = dbapi.threadsafety
@@ -190,7 +186,6 @@ def _unthreadsafe_return_connection(self, con):
         back into the queue after they have been used.
         This is done automatically when the connection is closed
         and should never be called explicitly outside of this module.
-
         """
         self._unthreadsafe_add_connection(con)
 
@@ -222,6 +217,5 @@ def _threadsafe_return_connection(self, con):
 
         In this case, the connections always stay in the pool,
         so there is no need to do anything here.
-
         """
         pass
diff --git a/dbutils/simple_pooled_pg.py b/dbutils/simple_pooled_pg.py
index 4927a35..844fdd0 100644
--- a/dbutils/simple_pooled_pg.py
+++ b/dbutils/simple_pooled_pg.py
@@ -65,7 +65,6 @@
   by Dan Green in December 2000
 
 Licensed under the MIT license.
-
 """
 
 from pg import DB as PgConnection
@@ -78,7 +77,6 @@ class PooledPgConnection:
 
     You don't normally deal with this class directly,
     but use PooledPg to get new connections.
-
     """
 
     def __init__(self, pool, con):
@@ -106,7 +104,6 @@ class PooledPg:
 
     After you have created the connection pool,
     you can get connections using getConnection().
-
     """
 
     version = __version__
@@ -117,7 +114,6 @@ def __init__(self, maxconnections, *args, **kwargs):
         maxconnections: the number of connections cached in the pool
         args, kwargs: the parameters that shall be used to establish
             the PostgreSQL connections using pg.connect()
-
         """
         # Since there is no connection level safety, we
         # build the pool using the synchronized queue class
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 13918d6..6af877c 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -87,7 +87,6 @@
   suggested by Ezio Vernacotola in December 2006
 
 Licensed under the MIT license.
-
 """
 
 import sys
@@ -132,7 +131,6 @@ def connect(
         be silently ignored, but by default the connection can be closed
     args, kwargs: the parameters that shall be passed to the creator
         function or the connection constructor of the DB-API 2 module
-
     """
     return SteadyDBConnection(
         creator, maxusage, setsession,
@@ -199,7 +197,6 @@ def __exit__(self, *exc):
         """Exit the runtime context for the connection object.
 
         This does not close the connection, but it ends a transaction.
-
         """
         if exc[0] is None and exc[1] is None and exc[2] is None:
             self.commit()
@@ -317,7 +314,6 @@ def _close(self):
 
         You can always close a tough connection with this method
         and it will not complain if you close it more than once.
-
         """
         if not self._closed:
             try:
@@ -331,7 +327,6 @@ def _reset(self, force=False):
         """Reset a tough connection.
 
         Rollback if forced or the connection was in a transaction.
-
         """
         if not self._closed and (force or self._transaction):
             try:
@@ -345,7 +340,6 @@ def _ping_check(self, ping=1, reconnect=True):
         If the the underlying connection is not active and the ping
         parameter is set accordingly, the connection will be recreated
         unless the connection is currently inside a transaction.
-
         """
         if ping & self._ping:
             try:  # if possible, ping the connection
@@ -402,7 +396,6 @@ def close(self):
         You can disallow closing connections by setting
         the closeable parameter to something false.  In this case,
         closing tough connections will be silently ignored.
-
         """
         if self._closeable:
             self._close()
@@ -417,7 +410,6 @@ def begin(self, *args, **kwargs):
 
         If the underlying driver supports this method, it will be called
         with the given parameters (e.g. for distributed transactions).
-
         """
         self._transaction = True
         try:
@@ -461,7 +453,6 @@ def cancel(self):
         """Cancel a long-running transaction.
 
         If the underlying driver supports this method, it will be called.
-
         """
         self._transaction = False
         try:
@@ -580,7 +571,6 @@ def close(self):
         """Close the tough cursor.
 
         It will not complain if you close it more than once.
-
         """
         if not self._closed:
             try:
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index a75b025..f7e4d4c 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -67,7 +67,6 @@
   by Christoph Zwerschke in September 2005
 
 Licensed under the MIT license.
-
 """
 
 from pg import DB as PgConnection
@@ -98,7 +97,6 @@ class SteadyPgConnection:
     If you want the connection to be persistent in a threaded environment,
     then you should not deal with this class directly, but use either the
     PooledPg module or the PersistentPg module to get the connections.
-
     """
 
     version = __version__
@@ -117,7 +115,6 @@ def __init__(
             be silently ignored, but by default the connection can be closed
         args, kwargs: the parameters that shall be used to establish
             the PostgreSQL connections with PyGreSQL using pg.DB()
-
         """
         # basic initialization to make finalizer work
         self._con = None
@@ -159,7 +156,6 @@ def _close(self):
 
         You can always close a tough connection with this method
         and it will not complain if you close it more than once.
-
         """
         if not self._closed:
             try:
@@ -178,7 +174,6 @@ def close(self):
         You can disallow closing connections by setting
         the closeable parameter to something false.  In this case,
         closing tough connections will be silently ignored.
-
         """
         if self._closeable:
             self._close()
@@ -189,7 +184,6 @@ def reopen(self):
         """Reopen the tough connection.
 
         It will not complain if the connection cannot be reopened.
-
         """
         try:
             self._con.reopen()
@@ -211,7 +205,6 @@ def reset(self):
 
         If a reset is not possible, tries to reopen the connection.
         It will not complain if the connection is already closed.
-
         """
         try:
             self._con.reset()
@@ -286,7 +279,6 @@ def _get_tough_method(self, method):
         The tough version checks whether the connection is bad (lost)
         and automatically and transparently tries to reset the connection
         if this is the case (for instance, the database has been restarted).
-
         """
         def tough_method(*args, **kwargs):
             transaction = self._transaction
@@ -318,7 +310,6 @@ def __getattr__(self, name):
         """Inherit the members of the standard connection class.
 
         Some methods are made "tougher" than in the standard version.
-
         """
         if self._con:
             attr = getattr(self._con, name)
diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index bf1f8c6..7cccb30 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -8,7 +8,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index bed067b..5053316 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -8,7 +8,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 860b7f5..bd5ccd0 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -8,7 +8,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index 0379693..1265888 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -8,7 +8,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest
diff --git a/tests/test_simple_pooled_db.py b/tests/test_simple_pooled_db.py
index 3c6ede1..564fda3 100644
--- a/tests/test_simple_pooled_db.py
+++ b/tests/test_simple_pooled_db.py
@@ -9,7 +9,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest
diff --git a/tests/test_simple_pooled_pg.py b/tests/test_simple_pooled_pg.py
index 251fd72..7065d58 100644
--- a/tests/test_simple_pooled_pg.py
+++ b/tests/test_simple_pooled_pg.py
@@ -8,7 +8,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index 79342a6..08aacfe 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -7,7 +7,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest
diff --git a/tests/test_steady_pg.py b/tests/test_steady_pg.py
index df88b77..f798f43 100644
--- a/tests/test_steady_pg.py
+++ b/tests/test_steady_pg.py
@@ -9,7 +9,6 @@
 Copyright and credit info:
 
 * This test was contributed by Christoph Zwerschke
-
 """
 
 import unittest

From 8d61b6fae9effb6d9d93616c23bf545c14be30ed Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 23:12:42 +0200
Subject: [PATCH 14/84] Fix PEP8 issues in setup.py

---
 setup.py | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/setup.py b/setup.py
index 82f6bba..861ed92 100755
--- a/setup.py
+++ b/setup.py
@@ -46,10 +46,14 @@
     url='https://webwareforpython.github.io/DBUtils/',
     download_url="https://pypi.org/project/DBUtils/",
     project_urls={
-        "Documentation": "https://webwareforpython.github.io/DBUtils/main.html",
-        "Changelog": "https://webwareforpython.github.io/DBUtils/changelog.html",
-        "Issue Tracker": "https://github.com/WebwareForPython/DBUtils/issues",
-        "Source Code": "https://github.com/WebwareForPython/DBUtils"},
+        'Documentation':
+            'https://webwareforpython.github.io/DBUtils/main.html',
+        'Changelog':
+            'https://webwareforpython.github.io/DBUtils/changelog.html',
+        'Issue Tracker':
+            'https://github.com/WebwareForPython/DBUtils/issues',
+        'Source Code':
+            'https://github.com/WebwareForPython/DBUtils'},
     platforms=['any'],
     license='MIT License',
     packages=['dbutils']

From c5f7db2ac3829877e0cd1452259a4f7b76bac558 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 26 Sep 2020 23:38:30 +0200
Subject: [PATCH 15/84] Install tox in Travis configuration

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index e705d3c..d67b1e0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,6 +27,6 @@ cache:
     - "$HOME/.cache/pip"
     - "$TRAVIS_BUILD_DIR/.tox"
 install:
-  - python setup.py install
+  - pip install tox
 script:
   - tox -e $TOXENV

From 4a60187187e1187f9c554bcb991b3bbd5f159594 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 27 Sep 2020 00:00:58 +0200
Subject: [PATCH 16/84] Forgot to change CSS path to lower-case

---
 docs/changelog.html | 2 +-
 docs/main.de.html   | 2 +-
 docs/main.html      | 2 +-
 docs/make.py        | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/changelog.html b/docs/changelog.html
index d473594..f85ecbb 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8"/>
 <meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
 <title>Changelog for DBUtils</title>
-<link rel="stylesheet" href="Doc.css" type="text/css" />
+<link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
 <body>
 <div class="document" id="changelog-for-dbutils">
diff --git a/docs/main.de.html b/docs/main.de.html
index 3d63022..0720d75 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8"/>
 <meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
 <title>Benutzeranleitung für DBUtils</title>
-<link rel="stylesheet" href="Doc.css" type="text/css" />
+<link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
 <body>
 <div class="document" id="benutzeranleitung-fur-dbutils">
diff --git a/docs/main.html b/docs/main.html
index 22a6ea9..e6bc741 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8"/>
 <meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
 <title>DBUtils User's Guide</title>
-<link rel="stylesheet" href="Doc.css" type="text/css" />
+<link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
 <body>
 <div class="document" id="dbutils-user-s-guide">
diff --git a/docs/make.py b/docs/make.py
index d81cfab..c2a0494 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -26,7 +26,7 @@
                 writer_name='html5', source=source, destination=destination,
                 enable_exit_status=True,
                 settings_overrides=dict(
-                    stylesheet_path='Doc.css',
+                    stylesheet_path='doc.css',
                     embed_stylesheet=False,
                     toc_backlinks=False,
                     language_code=lang,

From 19a8a33f71bb4295c8b5de364acbb494bd59fa8c Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 17 Oct 2020 10:32:16 +0200
Subject: [PATCH 17/84] Add note about breaking changes to README

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index b35abf9..9debced 100644
--- a/README.md
+++ b/README.md
@@ -9,4 +9,6 @@ and the classic PyGreSQL interface.
 
 The current version 2.0 of DBUtils supports Python versions 2.7 and 3.5 to 3.9.
 
+**Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there are some breaking changes in version 2.0.**
+
 The DBUtils home page can be found at https://webwareforpython.github.io/DBUtils/

From a703b00be8315767b4d2dc1bb8e751409583704d Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 3 Apr 2021 22:02:21 +0200
Subject: [PATCH 18/84] Avoid "name Exception is not defined" when exiting

At interpreter shutdown, the module's global variables are set to None
before the module itself is released. __del__ methods may be called in
those precarious circumstances, and should not rely on any global state.
---
 dbutils/pooled_db.py | 6 +++---
 dbutils/pooled_pg.py | 4 ++--
 dbutils/steady_db.py | 4 ++--
 dbutils/steady_pg.py | 2 +-
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 04b4f20..2de9b55 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -389,7 +389,7 @@ def __del__(self):
         """Delete the pool."""
         try:
             self.close()
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass
 
     def _wait_lock(self):
@@ -437,7 +437,7 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass
 
 
@@ -524,5 +524,5 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index a14cf6e..0e0a3ae 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -244,7 +244,7 @@ def __del__(self):
         """Delete the pool."""
         try:
             self.close()
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass
 
 
@@ -291,5 +291,5 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 6af877c..e5a6b03 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -512,7 +512,7 @@ def __del__(self):
         """Delete the steady connection."""
         try:
             self._close()  # make sure the connection is closed
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass
 
 
@@ -697,5 +697,5 @@ def __del__(self):
         """Delete the steady cursor."""
         try:
             self.close()  # make sure the cursor is closed
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index f7e4d4c..3a500ee 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -324,5 +324,5 @@ def __del__(self):
         """Delete the steady connection."""
         try:
             self._close()  # make sure the connection is closed
-        except Exception:
+        except:  # builtin Exceptions might not exist any more
             pass

From 4085abb0bfc97c73306b3c4bc8c4e12c5b3ada83 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 3 Apr 2021 22:17:53 +0200
Subject: [PATCH 19/84] Make Flake8 happy again

---
 .flake8     | 4 ++++
 MANIFEST.in | 1 +
 2 files changed, 5 insertions(+)
 create mode 100644 .flake8

diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..578558b
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+ignore = E722,W503
+exclude = .git,.pytest_cache,.tox,.venv,.idea,__pycache__,build,dist,docs
+max-line-length = 79
diff --git a/MANIFEST.in b/MANIFEST.in
index 2d8e122..82221db 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,6 +4,7 @@ include LICENSE
 include README.md
 
 include .bumpversion.cfg
+include .flake8
 include tox.ini
 
 recursive-include tests *.py

From a1c72a3d0b1e5fc0508a7ea8db5419f7972e7075 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Thu, 8 Apr 2021 17:27:02 +0200
Subject: [PATCH 20/84] GitHub Action to test with tox (#31)

---
 .github/workflows/test_with_tox.yml | 20 ++++++++++++++++++
 .travis.yml                         | 32 -----------------------------
 dbutils/simple_pooled_db.py         |  2 +-
 tox.ini                             | 14 +++++++++----
 4 files changed, 31 insertions(+), 37 deletions(-)
 create mode 100644 .github/workflows/test_with_tox.yml
 delete mode 100644 .travis.yml

diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
new file mode 100644
index 0000000..e19f860
--- /dev/null
+++ b/.github/workflows/test_with_tox.yml
@@ -0,0 +1,20 @@
+name: Python package
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python: [2.7, 3.6, 3.7, 3.8, 3.9]
+    steps:
+      - uses: actions/checkout@v2
+      - name: Setup Python ${{ matrix.python }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python }}
+      - run: pip install tox
+      - run: tox -e py
+      - if: matrix.python == 3.9
+        run: TOXENV=codespell,flake8,manifest,docs tox
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index d67b1e0..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-# Travis CI configuration
-# see https://docs.travis-ci.com/user/languages/python
-
-language: python
-matrix:
-  include:
-    - name: Code quality tests
-      env: TOXENV=flake8,manifest,docs
-      python: 3.8
-    - name: Unit tests with Python 3.8
-      env: TOXENV=py38
-      python: 3.8
-    - name: Unit tests with Python 3.7
-      env: TOXENV=py37
-      python: 3.7
-    - name: Unit tests with Python 3.6
-      env: TOXENV=py36
-      python: 3.6
-    - name: Unit tests with Python 3.5
-      env: TOXENV=py35
-      python: 3.5
-    - name: Unit tests with Python 2.7
-      env: TOXENV=py27
-      python: 2.7
-cache:
-  directories:
-    - "$HOME/.cache/pip"
-    - "$TRAVIS_BUILD_DIR/.tox"
-install:
-  - pip install tox
-script:
-  - tox -e $TOXENV
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index cfd2c8b..5e31b29 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -47,7 +47,7 @@
 * Connections should have some sort of maximum usage limit
 after which they should be automatically closed and reopened.
 * Prefer or enforce thread-affinity for the connections,
-allowing for both sharable and non-sharable connections.
+allowing for both shareable and non-shareable connections.
 
 Please note that these and other ideas have been already
 implemented in in PooledDB, a more sophisticated version
diff --git a/tox.ini b/tox.ini
index 2de6636..9700b46 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,20 +8,26 @@ deps = pytest
 commands =
     pytest {posargs}
 
+[testenv:codespell]
+basepython = python3.9
+deps = codespell
+commands =
+    codespell --ignore-words-list="dont'" --quiet-level=2 --skip="./.*,./docs/main.de.*"
+
 [testenv:flake8]
-basepython = python3.8
+basepython = python3.9
 deps = flake8
 commands =
-    flake8 dbutils tests docs setup.py
+    flake8 .
 
 [testenv:manifest]
-basepython = python3.8
+basepython = python3.9
 deps = check-manifest
 commands =
     check-manifest -v
 
 [testenv:docs]
-basepython = python3.8
+basepython = python3.9
 deps = docutils
 changedir = docs
 commands =

From afcecd47592435bc36ef6c3b91ad06d852ba4488 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Apr 2021 17:43:07 +0200
Subject: [PATCH 21/84] Change name of GitHub workflow

---
 .github/workflows/test_with_tox.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index e19f860..509af11 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -1,4 +1,4 @@
-name: Python package
+name: Test DBUtils using tox
 
 on: [push, pull_request]
 

From 6d5bd16140869032b4bcf45e8565cf2d0b3b7c4f Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Apr 2021 18:12:18 +0200
Subject: [PATCH 22/84] Use config file for codespell

This allows running it without tox.
---
 .codespellrc                        | 3 +++
 .github/workflows/test_with_tox.yml | 2 +-
 tests/test_steady_db.py             | 6 +++---
 tox.ini                             | 6 +++---
 4 files changed, 10 insertions(+), 7 deletions(-)
 create mode 100644 .codespellrc

diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 0000000..15761d2
--- /dev/null
+++ b/.codespellrc
@@ -0,0 +1,3 @@
+[codespell]
+skip = .tox,.venv,*.de.html,*.de.rst
+quiet-level = 2
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index 509af11..fe6e356 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -17,4 +17,4 @@ jobs:
       - run: pip install tox
       - run: tox -e py
       - if: matrix.python == 3.9
-        run: TOXENV=codespell,flake8,manifest,docs tox
+        run: TOXENV=flake8,manifest,docs,spell tox
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index 08aacfe..e32a711 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -237,12 +237,12 @@ def test_connection(self):
         self.assertTrue(cursor.valid)
         self.assertEqual(db._usage, 1)
         self.assertEqual(db._con.num_uses, 1)
-        cursor.execute('set doit')
+        cursor.execute('set this')
         db.commit()
-        cursor.execute('set dont')
+        cursor.execute('set that')
         db.rollback()
         self.assertEqual(
-            db._con.session, ['doit', 'commit', 'dont', 'rollback'])
+            db._con.session, ['this', 'commit', 'that', 'rollback'])
 
     def test_connection_context_handler(self):
         db = SteadyDBconnect(
diff --git a/tox.ini b/tox.ini
index 9700b46..a8b505b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py{27,35,36,37,38,39}, flake8, manifest, docs
+envlist = py{27,35,36,37,38,39}, flake8, manifest, docs, spell
 
 [testenv]
 setenv =
@@ -8,11 +8,11 @@ deps = pytest
 commands =
     pytest {posargs}
 
-[testenv:codespell]
+[testenv:spell]
 basepython = python3.9
 deps = codespell
 commands =
-    codespell --ignore-words-list="dont'" --quiet-level=2 --skip="./.*,./docs/main.de.*"
+    codespell .
 
 [testenv:flake8]
 basepython = python3.9

From ace813f963329dfc0e43b57a611e031aafe8082e Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Apr 2021 18:12:58 +0200
Subject: [PATCH 23/84] Recreate html with latest docutils

---
 docs/changelog.html |  79 +++++++++++++-------------
 docs/main.de.html   | 133 ++++++++++++++++++++++----------------------
 docs/main.html      | 133 ++++++++++++++++++++++----------------------
 3 files changed, 174 insertions(+), 171 deletions(-)

diff --git a/docs/changelog.html b/docs/changelog.html
index f85ecbb..7853af5 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -2,16 +2,17 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8"/>
-<meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta name="generator" content="Docutils 0.17: http://docutils.sourceforge.net/" />
 <title>Changelog for DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
 <body>
-<div class="document" id="changelog-for-dbutils">
+<main id="changelog-for-dbutils">
 <h1 class="title">Changelog for DBUtils</h1>
 
-<div class="section" id="id1">
-<h1>2.0</h1>
+<section id="id1">
+<h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
 <p>Changes:</p>
@@ -25,9 +26,9 @@ <h1>2.0</h1>
 <li><p>The documentation has been adapted to reflect the changes in this version.</p></li>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
-</div>
-<div class="section" id="id2">
-<h1>1.4</h1>
+</section>
+<section id="id2">
+<h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
 <p>Improvements:</p>
@@ -36,23 +37,23 @@ <h1>1.4</h1>
 <span class="docutils literal">maxusage</span> limit has been reached when the connection is not currently
 inside a transaction.</p></li>
 </ul>
-</div>
-<div class="section" id="id3">
-<h1>1.3</h1>
+</section>
+<section id="id3">
+<h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
 <p>Improvements:</p>
 <ul class="simple">
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
-</div>
-<div class="section" id="id4">
-<h1>1.2</h1>
+</section>
+<section id="id4">
+<h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
-</div>
-<div class="section" id="id5">
-<h1>1.1.1</h1>
+</section>
+<section id="id5">
+<h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
 <p>Improvements:</p>
@@ -64,9 +65,9 @@ <h1>1.1.1</h1>
 <ul class="simple">
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
-</div>
-<div class="section" id="id6">
-<h1>1.1</h1>
+</section>
+<section id="id6">
+<h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
 <ul class="simple">
@@ -93,9 +94,9 @@ <h1>1.1</h1>
 <li><p>Fixed an issue with the <span class="docutils literal">setoutputsize()</span>  cursor method.</p></li>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
-</div>
-<div class="section" id="id7">
-<h1>1.0</h1>
+</section>
+<section id="id7">
+<h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
 <p>Changes:</p>
@@ -126,18 +127,18 @@ <h1>1.0</h1>
 connection creator function is specified. This had not worked before with
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
-</div>
-<div class="section" id="id8">
-<h1>0.9.4</h1>
+</section>
+<section id="id8">
+<h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
 with a German User's Guide.</p>
 <p>Again, please note that the <span class="docutils literal">dbapi</span> parameter has been renamed to <span class="docutils literal">creator</span>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
-</div>
-<div class="section" id="id9">
-<h1>0.9.3</h1>
+</section>
+<section id="id9">
+<h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
 <ul class="simple">
@@ -150,9 +151,9 @@ <h1>0.9.3</h1>
 <li><p>Some fixes in the documentation.
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
-</div>
-<div class="section" id="id10">
-<h1>0.9.2</h1>
+</section>
+<section id="id10">
+<h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
 <p>Changes:</p>
@@ -160,9 +161,9 @@ <h1>0.9.2</h1>
 <li><p>Renamed <span class="docutils literal">SolidDB</span> to <span class="docutils literal">SteadyDB</span> to avoid confusion with the &quot;solidDB&quot;
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
-</div>
-<div class="section" id="id11">
-<h1>0.9.1</h1>
+</section>
+<section id="id11">
+<h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
 <p>Changes:</p>
@@ -174,13 +175,13 @@ <h1>0.9.1</h1>
 to specify cursor classes. (Suggested by Michael Palmer.)</p></li>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
-</div>
-<div class="section" id="id12">
-<h1>0.8.1 - 2005-09-13</h1>
+</section>
+<section id="id12">
+<h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
 <p>This is the first public release of DBUtils.</p>
-</div>
-</div>
+</section>
+</main>
 </body>
 </html>
diff --git a/docs/main.de.html b/docs/main.de.html
index 0720d75..1e6ae7e 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -2,12 +2,13 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
 <head>
 <meta charset="utf-8"/>
-<meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta name="generator" content="Docutils 0.17: http://docutils.sourceforge.net/" />
 <title>Benutzeranleitung für DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
-<body>
-<div class="document" id="benutzeranleitung-fur-dbutils">
+<body class="with-toc">
+<main id="benutzeranleitung-fur-dbutils">
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
@@ -51,8 +52,8 @@ <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <li><p><a class="reference internal" href="#copyright-und-lizenz" id="id25">Copyright und Lizenz</a></p></li>
 </ul>
 </div>
-<div class="section" id="zusammenfassung">
-<h1>Zusammenfassung</h1>
+<section id="zusammenfassung">
+<h2>Zusammenfassung</h2>
 <p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a> ist eine Sammlung von Python-Modulen, mit deren Hilfe man in <a class="reference external" href="https://www.python.org">Python</a>
 geschriebene Multithread-Anwendungen auf sichere und effiziente Weise an
 Datenbanken anbinden kann.</p>
@@ -60,9 +61,9 @@ <h1>Zusammenfassung</h1>
 und <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> als <a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a>-Datenbankadapter entwickelt,
 kann aber inzwischen für beliebige Python-Anwendungen und beliebige
 auf <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> beruhende Python-Datenbankadapter verwendet werden.</p>
-</div>
-<div class="section" id="module">
-<h1>Module</h1>
+</section>
+<section id="module">
+<h2>Module</h2>
 <p>DBUtils ist als Python-Package realisiert worden, das aus zwei verschiedenen
 Gruppen von Modulen besteht: Einer Gruppe zur Verwendung mit beliebigen
 DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen
@@ -121,44 +122,44 @@ <h1>Module</h1>
 <p>Die Abhängigkeiten der Module in der Variante für den klassischen
 PyGreSQL-Adapter sehen ähnlich aus:</p>
 <img alt="depdependencies_pg.png" src="depdependencies_pg.png" />
-</div>
-<div class="section" id="download">
-<h1>Download</h1>
+</section>
+<section id="download">
+<h2>Download</h2>
 <p>Die aktuelle Version von DBUtils kann vom Python Package Index
 heruntergeladen werden:</p>
 <pre class="literal-block">https://pypi.python.org/pypi/DBUtils</pre>
 <p>Das Source-Code-Repository befindet sich hier auf GitHub:</p>
 <pre class="literal-block">https://github.com/WebwareForPython/DBUtils</pre>
-</div>
-<div class="section" id="installation">
-<h1>Installation</h1>
-<div class="section" id="id1">
+</section>
+<section id="installation">
 <h2>Installation</h2>
+<section id="id1">
+<h3>Installation</h3>
 <p>Das Paket kann auf die übliche Weise installiert werden:</p>
 <pre class="literal-block">python setup.py install</pre>
 <p>Noch einfacher ist, das Paket in einem Schritt mit <a class="reference external" href="https://pip.pypa.io/">pip</a> automatisch
 herunterzuladen und zu installieren:</p>
 <pre class="literal-block">pip install DBUtils</pre>
-</div>
-</div>
-<div class="section" id="anforderungen">
-<h1>Anforderungen</h1>
+</section>
+</section>
+<section id="anforderungen">
+<h2>Anforderungen</h2>
 <p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 2.7 und 3.5 bis 3.9.</p>
 <p>Die Module in der Variante für klassisches PyGreSQL benötigen <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a>
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
 für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten,
 das auf <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> basiert.</p>
-</div>
-<div class="section" id="funktionalitat">
-<h1>Funktionalität</h1>
+</section>
+<section id="funktionalitat">
+<h2>Funktionalität</h2>
 <p>Dieser Abschnitt verwendet nur die Bezeichnungen der DB-API-2-Variante, aber
 Entsprechendes gilt auch für die PyGreSQL-Variante.</p>
 <p>DBUtils installiert sich als Paket <span class="docutils literal">dbutils</span>, das alle hier beschriebenen
 Module enthält. Jedes dieser Modul enthält im Wesentlichen eine Klasse, die
 einen analogen Namen trägt und die jeweilige Funktionalität bereitstellt.
 So enthält z.B. das Modul <span class="docutils literal">dbutils.pooled_db</span> die Klasse <span class="docutils literal">PooledDB</span>.</p>
-<div class="section" id="simplepooleddb-simple-pooled-db">
-<h2>SimplePooledDB (simple_pooled_db)</h2>
+<section id="simplepooleddb-simple-pooled-db">
+<h3>SimplePooledDB (simple_pooled_db)</h3>
 <p>Die Klasse <span class="docutils literal">SimplePooledDB</span> in <span class="docutils literal">dbutils.simple_pooled_db</span> ist eine sehr
 elementare Referenz-Implementierung eines Pools von Datenbankverbindungen.
 Hiermit ist ein Vorratsspeicher an Datenbankverbindungen gemeint, aus dem sich
@@ -168,9 +169,9 @@ <h2>SimplePooledDB (simple_pooled_db)</h2>
 Wesentlichen identisch mit dem zu Webware for Python gehörenden Modul
 <span class="docutils literal">MiscUtils.DBPool</span>. Es ist eher zur Verdeutlichung des Konzepts gedacht,
 als zum Einsatz im produktiven Betrieb.</p>
-</div>
-<div class="section" id="steadydbconnection-steady-db">
-<h2>SteadyDBConnection (steady_db)</h2>
+</section>
+<section id="steadydbconnection-steady-db">
+<h3>SteadyDBConnection (steady_db)</h3>
 <p>Die Klasse <span class="docutils literal">SteadyDBConnection</span> im Modul <span class="docutils literal">dbutils.steady_db</span> stellt
 &quot;gehärtete&quot; Datenbankverbindungen bereit, denen gewöhnlichen Verbindungen
 eines DB-API-2-Datenbankadapters zugrunde liegen. Eine &quot;gehärtete&quot; Verbindung
@@ -184,9 +185,9 @@ <h2>SteadyDBConnection (steady_db)</h2>
 Firewall neu gestartet wurde und dabei ihren Verbindungsstatus verloren hat.</p>
 <p>Normalerweise benutzen Sie das <span class="docutils literal">steady_db</span>-Modul nicht direkt; es wird aber
 von den beiden nächsten Modulen benötigt, <span class="docutils literal">persistent_db</span> und <span class="docutils literal">pooled_db</span>.</p>
-</div>
-<div class="section" id="persistentdb-persistent-db">
-<h2>PersistentDB (persistent_db)</h2>
+</section>
+<section id="persistentdb-persistent-db">
+<h3>PersistentDB (persistent_db)</h3>
 <p>Die Klasse <span class="docutils literal">PersistentDB</span> im Modul <span class="docutils literal">dbutils.persistent_db</span> stellt
 gehärtete,  thread-affine, persistente Datenbankverbindungen zur Verfügung,
 unter Benutzung eines beliebigen DB-API-2-Datenbankadapters. Mit &quot;thread-affin&quot;
@@ -211,9 +212,9 @@ <h2>PersistentDB (persistent_db)</h2>
 liegende DB-API-2-Datenbankadapter nicht thread-sicher auf der Verbindungsebene
 ist, oder wenn parallele Threads Parameter der Datenbank-Sitzung verändern
 oder Transaktionen mit mehreren SQL-Befehlen durchführen.</p>
-</div>
-<div class="section" id="pooleddb-pooled-db">
-<h2>PooledDB (pooled_db)</h2>
+</section>
+<section id="pooleddb-pooled-db">
+<h3>PooledDB (pooled_db)</h3>
 <p>Die Klasse <span class="docutils literal">PooledDB</span> im Modul <span class="docutils literal">dbutils.pooled_db</span> stellt, unter Benutzung
 eines beliebigen DB-API-2-Datenbankadapters, einen Pool von gehärteten,
 thread-sicheren Datenbankverbindungen zur Verfügung, die automatisch, ohne dass
@@ -241,9 +242,9 @@ <h2>PooledDB (pooled_db)</h2>
 hierum keine Sorgen zu machen, aber Sie sollten darauf achten, dedizierte
 Datenbankverbindungen zu verwenden, sobald Sie Parameter der Datenbanksitzung
 verändern oder Transaktionen mit mehreren SQL-Befehlen ausführen.</p>
-</div>
-<div class="section" id="die-qual-der-wahl">
-<h2>Die Qual der Wahl</h2>
+</section>
+<section id="die-qual-der-wahl">
+<h3>Die Qual der Wahl</h3>
 <p>Sowohl <span class="docutils literal">persistent_db</span> als auch <span class="docutils literal">pooled_db</span> dienen dem gleichen Zweck,
 nämlich die Effizienz des Datenbankzugriffs durch Wiederverwendung von
 Datenbankverbindungen zu steigern, und dabei gleichzeitig die Stabilität
@@ -259,10 +260,10 @@ <h2>Die Qual der Wahl</h2>
 DB-API-2-Datenbankadapters.</p>
 <p>Da die Schnittstellen beider Module sehr ähnlich sind, können Sie recht einfach
 von einem Modul zum anderen wechseln und austesten, welches geeigneter ist.</p>
-</div>
-</div>
-<div class="section" id="benutzung">
-<h1>Benutzung</h1>
+</section>
+</section>
+<section id="benutzung">
+<h2>Benutzung</h2>
 <p>Die Benutzung aller Module ist zwar recht ähnlich, aber es gibt vor allem bei
 der Initialisierung auch einige Unterschiede, sowohl zwischen den &quot;Pooled&quot;-
 und den &quot;Persistent&quot;-Varianten, als auch zwischen den DB-API-2- und den
@@ -273,8 +274,8 @@ <h1>Benutzung</h1>
 sich die Dokumentation des <span class="docutils literal">pooled_db</span>-Moduls wie folgt anzeigen lassen (dies
 funktioniert entsprechend auch mit den anderen Modulen):</p>
 <pre class="literal-block">help(pooled_db)</pre>
-<div class="section" id="id2">
-<h2>PersistentDB (persistent_db)</h2>
+<section id="id2">
+<h3>PersistentDB (persistent_db)</h3>
 <p>Wenn Sie das <span class="docutils literal">persistent_db</span>-Modul einsetzen möchten, müssen Sie zuerst einen
 Generator für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
 indem Sie eine Instanz der Klasse <span class="docutils literal">persistent_db</span> erzeugen, wobei Sie folgende
@@ -336,9 +337,9 @@ <h2>PersistentDB (persistent_db)</h2>
 einigen Umgebungen nicht funktionieren (es ist zum Beispiel bekannt, dass
 <span class="docutils literal">mod_wsgi</span> hier Probleme bereitet, da es Daten, die mit <span class="docutils literal">threading.local</span>
 gespeichert wurden, zwischen Requests löscht).</p>
-</div>
-<div class="section" id="id3">
-<h2>PooledDB (pooled_db)</h2>
+</section>
+<section id="id3">
+<h3>PooledDB (pooled_db)</h3>
 <p>Wenn Sie das <span class="docutils literal">pooled_db</span>-Modul einsetzen möchten, müssen Sie zuerst einen
 Pool für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
 indem Sie eine Instanz der Klasse <span class="docutils literal">pooled_db</span> erzeugen, wobei Sie folgende
@@ -433,10 +434,10 @@ <h2>PooledDB (pooled_db)</h2>
 transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
 wieder an den Verbindungspool zurückgegeben wird.</p>
-</div>
-</div>
-<div class="section" id="anmerkungen">
-<h1>Anmerkungen</h1>
+</section>
+</section>
+<section id="anmerkungen">
+<h2>Anmerkungen</h2>
 <p>Wenn Sie einen der bekannten &quot;Object-Relational Mapper&quot; <a class="reference external" href="http://sqlobject.org/">SQLObject</a> oder
 <a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a> verwenden, dann benötigen Sie DBUtils nicht, denn diese haben
 ihre eigenen Mechanismen zum Pooling von Datenbankverbindungen eingebaut.
@@ -453,9 +454,9 @@ <h1>Anmerkungen</h1>
 dann sollten Sie auf eine Middleware für das Connection-Pooling zurückgreifen,
 die Multi-Processing unterstützt, wie zum Beispiel <a class="reference external" href="https://www.pgpool.net/">pgpool</a> oder <a class="reference external" href="https://pgbouncer.github.io/">pgbouncer</a>
 für die PostgreSQL-Datenbank.</p>
-</div>
-<div class="section" id="zukunft">
-<h1>Zukunft</h1>
+</section>
+<section id="zukunft">
+<h2>Zukunft</h2>
 <p>Einige Ideen für zukünftige Verbesserungen:</p>
 <ul class="simple">
 <li><p>Alternativ zur Obergrenze in der Anzahl der Nutzung einer Datenbankverbindung
@@ -475,14 +476,14 @@ <h1>Zukunft</h1>
 <li><p>Optional sollten Benutzung, schlechte Verbindungen und Überschreitung von
 Obergrenzen in Logs gespeichert werden können.</p></li>
 </ul>
-</div>
-<div class="section" id="fehlermeldungen-und-feedback">
-<h1>Fehlermeldungen und Feedback</h1>
+</section>
+<section id="fehlermeldungen-und-feedback">
+<h2>Fehlermeldungen und Feedback</h2>
 <p>Fehlermeldungen, Patches und Feedback können Sie als <a class="reference external" href="https://github.com/WebwareForPython/DBUtils/issues">Issues</a> oder
 <a class="reference external" href="https://github.com/WebwareForPython/DBUtils/pulls">Pull Requests</a> auf der <a class="reference external" href="https://github.com/WebwareForPython/DBUtils">GitHub-Projektseite</a> von DBUtils übermitteln.</p>
-</div>
-<div class="section" id="links">
-<h1>Links</h1>
+</section>
+<section id="links">
+<h2>Links</h2>
 <p>Einige Links zu verwandter und alternativer Software:</p>
 <ul class="simple">
 <li><p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a></p></li>
@@ -496,9 +497,9 @@ <h1>Links</h1>
 <li><p><a class="reference external" href="http://sqlobject.org/">SQLObject</a> Objekt-relationaler Mapper</p></li>
 <li><p><a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a> Objekt-relationaler Mapper</p></li>
 </ul>
-</div>
-<div class="section" id="autoren">
-<h1>Autoren</h1>
+</section>
+<section id="autoren">
+<h2>Autoren</h2>
 <dl class="field-list simple">
 <dt>Autor</dt>
 <dd><p><a class="reference external" href="https://github.com/Cito">Christoph Zwerschke</a></p>
@@ -511,14 +512,14 @@ <h1>Autoren</h1>
 Matthew Harriger, Gregory Piñero und Josef van Eenbergen.</p>
 </dd>
 </dl>
-</div>
-<div class="section" id="copyright-und-lizenz">
-<h1>Copyright und Lizenz</h1>
+</section>
+<section id="copyright-und-lizenz">
+<h2>Copyright und Lizenz</h2>
 <p>Copyright © 2005-2020 Christoph Zwerschke.
 Alle Rechte vorbehalten.</p>
 <p>DBUtils ist freie und quelloffene Software,
 lizenziert unter der <a class="reference external" href="https://opensource.org/licenses/MIT">MIT-Lizenz</a>.</p>
-</div>
-</div>
+</section>
+</main>
 </body>
 </html>
diff --git a/docs/main.html b/docs/main.html
index e6bc741..49c5cc5 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -2,12 +2,13 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8"/>
-<meta name="generator" content="Docutils 0.16: http://docutils.sourceforge.net/" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta name="generator" content="Docutils 0.17: http://docutils.sourceforge.net/" />
 <title>DBUtils User's Guide</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
-<body>
-<div class="document" id="dbutils-user-s-guide">
+<body class="with-toc">
+<main id="dbutils-user-s-guide">
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
@@ -51,17 +52,17 @@ <h1 class="title">DBUtils User's Guide</h1>
 <li><p><a class="reference internal" href="#copyright-and-license" id="id25">Copyright and License</a></p></li>
 </ul>
 </div>
-<div class="section" id="synopsis">
-<h1>Synopsis</h1>
+<section id="synopsis">
+<h2>Synopsis</h2>
 <p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a> is a suite of Python modules allowing to connect in a safe and
 efficient way between a threaded <a class="reference external" href="https://www.python.org">Python</a> application and a database.</p>
 <p>DBUtils has been originally written particularly for <a class="reference external" href="https://webwareforpython.github.io/w4py/">Webware for Python</a> as
 the application and <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> as the adapter to a <a class="reference external" href="https://www.postgresql.org/">PostgreSQL</a> database, but it
 can meanwhile be used for any other Python application and <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a>
 conformant database adapter.</p>
-</div>
-<div class="section" id="modules">
-<h1>Modules</h1>
+</section>
+<section id="modules">
+<h2>Modules</h2>
 <p>The DBUtils suite is realized as a Python package containing
 two subsets of modules, one for use with arbitrary DB-API 2 modules,
 the other one for use with the classic PyGreSQL module.</p>
@@ -119,42 +120,42 @@ <h1>Modules</h1>
 <p>The dependencies of the modules in the classic PyGreSQL variant
 are similar:</p>
 <img alt="dependencies_pg.png" src="dependencies_pg.png" />
-</div>
-<div class="section" id="download">
-<h1>Download</h1>
+</section>
+<section id="download">
+<h2>Download</h2>
 <p>You can download the actual version of DBUtils from
 the Python Package Index at:</p>
 <pre class="literal-block">https://pypi.python.org/pypi/DBUtils</pre>
 <p>The source code repository can be found here on GitHub:</p>
 <pre class="literal-block">https://github.com/WebwareForPython/DBUtils</pre>
-</div>
-<div class="section" id="installation">
-<h1>Installation</h1>
-<div class="section" id="id1">
+</section>
+<section id="installation">
 <h2>Installation</h2>
+<section id="id1">
+<h3>Installation</h3>
 <p>The package can be installed in the usual way:</p>
 <pre class="literal-block">python setup.py install</pre>
 <p>It is even easier to download and install the package in one go using <a class="reference external" href="https://pip.pypa.io/">pip</a>:</p>
 <pre class="literal-block">pip install DBUtils</pre>
-</div>
-</div>
-<div class="section" id="requirements">
-<h1>Requirements</h1>
+</section>
+</section>
+<section id="requirements">
+<h2>Requirements</h2>
 <p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> version 2.7 and Python versions 3.5 to 3.9.</p>
 <p>The modules in the classic PyGreSQL variant need <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
 any Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> compliant database interface module.</p>
-</div>
-<div class="section" id="functionality">
-<h1>Functionality</h1>
+</section>
+<section id="functionality">
+<h2>Functionality</h2>
 <p>This section will refer to the names in the DB-API 2 variant only,
 but the same applies to the classic PyGreSQL variant.</p>
 <p>DBUtils installs itself as a package <span class="docutils literal">dbutils</span> containing all the modules
 that are described in this guide. Each of these modules contains essentially
 one class with an analogous name that provides the corresponding functionality.
 For instance, the module <span class="docutils literal">dbutils.pooled_db</span> contains the class <span class="docutils literal">PooledDB</span>.</p>
-<div class="section" id="simplepooleddb-simple-pooled-db">
-<h2>SimplePooledDB (simple_pooled_db)</h2>
+<section id="simplepooleddb-simple-pooled-db">
+<h3>SimplePooledDB (simple_pooled_db)</h3>
 <p>The class <span class="docutils literal">SimplePooledDB</span> in <span class="docutils literal">dbutils.simple_pooled_db</span> is a very basic
 reference implementation of a pooled database connection. It is much less
 sophisticated than the regular <span class="docutils literal">pooled_db</span> module and is particularly lacking
@@ -162,9 +163,9 @@ <h2>SimplePooledDB (simple_pooled_db)</h2>
 same as the <span class="docutils literal">MiscUtils.DBPool</span> module that is part of Webware for Python.
 You should consider it a demonstration of concept rather than something
 that should go into production.</p>
-</div>
-<div class="section" id="steadydbconnection-steady-db">
-<h2>SteadyDBConnection (steady_db)</h2>
+</section>
+<section id="steadydbconnection-steady-db">
+<h3>SteadyDBConnection (steady_db)</h3>
 <p>The class <span class="docutils literal">SteadyDBConnection</span> in the module <span class="docutils literal">dbutils.steady_db</span> implements
 &quot;hardened&quot; connections to a database, based on ordinary connections made by any
 DB-API 2 database module. A &quot;hardened&quot; connection will transparently reopen
@@ -177,9 +178,9 @@ <h2>SteadyDBConnection (steady_db)</h2>
 restarted and lost its state.</p>
 <p>Usually, you will not use the <span class="docutils literal">steady_db</span> module directly; it merely serves
 as a basis for the next two modules, <span class="docutils literal">persistent_db</span> and <span class="docutils literal">Pooled_db</span>.</p>
-</div>
-<div class="section" id="persistentdb-persistent-db">
-<h2>PersistentDB (persistent_db)</h2>
+</section>
+<section id="persistentdb-persistent-db">
+<h3>PersistentDB (persistent_db)</h3>
 <p>The class <span class="docutils literal">PersistentDB</span> in the module <span class="docutils literal">dbutils.persistent_db</span> implements
 steady, thread-affine, persistent connections to a database, using any DB-API 2
 database module. &quot;Thread-affine&quot; and &quot;persistent&quot; means that the individual
@@ -201,9 +202,9 @@ <h2>PersistentDB (persistent_db)</h2>
 DB-API module is not thread-safe at the connection level, and it will
 avoid problems when other threads change the database session or perform
 transactions spreading over more than one SQL command.</p>
-</div>
-<div class="section" id="pooleddb-pooled-db">
-<h2>PooledDB (pooled_db)</h2>
+</section>
+<section id="pooleddb-pooled-db">
+<h3>PooledDB (pooled_db)</h3>
 <p>The class <span class="docutils literal">PooledDB</span> in the module <span class="docutils literal">dbutils.pooled_db</span> implements a pool
 of steady, thread-safe cached connections to a database which are transparently
 reused, using any DB-API 2 database module.</p>
@@ -226,9 +227,9 @@ <h2>PooledDB (pooled_db)</h2>
 don't need to worry about that, but you should be careful to use dedicated
 connections whenever you change the database session or perform transactions
 spreading over more than one SQL command.</p>
-</div>
-<div class="section" id="which-one-to-use">
-<h2>Which one to use?</h2>
+</section>
+<section id="which-one-to-use">
+<h3>Which one to use?</h3>
 <p>Both <span class="docutils literal">persistent_db</span> and <span class="docutils literal">pooled_db</span> serve the same purpose to improve
 the database access performance by recycling database connections, while
 preserving stability even if database connection will be disrupted.</p>
@@ -241,10 +242,10 @@ <h2>Which one to use?</h2>
 fine-tuning, particularly if you are using a thread-safe DB-API 2 module.</p>
 <p>Since the interface of both modules is similar, you can easily switch from
 one to the other and check which one will suit better.</p>
-</div>
-</div>
-<div class="section" id="usage">
-<h1>Usage</h1>
+</section>
+</section>
+<section id="usage">
+<h2>Usage</h2>
 <p>The usage of all the modules is similar, but there are also some differences
 in the initialization between the &quot;Pooled&quot; and &quot;Persistent&quot; variants and also
 between the universal DB-API 2 and the classic PyGreSQL variants.</p>
@@ -254,8 +255,8 @@ <h1>Usage</h1>
 display the documentation of the <span class="docutils literal">pooled_db</span> module as follows (this
 works analogously for the other modules):</p>
 <pre class="literal-block">help(pooled_db)</pre>
-<div class="section" id="id2">
-<h2>PersistentDB (persistent_db)</h2>
+<section id="id2">
+<h3>PersistentDB (persistent_db)</h3>
 <p>In order to make use of the <span class="docutils literal">persistent_db</span> module, you first need to set
 up a generator for your kind of database connections by creating an instance
 of <span class="docutils literal">persistent_db</span>, passing the following parameters:</p>
@@ -311,9 +312,9 @@ <h2>PersistentDB (persistent_db)</h2>
 connections may become a bit faster, but this may not work in all
 environments (for instance, <span class="docutils literal">mod_wsgi</span> is known to cause problems
 since it clears the <span class="docutils literal">threading.local</span> data between requests).</p>
-</div>
-<div class="section" id="id3">
-<h2>PooledDB (pooled_db)</h2>
+</section>
+<section id="id3">
+<h3>PooledDB (pooled_db)</h3>
 <p>In order to make use of the <span class="docutils literal">pooled_db</span> module, you first need to set up the
 database connection pool by creating an instance of <span class="docutils literal">pooled_db</span>, passing the
 following parameters:</p>
@@ -394,10 +395,10 @@ <h2>PooledDB (pooled_db)</h2>
 with other threads, that the transparent reopening will be suspended
 until the end of the transaction, and that the connection will be rolled
 back before being given back to the connection pool.</p>
-</div>
-</div>
-<div class="section" id="notes">
-<h1>Notes</h1>
+</section>
+</section>
+<section id="notes">
+<h2>Notes</h2>
 <p>If you are using one of the popular object-relational mappers <a class="reference external" href="http://www.sqlobject.org/">SQLObject</a>
 or <a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a>, you won't need DBUtils, since they come with their own
 connection pools. SQLObject 2 (SQL-API) is actually borrowing some code
@@ -413,9 +414,9 @@ <h1>Notes</h1>
 If you're running such a configuration, you should resort to a middleware
 for connection pooling that supports multi-processing, such as <a class="reference external" href="https://www.pgpool.net/">pgpool</a>
 or <a class="reference external" href="https://pgbouncer.github.io/">pgbouncer</a> for the PostgreSQL database.</p>
-</div>
-<div class="section" id="future">
-<h1>Future</h1>
+</section>
+<section id="future">
+<h2>Future</h2>
 <p>Some ideas for future improvements:</p>
 <ul class="simple">
 <li><p>Alternatively to the maximum number of uses of a connection,
@@ -434,14 +435,14 @@ <h1>Future</h1>
 the connection pool every day shortly before the users arrive.</p></li>
 <li><p>Optionally log usage, bad connections and exceeding of limits.</p></li>
 </ul>
-</div>
-<div class="section" id="bug-reports-and-feedback">
-<h1>Bug reports and feedback</h1>
+</section>
+<section id="bug-reports-and-feedback">
+<h2>Bug reports and feedback</h2>
 <p>You can transmit bug reports, patches and feedback by creating <a class="reference external" href="https://github.com/WebwareForPython/DBUtils/issues">issues</a> or
 <a class="reference external" href="https://github.com/WebwareForPython/DBUtils/pulls">pull requests</a> on the GitHub project page for DBUtils.</p>
-</div>
-<div class="section" id="links">
-<h1>Links</h1>
+</section>
+<section id="links">
+<h2>Links</h2>
 <p>Some links to related and alternative software:</p>
 <ul class="simple">
 <li><p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a></p></li>
@@ -455,9 +456,9 @@ <h1>Links</h1>
 <li><p><a class="reference external" href="http://www.sqlobject.org/">SQLObject</a> object-relational mapper</p></li>
 <li><p><a class="reference external" href="https://www.sqlalchemy.org">SQLAlchemy</a> object-relational mapper</p></li>
 </ul>
-</div>
-<div class="section" id="credits">
-<h1>Credits</h1>
+</section>
+<section id="credits">
+<h2>Credits</h2>
 <dl class="field-list simple">
 <dt>Author</dt>
 <dd><p><a class="reference external" href="https://github.com/Cito">Christoph Zwerschke</a></p>
@@ -470,14 +471,14 @@ <h1>Credits</h1>
 Matthew Harriger, Gregory Piñero and Josef van Eenbergen.</p>
 </dd>
 </dl>
-</div>
-<div class="section" id="copyright-and-license">
-<h1>Copyright and License</h1>
+</section>
+<section id="copyright-and-license">
+<h2>Copyright and License</h2>
 <p>Copyright © 2005-2020 by Christoph Zwerschke.
 All Rights Reserved.</p>
 <p>DBUtils is free and open source software,
 licensed under the <a class="reference external" href="https://opensource.org/licenses/MIT">MIT license</a>.</p>
-</div>
-</div>
+</section>
+</main>
 </body>
 </html>

From 844c31b06b9a2ceeed445407a0d12f3cbf4103c4 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Apr 2021 18:25:37 +0200
Subject: [PATCH 24/84] Add GitHub workflow for publishing releases on PyPI

---
 .github/workflows/publish_on_pypi.yml | 33 +++++++++++++++++++++++++++
 .github/workflows/test_with_tox.yml   |  5 ++++
 2 files changed, 38 insertions(+)
 create mode 100644 .github/workflows/publish_on_pypi.yml

diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
new file mode 100644
index 0000000..fc22580
--- /dev/null
+++ b/.github/workflows/publish_on_pypi.yml
@@ -0,0 +1,33 @@
+name: Publish DBUtils on PyPI
+
+on:
+  push:
+    tags:
+      - 'Release-*'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python: [2.7, 3.9]
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Set up Python ${{ matrix.python }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python }}
+
+    - name: Build wheel and source tarball
+      run: |
+        pip install wheel
+        python setup.py sdist bdist_wheel
+
+    - name: Publish a Python distribution to PyPI
+      if: matrix.python == 3.9
+      uses: pypa/gh-action-pypi-publish@release/v1
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index fe6e356..4ade996 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -8,13 +8,18 @@ jobs:
     strategy:
       matrix:
         python: [2.7, 3.6, 3.7, 3.8, 3.9]
+
     steps:
       - uses: actions/checkout@v2
+
       - name: Setup Python ${{ matrix.python }}
         uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python }}
+
       - run: pip install tox
+
       - run: tox -e py
+
       - if: matrix.python == 3.9
         run: TOXENV=flake8,manifest,docs,spell tox

From 0ba650bf5fa4776738f3627829c2062b1a5d7ebc Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Apr 2021 19:20:07 +0200
Subject: [PATCH 25/84] Prepare patch release

---
 .bumpversion.cfg    |  2 +-
 LICENSE             |  2 +-
 MANIFEST.in         |  1 +
 README.md           |  2 +-
 dbutils/__init__.py |  2 +-
 docs/changelog.html | 30 +++++++++++++++++++-----------
 docs/changelog.rst  |  9 +++++++++
 docs/main.de.html   |  4 ++--
 docs/main.de.rst    |  4 ++--
 docs/main.html      |  4 ++--
 docs/main.rst       |  4 ++--
 setup.py            |  2 +-
 12 files changed, 42 insertions(+), 24 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 9eab400..456e479 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 2.0
+current_version = 2.0.1
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/LICENSE b/LICENSE
index 9ecd470..641db5d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2018 Christoph Zwerschke
+Copyright (c) 2021 Christoph Zwerschke
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
index 82221db..be66fba 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,6 +4,7 @@ include LICENSE
 include README.md
 
 include .bumpversion.cfg
+include .codespellrc
 include .flake8
 include tox.ini
 
diff --git a/README.md b/README.md
index 9debced..ffd3afd 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 2.0 of DBUtils supports Python versions 2.7 and 3.5 to 3.9.
+The current version 2.0.1 of DBUtils supports Python versions 2.7 and 3.5 to 3.9.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there are some breaking changes in version 2.0.**
 
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index adfdbf7..23e2f58 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '2.0'
+__version__ = '2.0.1'
diff --git a/docs/changelog.html b/docs/changelog.html
index 7853af5..36833ea 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -12,6 +12,14 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="id1">
+<h2>2.0.1</h2>
+<p>DBUtils 2.0.1 was released on April 8, 2021.</p>
+<p>Changes:</p>
+<ul class="simple">
+<li><p>Avoid &quot;name Exception is not defined&quot; when exiting</p></li>
+</ul>
+</section>
+<section id="id2">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -27,7 +35,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="id2">
+<section id="id3">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -38,7 +46,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="id3">
+<section id="id4">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -47,12 +55,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="id4">
+<section id="id5">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="id5">
+<section id="id6">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -66,7 +74,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="id6">
+<section id="id7">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -95,7 +103,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="id7">
+<section id="id8">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -128,7 +136,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="id8">
+<section id="id9">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -137,7 +145,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="id9">
+<section id="id10">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -152,7 +160,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="id10">
+<section id="id11">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -162,7 +170,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="id11">
+<section id="id12">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -176,7 +184,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="id12">
+<section id="id13">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index fad22c0..36b300a 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,15 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+2.0.1
+=====
+
+DBUtils 2.0.1 was released on April 8, 2021.
+
+Changes:
+
+* Avoid "name Exception is not defined" when exiting
+
 2.0
 ===
 
diff --git a/docs/main.de.html b/docs/main.de.html
index 1e6ae7e..833ac1e 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -12,7 +12,7 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
-<dd class="version">2.0</dd>
+<dd class="version">2.0.1</dd>
 <dt class="translations">Translations</dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
@@ -515,7 +515,7 @@ <h2>Autoren</h2>
 </section>
 <section id="copyright-und-lizenz">
 <h2>Copyright und Lizenz</h2>
-<p>Copyright © 2005-2020 Christoph Zwerschke.
+<p>Copyright © 2005-2021 Christoph Zwerschke.
 Alle Rechte vorbehalten.</p>
 <p>DBUtils ist freie und quelloffene Software,
 lizenziert unter der <a class="reference external" href="https://opensource.org/licenses/MIT">MIT-Lizenz</a>.</p>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 3742b5b..561d3e2 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -1,7 +1,7 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 2.0
+:Version: 2.0.1
 :Translations: English_ | German
 
 .. _English: main.html
@@ -550,7 +550,7 @@ Autoren
 Copyright und Lizenz
 ====================
 
-Copyright © 2005-2020 Christoph Zwerschke.
+Copyright © 2005-2021 Christoph Zwerschke.
 Alle Rechte vorbehalten.
 
 DBUtils ist freie und quelloffene Software,
diff --git a/docs/main.html b/docs/main.html
index 49c5cc5..c666367 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -12,7 +12,7 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
-<dd class="version">2.0</dd>
+<dd class="version">2.0.1</dd>
 <dt class="translations">Translations</dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
@@ -474,7 +474,7 @@ <h2>Credits</h2>
 </section>
 <section id="copyright-and-license">
 <h2>Copyright and License</h2>
-<p>Copyright © 2005-2020 by Christoph Zwerschke.
+<p>Copyright © 2005-2021 by Christoph Zwerschke.
 All Rights Reserved.</p>
 <p>DBUtils is free and open source software,
 licensed under the <a class="reference external" href="https://opensource.org/licenses/MIT">MIT license</a>.</p>
diff --git a/docs/main.rst b/docs/main.rst
index 96f9804..8294edf 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -1,7 +1,7 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 2.0
+:Version: 2.0.1
 :Translations: English | German_
 
 .. _German: main.de.html
@@ -512,7 +512,7 @@ Credits
 Copyright and License
 =====================
 
-Copyright © 2005-2020 by Christoph Zwerschke.
+Copyright © 2005-2021 by Christoph Zwerschke.
 All Rights Reserved.
 
 DBUtils is free and open source software,
diff --git a/setup.py b/setup.py
index 861ed92..c355a37 100755
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '2.0'
+__version__ = '2.0.1'
 
 readme = open('README.md').read()
 

From 1e888ba57dc021fee7582c4f697e246c772e8be6 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Apr 2021 19:37:55 +0200
Subject: [PATCH 26/84] Improve workflow for publishing on PyPI

We want to publish wheels for both Python 2 and 3
---
 .github/workflows/publish_on_pypi.yml | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
index fc22580..92f72db 100644
--- a/.github/workflows/publish_on_pypi.yml
+++ b/.github/workflows/publish_on_pypi.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: [2.7, 3.9]
+        python: [3.9, 2.7]
 
     steps:
     - uses: actions/checkout@v2
@@ -20,13 +20,16 @@ jobs:
       with:
         python-version: ${{ matrix.python }}
 
-    - name: Build wheel and source tarball
+    - name: Build source tarball
+      if: matrix.python == 3.9
+      run: python setup.py sdist
+
+    - name: Build wheel
       run: |
         pip install wheel
-        python setup.py sdist bdist_wheel
+        python setup.py bdist_wheel
 
-    - name: Publish a Python distribution to PyPI
-      if: matrix.python == 3.9
+    - name: Publish distribution to PyPI
       uses: pypa/gh-action-pypi-publish@release/v1
       with:
         user: __token__

From c35a36d7cb7e71d5ba17982f8139dc1a7cc1e882 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Apr 2021 20:07:29 +0200
Subject: [PATCH 27/84] Update Docutils CSS

---
 docs/docutils.css | 103 +---------------------------------------------
 1 file changed, 2 insertions(+), 101 deletions(-)

diff --git a/docs/docutils.css b/docs/docutils.css
index 5219d8e..bebeb07 100644
--- a/docs/docutils.css
+++ b/docs/docutils.css
@@ -1,101 +1,2 @@
-/* CSS 3.1 style sheet for the output of Docutils 0.13 HTML5 writer. */
-.align-left{text-align:left}
-.align-right{text-align:right}
-.align-center{clear:both;text-align:center}
-.align-top{vertical-align:top}
-.align-middle{vertical-align:middle}
-.align-bottom{vertical-align:bottom}
-h1.title,p.subtitle{text-align:center}
-p.admonition-title,p.topic-title,p.sidebar-title,p.rubric,p.system-message-title{font-weight:700}
-h1 + p.subtitle,h1 + p.section-subtitle{font-size:1.6em}
-h2 + p.section-subtitle{font-size:1.28em}
-p.subtitle,p.section-subtitle,p.sidebar-subtitle{font-weight:700;margin-top:-.5em}
-p.sidebar-title,p.rubric{font-size:larger}
-p.rubric{color:maroon}
-a.toc-backref{color:#000;text-decoration:none}
-div.caution p.admonition-title,div.attention p.admonition-title,div.danger p.admonition-title,div.error p.admonition-title,div.warning p.admonition-title,div.system-messages h1,div.error,span.problematic,p.system-message-title{color:red}
-span.docutils.literal{font-family:monospace;white-space:pre-wrap}
-.literal > span.pre{white-space:nowrap}
-.simple li,.compact li,.simple ul,.compact ul,.simple ol,.compact ol,.simple > li p,.compact > li p,dl.simple > dd,dl.compact > dd{margin-top:0;margin-bottom:0}
-div.topic.contents{margin:0}
-ul.auto-toc{list-style-type:none;padding-left:1.5em}
-ol.arabic{list-style:decimal}
-ol.loweralpha{list-style:lower-alpha}
-ol.upperalpha{list-style:upper-alpha}
-ol.lowerroman{list-style:lower-roman}
-ol.upperroman{list-style:upper-roman}
-dt span.classifier{font-style:italic}
-dt span.classifier:before{font-style:normal;margin:.5em;content:":"}
-dl.field-list > dt,dl.option-list > dt,dl.docinfo > dt,dl.footnote > dt,dl.citation > dt{font-weight:700;clear:left;float:left;margin:0;padding:0;padding-right:.5em}
-dl.field-list > dd,dl.option-list > dd,dl.docinfo > dd{margin-left:9em}
-dl.field-list > dd > :first-child,dl.option-list > dd > :first-child{display:inline-block;width:100%;margin:0}
-dl.field-list > dt:after,dl.docinfo > dt:after{content:":"}
-pre.address{font:inherit}
-dd.authors > p{margin:0}
-dl.option-list{margin-left:40px}
-dl.option-list > dt{font-weight:400}
-span.option{white-space:nowrap}
-dl.footnote.superscript > dd{margin-left:1em}
-dl.footnote.brackets > dd{margin-left:2em}
-dl > dt.label{font-weight:400}
-a.footnote-reference.brackets:before,dt.label > span.brackets:before{content:"["}
-a.footnote-reference.brackets:after,dt.label > span.brackets:after{content:"]"}
-a.footnote-reference.superscript,dl.footnote.superscript > dt.label{vertical-align:super;font-size:smaller}
-dt.label > span.fn-backref{margin-left:.2em}
-dt.label > span.fn-backref > a{font-style:italic}
-div.line-block{display:block}
-div.line-block div.line-block{margin-top:0;margin-bottom:0;margin-left:40px}
-.figure.align-left,img.align-left,object.align-left,table.align-left{margin-right:auto}
-.figure.align-center,img.align-center,object.align-center{margin-left:auto;margin-right:auto;display:block}
-table.align-center{margin-left:auto;margin-right:auto}
-.figure.align-right,img.align-right,object.align-right,table.align-right{margin-left:auto}
-div.align-left,div.align-center,div.align-right,table.align-left,table.align-center,table.align-right{text-align:inherit}
-div.admonition,div.system-message,div.sidebar{margin:40px;border:medium outset;padding-right:1em;padding-left:1em}
-div.sidebar{width:30%;max-width:26em;float:right;clear:right}
-div.topic,pre.literal-block,pre.doctest-block,pre.math,pre.code{margin-right:40px;margin-left:40px}
-pre.code .ln{color:gray}
-table{border-collapse:collapse}
-td,th{border-style:solid;border-color:silver;padding:0 1ex;border-width:thin}
-td > p:first-child,th > p:first-child{margin-top:0}
-td > p,th > p{margin-bottom:0}
-table > caption{text-align:left;margin-bottom:.25em}
-table.borderless td,table.borderless th{border:0;padding:0;padding-right:.5em}
-body{padding:0 5%;margin:8px 0}
-div.document{line-height:1.3;counter-reset:table;max-width:50em;margin:auto}
-hr.docutils{width:80%;margin-top:1em;margin-bottom:1em;clear:both}
-p,ol,ul,dl,div.line-block,table{margin-top:.5em;margin-bottom:.5em}
-h1,h2,h3,h4,h5,h6,dl > dd{margin-bottom:.5em}
-dl > dd p:first-child{margin-top:0}
-dd > ul:only-child,dd > ol:only-child{padding-left:1em}
-dl.description > dt{font-weight:700;clear:left;float:left;margin:0;padding:0;padding-right:.5em}
-dl.field-list.narrow > dd{margin-left:5em}
-dl.field-list.run-in > dd p{display:block}
-div.abstract p.topic-title{text-align:center}
-div.dedication{margin:2em 5em;text-align:center;font-style:italic}
-div.dedication p.topic-title{font-style:normal}
-dl.citation dt.label{font-weight:700}
-span.fn-backref{font-weight:400}
-pre.literal-block,pre.doctest-block,pre.math,pre.code{margin-left:1.5em;margin-right:1.5em}
-blockquote,div.topic{margin-left:1.5em;margin-right:1.5em}
-blockquote > table,div.topic > table{margin-top:0;margin-bottom:0}
-blockquote p.attribution,div.topic p.attribution{text-align:right;margin-left:20%}
-table tr{text-align:left}
-table.booktabs{border:0;border-top:2px solid;border-bottom:2px solid;border-collapse:collapse}
-table.booktabs *{border:0}
-table.booktabs th{border-bottom:thin solid}
-table.numbered > caption:before{counter-increment:table;content:"Table " counter(table) ": ";font-weight:700}
-dl.footnote{padding-left:1ex;border-left:solid;border-left-width:thin}
-.figure.align-left,img.align-left,object.align-left{display:block;clear:left;float:left;margin-right:1em}
-.figure.align-right,img.align-right,object.align-right{display:block;clear:right;float:right;margin-left:1em}
-h1,h2,h3{clear:both}
-div.sidebar{width:30%;max-width:26em;margin-left:1em;margin-right:-5.5%;background-color:#ffe}
-pre.code,code{background-color:#eee}
-pre.code .ln{color:gray}
-pre.code .comment,code .comment{color:#5C6576}
-pre.code .keyword,code .keyword{color:#3B0D06;font-weight:700}
-pre.code .literal.string,code .literal.string{color:#0C5404}
-pre.code .name.builtin,code .name.builtin{color:#352B84}
-pre.code .deleted,code .deleted{background-color:#DEB0A1}
-pre.code .inserted,code .inserted{background-color:#A3D289}
-div.footer,div.header{clear:both;font-size:smaller}
-a{text-decoration:none}
\ No newline at end of file
+/* CSS 3.1 style sheet for the output of Docutils 0.17 HTML writer. */
+body{margin:0;background-color:#dbdbdb}main,footer,header{line-height:1.3;max-width:50rem;padding:1px 2%;margin:auto}main{counter-reset:table figure;background-color:white}footer,header{font-size:smaller;padding:.5em 2%;border:0}hr.docutils{width:80%;margin-top:1em;margin-bottom:1em;clear:both}p,ol,ul,dl,li,dd,div.line-block,div.topic,table{margin-top:.5em;margin-bottom:.5em}p:first-child{margin-top:0}p:last-child{margin-bottom:0}h1,h2,h3,h4,h5,h6,dl>dd{margin-bottom:.5em}dl>dd,ol>li,dd>ul:only-child,dd>ol:only-child{padding-left:1em}dl.description>dt{font-weight:bold;clear:left;float:left;margin:0;padding:0;padding-right:.5em}dl.field-list.narrow>dd{margin-left:5em}dl.field-list.run-in>dd p{display:block}div.abstract p.topic-title{text-align:center}div.dedication{margin:2em 5em;text-align:center;font-style:italic}div.dedication p.topic-title{font-style:normal}pre.literal-block,pre.doctest-block,pre.math,pre.code{font-family:monospace}blockquote>table,div.topic>table{margin-top:0;margin-bottom:0}blockquote p.attribution,div.topic p.attribution{text-align:right;margin-left:20%}table tr{text-align:left}table.booktabs{border:0;border-top:2px solid;border-bottom:2px solid;border-collapse:collapse}table.booktabs *{border:0}table.booktabs th{border-bottom:thin solid}table.numbered>caption:before{counter-increment:table;content:"Table " counter(table) ": ";font-weight:bold}dl.footnote{padding-left:1ex;border-left:solid;border-left-width:thin}figure.align-left,img.align-left,video.align-left,object.align-left{clear:left;float:left;margin-right:1em}figure.align-right,img.align-right,video.align-right,object.align-right{clear:right;float:right;margin-left:1em}h1,h2,h3,h4,footer,header{clear:both}figure.numbered>figcaption>p:before{counter-increment:figure;content:"Figure " counter(figure) ": ";font-weight:bold}.caution p.admonition-title,.attention p.admonition-title,.danger p.admonition-title,.error p.admonition-title,.warning p.admonition-title,div.error{color:red}aside.sidebar{width:30%;max-width:26em;margin-left:1em;margin-right:-2%;background-color:#ffe}pre.code{padding:.7ex}pre.code,code{background-color:#eee}pre.code .comment,code .comment{color:#5c6576}pre.code .keyword,code .keyword{color:#3b0d06;font-weight:bold}pre.code .literal.string,code .literal.string{color:#0c5404}pre.code .name.builtin,code .name.builtin{color:#352b84}pre.code .deleted,code .deleted{background-color:#deb0a1}pre.code .inserted,code .inserted{background-color:#a3d289}a{text-decoration:none}
\ No newline at end of file

From 54da25ea700a4128b1f5291dbb053e32847cdb01 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 14 May 2021 22:37:13 +0200
Subject: [PATCH 28/84] Support context managers (fixes #33)

---
 dbutils/pooled_db.py        | 23 +++++++++++++++++++++++
 dbutils/pooled_pg.py        | 13 +++++++++++++
 docs/changelog.html         | 34 +++++++++++++++++++++-------------
 docs/changelog.rst          | 12 +++++++++++-
 docs/main.de.html           |  5 +++++
 docs/main.de.rst            |  8 ++++++++
 docs/main.html              |  5 +++++
 docs/main.rst               |  7 +++++++
 tests/test_persistent_db.py |  8 ++++++++
 tests/test_persistent_pg.py |  6 ++++++
 tests/test_pooled_db.py     | 27 +++++++++++++++++++++++++++
 tests/test_pooled_pg.py     | 15 ++++++++++++++-
 12 files changed, 148 insertions(+), 15 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 2de9b55..e8367b0 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -114,6 +114,13 @@
     cur.close()  # or del cur
     db.close()  # or del db
 
+You can also use context managers for simpler code:
+
+    with pool.connection() as db:
+        with db.cursor as cur:
+            cur.execute(...)
+            res = cur.fetchone()
+
 Note that you need to explicitly start transactions by calling the
 begin() method.  This ensures that the connection will not be shared
 with other threads, that the transparent reopening will be suspended
@@ -440,6 +447,14 @@ def __del__(self):
         except:  # builtin Exceptions might not exist any more
             pass
 
+    def __enter__(self):
+        """Enter a runtime context for the connection."""
+        return self
+
+    def __exit__(self, *exc):
+        """Exit a runtime context for the connection."""
+        self.close()
+
 
 class SharedDBConnection:
     """Auxiliary class for shared connections."""
@@ -526,3 +541,11 @@ def __del__(self):
             self.close()
         except:  # builtin Exceptions might not exist any more
             pass
+
+    def __enter__(self):
+        """Enter a runtime context for the connection."""
+        return self
+
+    def __exit__(self, *exc):
+        """Exit a runtime context for the connection."""
+        self.close()
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index 0e0a3ae..86c379d 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -83,6 +83,11 @@
     res = db.query(...).getresult()
     db.close()  # or del db
 
+You can also a context manager for simpler code:
+
+    with pool.connection() as db:
+        res = db.query(...).getresult()
+
 Note that you need to explicitly start transactions by calling the
 begin() method.  This ensures that the transparent reopening will be
 suspended until the end of the transaction, and that the connection will
@@ -293,3 +298,11 @@ def __del__(self):
             self.close()
         except:  # builtin Exceptions might not exist any more
             pass
+
+    def __enter__(self):
+        """Enter a runtime context for the connection."""
+        return self
+
+    def __exit__(self, *exc):
+        """Exit a runtime context for the connection."""
+        self.close()
diff --git a/docs/changelog.html b/docs/changelog.html
index 36833ea..c8eddb0 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -12,14 +12,22 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="id1">
+<h2>2.0.2</h2>
+<p>DBUtils 2.0.2 was released on ...</p>
+<p>Changes:</p>
+<ul class="simple">
+<li><p>Allow using context managers for pooled connections.</p></li>
+</ul>
+</section>
+<section id="id2">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
 <ul class="simple">
-<li><p>Avoid &quot;name Exception is not defined&quot; when exiting</p></li>
+<li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="id2">
+<section id="id3">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -35,7 +43,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="id3">
+<section id="id4">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -46,7 +54,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="id4">
+<section id="id5">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -55,12 +63,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="id5">
+<section id="id6">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="id6">
+<section id="id7">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -74,7 +82,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="id7">
+<section id="id8">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -103,7 +111,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="id8">
+<section id="id9">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -136,7 +144,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="id9">
+<section id="id10">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -145,7 +153,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="id10">
+<section id="id11">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -160,7 +168,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="id11">
+<section id="id12">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -170,7 +178,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="id12">
+<section id="id13">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -184,7 +192,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="id13">
+<section id="id14">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 36b300a..5fd6765 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,16 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+2.0.2
+=====
+
+DBUtils 2.0.2 was released on ...
+
+Changes:
+
+* Allow using context managers for pooled connections.
+
+
 2.0.1
 =====
 
@@ -8,7 +18,7 @@ DBUtils 2.0.1 was released on April 8, 2021.
 
 Changes:
 
-* Avoid "name Exception is not defined" when exiting
+* Avoid "name Exception is not defined" when exiting.
 
 2.0
 ===
diff --git a/docs/main.de.html b/docs/main.de.html
index 833ac1e..a41f502 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -428,6 +428,11 @@ <h3>PooledDB (pooled_db)</h3>
 res = cur.fetchone()
 cur.close()  # oder del cur
 db.close()  # oder del db</pre>
+<p>Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen:</p>
+<pre class="literal-block">with pool.connection() as db:
+    with db.cursor as cur:
+        cur.execute(...)
+        res = cur.fetchone()</pre>
 <p>Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
 <span class="docutils literal">begin()</span> eingeleiten werden müssen. Hierdurch wird sichergestellt,
 dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 561d3e2..dc3f315 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -443,6 +443,14 @@ sie gebraucht werden, etwa so::
   cur.close()  # oder del cur
   db.close()  # oder del db
 
+Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen::
+
+  with pool.connection() as db:
+      with db.cursor as cur:
+          cur.execute(...)
+          res = cur.fetchone()
+
+
 Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
 ``begin()`` eingeleiten werden müssen. Hierdurch wird sichergestellt,
 dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das
diff --git a/docs/main.html b/docs/main.html
index c666367..2f81912 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -390,6 +390,11 @@ <h3>PooledDB (pooled_db)</h3>
 res = cur.fetchone()
 cur.close()  # or del cur
 db.close()  # or del db</pre>
+<p>You can also use context managers for simpler code:</p>
+<pre class="literal-block">with pool.connection() as db:
+    with db.cursor as cur:
+        cur.execute(...)
+        res = cur.fetchone()</pre>
 <p>Note that you need to explicitly start transactions by calling the
 <span class="docutils literal">begin()</span> method. This ensures that the connection will not be shared
 with other threads, that the transparent reopening will be suspended
diff --git a/docs/main.rst b/docs/main.rst
index 8294edf..426a298 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -405,6 +405,13 @@ object stays alive as long as you are using it, like that::
   cur.close()  # or del cur
   db.close()  # or del db
 
+You can also use context managers for simpler code::
+
+  with pool.connection() as db:
+      with db.cursor as cur:
+          cur.execute(...)
+          res = cur.fetchone()
+
 Note that you need to explicitly start transactions by calling the
 ``begin()`` method. This ensures that the connection will not be shared
 with other threads, that the transparent reopening will be suspended
diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index 7cccb30..ed5bce6 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -286,6 +286,14 @@ def test_failed_transaction(self):
         db._con.close()
         cursor.execute('select test')
 
+    def test_context_manager(self):
+        persist = PersistentDB(dbapi)
+        with persist.connection() as db:
+            with db.cursor() as cursor:
+                cursor.execute('select test')
+                r = cursor.fetchone()
+            self.assertEqual(r, 'test')
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index 5053316..78c85ca 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -189,6 +189,12 @@ def test_failed_transaction(self):
         db._con.close()
         self.assertEqual(db.query('select test'), 'test')
 
+    def test_context_manager(self):
+        persist = PersistentPg()
+        with persist.connection() as db:
+            db.query('select test')
+            self.assertEqual(db.num_queries, 1)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index bd5ccd0..80a8b45 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -1181,6 +1181,33 @@ def test_reset_transaction(self):
         self.assertFalse(con._transaction)
         self.assertEqual(con._con.session, ['rollback'])
 
+    def test_context_manager(self):
+        pool = PooledDB(dbapi, 1, 1, 1)
+        con = pool._idle_cache[0]._con
+        with pool.connection() as db:
+            self.assertTrue(hasattr(db, '_shared_con'))
+            self.assertFalse(pool._idle_cache)
+            self.assertTrue(con.valid)
+            with db.cursor() as cursor:
+                self.assertEqual(con.open_cursors, 1)
+                cursor.execute('select test')
+                r = cursor.fetchone()
+            self.assertEqual(con.open_cursors, 0)
+            self.assertEqual(r, 'test')
+            self.assertEqual(con.num_queries, 1)
+        self.assertTrue(pool._idle_cache)
+        with pool.dedicated_connection() as db:
+            self.assertFalse(hasattr(db, '_shared_con'))
+            self.assertFalse(pool._idle_cache)
+            with db.cursor() as cursor:
+                self.assertEqual(con.open_cursors, 1)
+                cursor.execute('select test')
+                r = cursor.fetchone()
+            self.assertEqual(con.open_cursors, 0)
+            self.assertEqual(r, 'test')
+            self.assertEqual(con.num_queries, 2)
+        self.assertTrue(pool._idle_cache)
+
 
 class TestSharedDBConnection(unittest.TestCase):
 
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index 1265888..bf0e314 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -14,7 +14,7 @@
 
 from . import mock_pg  # noqa
 
-from dbutils.pooled_pg import PooledPg, InvalidConnection
+from dbutils.pooled_pg import PooledPg, InvalidConnection, TooManyConnections
 
 
 class TestPooledPg(unittest.TestCase):
@@ -304,6 +304,19 @@ def test_reset_transaction(self):
         self.assertEqual(con.session, [])
         self.assertEqual(con.num_queries, 0)
 
+    def test_context_manager(self):
+        pool = PooledPg(1, 1, 1)
+        with pool.connection() as db:
+            db_con = db._con._con
+            db.query('select test')
+            self.assertEqual(db_con.num_queries, 1)
+            self.assertRaises(TooManyConnections, pool.connection)
+        with pool.connection() as db:
+            db_con = db._con._con
+            db.query('select test')
+            self.assertEqual(db_con.num_queries, 2)
+            self.assertRaises(TooManyConnections, pool.connection)
+
 
 if __name__ == '__main__':
     unittest.main()

From 2a43fda98ec58f35c9f50ed91160e119ceadc06d Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 14 May 2021 22:41:24 +0200
Subject: [PATCH 29/84] Fix some typos

---
 dbutils/persistent_db.py | 2 +-
 dbutils/persistent_pg.py | 2 +-
 docs/changelog.html      | 2 +-
 docs/main.de.html        | 8 ++++----
 docs/main.de.rst         | 6 +++---
 docs/main.html           | 4 ++--
 docs/main.rst            | 2 +-
 7 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/dbutils/persistent_db.py b/dbutils/persistent_db.py
index cc93720..8ebf43d 100644
--- a/dbutils/persistent_db.py
+++ b/dbutils/persistent_db.py
@@ -80,7 +80,7 @@
 ignored since it would be reopened at the next usage anyway and
 contrary to the intent of having persistent connections.  Instead,
 the connection will be automatically closed when the thread dies.
-You can change this behavior be setting the closeable parameter.
+You can change this behavior by setting the closeable parameter.
 
 Note that you need to explicitly start transactions by calling the
 begin() method.  This ensures that the transparent reopening will be
diff --git a/dbutils/persistent_pg.py b/dbutils/persistent_pg.py
index eb53e22..16b1338 100644
--- a/dbutils/persistent_pg.py
+++ b/dbutils/persistent_pg.py
@@ -70,7 +70,7 @@
 ignored since it would be reopened at the next usage anyway and
 contrary to the intent of having persistent connections.  Instead,
 the connection will be automatically closed when the thread dies.
-You can change this behavior be setting the closeable parameter.
+You can change this behavior by setting the closeable parameter.
 
 Note that you need to explicitly start transactions by calling the
 begin() method.  This ensures that the transparent reopening will be
diff --git a/docs/changelog.html b/docs/changelog.html
index c8eddb0..e38ae50 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8"/>
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.17: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 <title>Changelog for DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
diff --git a/docs/main.de.html b/docs/main.de.html
index a41f502..c5f0dd4 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8"/>
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.17: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 <title>Benutzeranleitung für DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -328,7 +328,7 @@ <h3>PersistentDB (persistent_db)</h3>
 endet. Sie können dieses Verhalten ändern, indem Sie den Parameter namens
 <span class="docutils literal">closeable</span> setzen.</p>
 <p>Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
-<span class="docutils literal">begin()</span> eingeleiten werden müssen. Hierdurch wird sichergestellt, dass
+<span class="docutils literal">begin()</span> eingeleitet werden müssen. Hierdurch wird sichergestellt, dass
 das transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, before sie vom
 gleichen Thread erneut benutzt wird.</p>
@@ -434,7 +434,7 @@ <h3>PooledDB (pooled_db)</h3>
         cur.execute(...)
         res = cur.fetchone()</pre>
 <p>Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
-<span class="docutils literal">begin()</span> eingeleiten werden müssen. Hierdurch wird sichergestellt,
+<span class="docutils literal">begin()</span> eingeleitet werden müssen. Hierdurch wird sichergestellt,
 dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das
 transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
@@ -452,7 +452,7 @@ <h2>Anmerkungen</h2>
 oder <a class="reference external" href="https://github.com/GrahamDumpleton/mod_wsgi">mod_wsgi</a>, dann sollten Sie bedenken, dass Ihr Python-Code normalerweise
 im Kontext der Kindprozesse des Webservers läuft. Wenn Sie also das
 <span class="docutils literal">pooled_db</span>-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann
-werden Sie ebensoviele Pools mit Datenbankverbindungen erhalten. Wenn diese
+werden Sie ebenso viele Pools mit Datenbankverbindungen erhalten. Wenn diese
 Prozesse viele Threads laufen lassen,  dann mag dies eine sinnvoller Ansatz
 sein, wenn aber diese Prozesse nicht mehr als einen Worker-Thread starten,
 wie im Fall des Multi-Processing Moduls &quot;prefork&quot; für den Apache-Webserver,
diff --git a/docs/main.de.rst b/docs/main.de.rst
index dc3f315..b14bae6 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -317,7 +317,7 @@ endet. Sie können dieses Verhalten ändern, indem Sie den Parameter namens
 ``closeable`` setzen.
 
 Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
-``begin()`` eingeleiten werden müssen. Hierdurch wird sichergestellt, dass
+``begin()`` eingeleitet werden müssen. Hierdurch wird sichergestellt, dass
 das transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, before sie vom
 gleichen Thread erneut benutzt wird.
@@ -452,7 +452,7 @@ Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen::
 
 
 Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
-``begin()`` eingeleiten werden müssen. Hierdurch wird sichergestellt,
+``begin()`` eingeleitet werden müssen. Hierdurch wird sichergestellt,
 dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das
 transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
@@ -471,7 +471,7 @@ Wenn Sie eine Lösung verwenden wie den Apache-Webserver mit mod_python_
 oder mod_wsgi_, dann sollten Sie bedenken, dass Ihr Python-Code normalerweise
 im Kontext der Kindprozesse des Webservers läuft. Wenn Sie also das
 ``pooled_db``-Modul einsetzen, und mehrere dieser Kindprozesse laufen, dann
-werden Sie ebensoviele Pools mit Datenbankverbindungen erhalten. Wenn diese
+werden Sie ebenso viele Pools mit Datenbankverbindungen erhalten. Wenn diese
 Prozesse viele Threads laufen lassen,  dann mag dies eine sinnvoller Ansatz
 sein, wenn aber diese Prozesse nicht mehr als einen Worker-Thread starten,
 wie im Fall des Multi-Processing Moduls "prefork" für den Apache-Webserver,
diff --git a/docs/main.html b/docs/main.html
index 2f81912..1c5aa93 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8"/>
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.17: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 <title>DBUtils User's Guide</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -303,7 +303,7 @@ <h3>PersistentDB (persistent_db)</h3>
 ignored since it would be reopened at the next usage anyway and
 contrary to the intent of having persistent connections. Instead,
 the connection will be automatically closed when the thread dies.
-You can change this behavior be setting the <span class="docutils literal">closeable</span> parameter.</p>
+You can change this behavior by setting the <span class="docutils literal">closeable</span> parameter.</p>
 <p>Note that you need to explicitly start transactions by calling the
 <span class="docutils literal">begin()</span> method. This ensures that the transparent reopening will be
 suspended until the end of the transaction, and that the connection
diff --git a/docs/main.rst b/docs/main.rst
index 426a298..e11e301 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -291,7 +291,7 @@ Closing a persistent connection with ``db.close()`` will be silently
 ignored since it would be reopened at the next usage anyway and
 contrary to the intent of having persistent connections. Instead,
 the connection will be automatically closed when the thread dies.
-You can change this behavior be setting the ``closeable`` parameter.
+You can change this behavior by setting the ``closeable`` parameter.
 
 Note that you need to explicitly start transactions by calling the
 ``begin()`` method. This ensures that the transparent reopening will be

From 3cd75e964ee2abcbf35d9ad13a96bd9177693842 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 14 May 2021 22:50:40 +0200
Subject: [PATCH 30/84] Make use of context managers for locks

---
 dbutils/pooled_db.py        | 25 +++++--------------------
 dbutils/simple_pooled_db.py |  5 +----
 2 files changed, 6 insertions(+), 24 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index e8367b0..e7d9947 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -284,8 +284,7 @@ def connection(self, shareable=True):
         then the connection may be shared with other threads.
         """
         if shareable and self._maxshared:
-            self._lock.acquire()
-            try:
+            with self._lock:
                 while (not self._shared_cache and self._maxconnections
                         and self._connections >= self._maxconnections):
                     self._wait_lock()
@@ -313,12 +312,9 @@ def connection(self, shareable=True):
                 # put the connection (back) into the shared cache
                 self._shared_cache.append(con)
                 self._lock.notify()
-            finally:
-                self._lock.release()
             con = PooledSharedDBConnection(self, con)
         else:  # try to get a dedicated connection
-            self._lock.acquire()
-            try:
+            with self._lock:
                 while (self._maxconnections
                         and self._connections >= self._maxconnections):
                     self._wait_lock()
@@ -331,8 +327,6 @@ def connection(self, shareable=True):
                     con._ping_check()  # check connection
                 con = PooledDedicatedDBConnection(self, con)
                 self._connections += 1
-            finally:
-                self._lock.release()
         return con
 
     def dedicated_connection(self):
@@ -341,8 +335,7 @@ def dedicated_connection(self):
 
     def unshare(self, con):
         """Decrease the share of a connection in the shared cache."""
-        self._lock.acquire()
-        try:
+        with self._lock:
             con.unshare()
             shared = con.shared
             if not shared:  # connection is idle,
@@ -350,15 +343,12 @@ def unshare(self, con):
                     self._shared_cache.remove(con)  # from shared cache
                 except ValueError:
                     pass  # pool has already been closed
-        finally:
-            self._lock.release()
         if not shared:  # connection has become idle,
             self.cache(con.con)  # so add it to the idle cache
 
     def cache(self, con):
         """Put a dedicated connection back into the idle cache."""
-        self._lock.acquire()
-        try:
+        with self._lock:
             if not self._maxcached or len(self._idle_cache) < self._maxcached:
                 con._reset(force=self._reset)  # rollback possible transaction
                 # the idle cache is not full, so put it there
@@ -367,13 +357,10 @@ def cache(self, con):
                 con.close()  # then close the connection
             self._connections -= 1
             self._lock.notify()
-        finally:
-            self._lock.release()
 
     def close(self):
         """Close all connections in the pool."""
-        self._lock.acquire()
-        try:
+        with self._lock:
             while self._idle_cache:  # close all idle connections
                 con = self._idle_cache.pop(0)
                 try:
@@ -389,8 +376,6 @@ def close(self):
                         pass
                     self._connections -= 1
             self._lock.notifyAll()
-        finally:
-            self._lock.release()
 
     def __del__(self):
         """Delete the pool."""
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index 5e31b29..b66cf7f 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -196,8 +196,7 @@ def _unthreadsafe_return_connection(self, con):
 
     def _threadsafe_get_connection(self):
         """Get a connection from the pool."""
-        self._lock.acquire()
-        try:
+        with self._lock:
             next = self._nextConnection
             con = PooledDBConnection(self, self._connections[next])
             next += 1
@@ -205,8 +204,6 @@ def _threadsafe_get_connection(self):
                 next = 0
             self._nextConnection = next
             return con
-        finally:
-            self._lock.release()
 
     def _threadsafe_add_connection(self, con):
         """Add a connection to the pool."""

From b71bb5d529c0e2ac2da9b729ffde40396925d776 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 8 Jul 2021 18:45:21 +0200
Subject: [PATCH 31/84] Prepare patch release

---
 .bumpversion.cfg    | 2 +-
 README.md           | 2 +-
 dbutils/__init__.py | 2 +-
 docs/changelog.html | 2 +-
 docs/changelog.rst  | 2 +-
 docs/main.de.html   | 2 +-
 docs/main.de.rst    | 2 +-
 docs/main.html      | 2 +-
 docs/main.rst       | 2 +-
 setup.py            | 2 +-
 10 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 456e479..e9be47b 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 2.0.1
+current_version = 2.0.2
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/README.md b/README.md
index ffd3afd..1ebfde7 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 2.0.1 of DBUtils supports Python versions 2.7 and 3.5 to 3.9.
+The current version 2.0.2 of DBUtils supports Python versions 2.7 and 3.5 to 3.9.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there are some breaking changes in version 2.0.**
 
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index 23e2f58..3a26d9b 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '2.0.1'
+__version__ = '2.0.2'
diff --git a/docs/changelog.html b/docs/changelog.html
index e38ae50..06a7831 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -13,7 +13,7 @@ <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="id1">
 <h2>2.0.2</h2>
-<p>DBUtils 2.0.2 was released on ...</p>
+<p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
 <ul class="simple">
 <li><p>Allow using context managers for pooled connections.</p></li>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 5fd6765..d216900 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,7 +4,7 @@
 2.0.2
 =====
 
-DBUtils 2.0.2 was released on ...
+DBUtils 2.0.2 was released on June 8, 2021.
 
 Changes:
 
diff --git a/docs/main.de.html b/docs/main.de.html
index c5f0dd4..7fecb2b 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -12,7 +12,7 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
-<dd class="version">2.0.1</dd>
+<dd class="version">2.0.2</dd>
 <dt class="translations">Translations</dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index b14bae6..180da0e 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -1,7 +1,7 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 2.0.1
+:Version: 2.0.2
 :Translations: English_ | German
 
 .. _English: main.html
diff --git a/docs/main.html b/docs/main.html
index 1c5aa93..0b9fcd7 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -12,7 +12,7 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version</dt>
-<dd class="version">2.0.1</dd>
+<dd class="version">2.0.2</dd>
 <dt class="translations">Translations</dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
diff --git a/docs/main.rst b/docs/main.rst
index e11e301..11a7c7e 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -1,7 +1,7 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 2.0.1
+:Version: 2.0.2
 :Translations: English | German_
 
 .. _German: main.de.html
diff --git a/setup.py b/setup.py
index c355a37..2ef3df7 100755
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '2.0.1'
+__version__ = '2.0.2'
 
 readme = open('README.md').read()
 

From 7d152632a2185a753fe8f23e04c91630b4ce4198 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 26 Nov 2021 20:51:02 +0100
Subject: [PATCH 32/84] Support Python 3.10

Replace deprecated spelling of Condition.notify_all().
---
 .github/workflows/publish_on_pypi.yml |  2 +-
 .github/workflows/test_with_tox.yml   |  2 +-
 dbutils/pooled_db.py                  |  2 +-
 docs/changelog.html                   | 30 +++++------
 docs/main.de.html                     | 72 ++++++++++++---------------
 docs/main.de.rst                      |  2 +-
 docs/main.html                        | 72 ++++++++++++---------------
 docs/main.rst                         |  2 +-
 setup.py                              |  1 +
 tox.ini                               |  2 +-
 10 files changed, 86 insertions(+), 101 deletions(-)

diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
index 92f72db..c9a7589 100644
--- a/.github/workflows/publish_on_pypi.yml
+++ b/.github/workflows/publish_on_pypi.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: [3.9, 2.7]
+        python: ['3.9', '2.7']
 
     steps:
     - uses: actions/checkout@v2
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index 4ade996..904f2db 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -7,7 +7,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: [2.7, 3.6, 3.7, 3.8, 3.9]
+        python: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
 
     steps:
       - uses: actions/checkout@v2
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index e7d9947..a442bdb 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -375,7 +375,7 @@ def close(self):
                     except Exception:
                         pass
                     self._connections -= 1
-            self._lock.notifyAll()
+            self._lock.notify_all()
 
     def __del__(self):
         """Delete the pool."""
diff --git a/docs/changelog.html b/docs/changelog.html
index 06a7831..4990b56 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8"/>
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
 <title>Changelog for DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -11,7 +11,7 @@
 <main id="changelog-for-dbutils">
 <h1 class="title">Changelog for DBUtils</h1>
 
-<section id="id1">
+<section id="section-1">
 <h2>2.0.2</h2>
 <p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
@@ -19,7 +19,7 @@ <h2>2.0.2</h2>
 <li><p>Allow using context managers for pooled connections.</p></li>
 </ul>
 </section>
-<section id="id2">
+<section id="section-2">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
@@ -27,7 +27,7 @@ <h2>2.0.1</h2>
 <li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="id3">
+<section id="section-3">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -43,7 +43,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="id4">
+<section id="section-4">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -54,7 +54,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="id5">
+<section id="section-5">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -63,12 +63,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="id6">
+<section id="section-6">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="id7">
+<section id="section-7">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -82,7 +82,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="id8">
+<section id="section-8">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -111,7 +111,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="id9">
+<section id="section-9">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -144,7 +144,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="id10">
+<section id="section-10">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -153,7 +153,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="id11">
+<section id="section-11">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -168,7 +168,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="id12">
+<section id="section-12">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -178,7 +178,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="id13">
+<section id="section-13">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -192,7 +192,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="id14">
+<section id="section-14">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/main.de.html b/docs/main.de.html
index 7fecb2b..5f72110 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8"/>
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
 <title>Benutzeranleitung für DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -11,47 +11,47 @@
 <main id="benutzeranleitung-fur-dbutils">
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
-<dt class="version">Version</dt>
+<dt class="version">Version<span class="colon">:</span></dt>
 <dd class="version">2.0.2</dd>
-<dt class="translations">Translations</dt>
+<dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
 </dl>
-<div class="contents topic" id="inhalt">
+<nav class="contents" id="inhalt" role="doc-toc">
 <p class="topic-title">Inhalt</p>
 <ul class="simple">
-<li><p><a class="reference internal" href="#zusammenfassung" id="id5">Zusammenfassung</a></p></li>
-<li><p><a class="reference internal" href="#module" id="id6">Module</a></p></li>
-<li><p><a class="reference internal" href="#download" id="id7">Download</a></p></li>
-<li><p><a class="reference internal" href="#installation" id="id8">Installation</a></p>
+<li><p><a class="reference internal" href="#zusammenfassung" id="toc-entry-1">Zusammenfassung</a></p></li>
+<li><p><a class="reference internal" href="#module" id="toc-entry-2">Module</a></p></li>
+<li><p><a class="reference internal" href="#download" id="toc-entry-3">Download</a></p></li>
+<li><p><a class="reference internal" href="#installation" id="toc-entry-4">Installation</a></p>
 <ul>
-<li><p><a class="reference internal" href="#id1" id="id9">Installation</a></p></li>
+<li><p><a class="reference internal" href="#installation-1" id="toc-entry-5">Installation</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#anforderungen" id="id10">Anforderungen</a></p></li>
-<li><p><a class="reference internal" href="#funktionalitat" id="id11">Funktionalität</a></p>
+<li><p><a class="reference internal" href="#anforderungen" id="toc-entry-6">Anforderungen</a></p></li>
+<li><p><a class="reference internal" href="#funktionalitat" id="toc-entry-7">Funktionalität</a></p>
 <ul>
-<li><p><a class="reference internal" href="#simplepooleddb-simple-pooled-db" id="id12">SimplePooledDB (simple_pooled_db)</a></p></li>
-<li><p><a class="reference internal" href="#steadydbconnection-steady-db" id="id13">SteadyDBConnection (steady_db)</a></p></li>
-<li><p><a class="reference internal" href="#persistentdb-persistent-db" id="id14">PersistentDB (persistent_db)</a></p></li>
-<li><p><a class="reference internal" href="#pooleddb-pooled-db" id="id15">PooledDB (pooled_db)</a></p></li>
-<li><p><a class="reference internal" href="#die-qual-der-wahl" id="id16">Die Qual der Wahl</a></p></li>
+<li><p><a class="reference internal" href="#simplepooleddb-simple-pooled-db" id="toc-entry-8">SimplePooledDB (simple_pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#steadydbconnection-steady-db" id="toc-entry-9">SteadyDBConnection (steady_db)</a></p></li>
+<li><p><a class="reference internal" href="#persistentdb-persistent-db" id="toc-entry-10">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#pooleddb-pooled-db" id="toc-entry-11">PooledDB (pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#die-qual-der-wahl" id="toc-entry-12">Die Qual der Wahl</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#benutzung" id="id17">Benutzung</a></p>
+<li><p><a class="reference internal" href="#benutzung" id="toc-entry-13">Benutzung</a></p>
 <ul>
-<li><p><a class="reference internal" href="#id2" id="id18">PersistentDB (persistent_db)</a></p></li>
-<li><p><a class="reference internal" href="#id3" id="id19">PooledDB (pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#persistentdb-persistent-db-1" id="toc-entry-14">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#pooleddb-pooled-db-1" id="toc-entry-15">PooledDB (pooled_db)</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#anmerkungen" id="id20">Anmerkungen</a></p></li>
-<li><p><a class="reference internal" href="#zukunft" id="id21">Zukunft</a></p></li>
-<li><p><a class="reference internal" href="#fehlermeldungen-und-feedback" id="id22">Fehlermeldungen und Feedback</a></p></li>
-<li><p><a class="reference internal" href="#links" id="id23">Links</a></p></li>
-<li><p><a class="reference internal" href="#autoren" id="id24">Autoren</a></p></li>
-<li><p><a class="reference internal" href="#copyright-und-lizenz" id="id25">Copyright und Lizenz</a></p></li>
+<li><p><a class="reference internal" href="#anmerkungen" id="toc-entry-16">Anmerkungen</a></p></li>
+<li><p><a class="reference internal" href="#zukunft" id="toc-entry-17">Zukunft</a></p></li>
+<li><p><a class="reference internal" href="#fehlermeldungen-und-feedback" id="toc-entry-18">Fehlermeldungen und Feedback</a></p></li>
+<li><p><a class="reference internal" href="#links" id="toc-entry-19">Links</a></p></li>
+<li><p><a class="reference internal" href="#autoren" id="toc-entry-20">Autoren</a></p></li>
+<li><p><a class="reference internal" href="#copyright-und-lizenz" id="toc-entry-21">Copyright und Lizenz</a></p></li>
 </ul>
-</div>
+</nav>
 <section id="zusammenfassung">
 <h2>Zusammenfassung</h2>
 <p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a> ist eine Sammlung von Python-Modulen, mit deren Hilfe man in <a class="reference external" href="https://www.python.org">Python</a>
@@ -69,10 +69,6 @@ <h2>Module</h2>
 DB-API-2-Datenbankadaptern, und einer Gruppe zur Verwendung mit dem klassischen
 PyGreSQL-Datenbankadapter-Modul.</p>
 <table>
-<colgroup>
-<col style="width: 28%" />
-<col style="width: 72%" />
-</colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Allgemeine Variante für beliebige DB-API-2-Adapter</p></th>
 </tr>
@@ -93,10 +89,6 @@ <h2>Module</h2>
 </tbody>
 </table>
 <table>
-<colgroup>
-<col style="width: 28%" />
-<col style="width: 72%" />
-</colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Variante speziell für den klassischen PyGreSQL-Adapter</p></th>
 </tr>
@@ -133,7 +125,7 @@ <h2>Download</h2>
 </section>
 <section id="installation">
 <h2>Installation</h2>
-<section id="id1">
+<section id="installation-1">
 <h3>Installation</h3>
 <p>Das Paket kann auf die übliche Weise installiert werden:</p>
 <pre class="literal-block">python setup.py install</pre>
@@ -144,7 +136,7 @@ <h3>Installation</h3>
 </section>
 <section id="anforderungen">
 <h2>Anforderungen</h2>
-<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 2.7 und 3.5 bis 3.9.</p>
+<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 2.7 und 3.5 bis 3.10.</p>
 <p>Die Module in der Variante für klassisches PyGreSQL benötigen <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a>
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
 für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten,
@@ -274,7 +266,7 @@ <h2>Benutzung</h2>
 sich die Dokumentation des <span class="docutils literal">pooled_db</span>-Moduls wie folgt anzeigen lassen (dies
 funktioniert entsprechend auch mit den anderen Modulen):</p>
 <pre class="literal-block">help(pooled_db)</pre>
-<section id="id2">
+<section id="persistentdb-persistent-db-1">
 <h3>PersistentDB (persistent_db)</h3>
 <p>Wenn Sie das <span class="docutils literal">persistent_db</span>-Modul einsetzen möchten, müssen Sie zuerst einen
 Generator für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
@@ -338,7 +330,7 @@ <h3>PersistentDB (persistent_db)</h3>
 <span class="docutils literal">mod_wsgi</span> hier Probleme bereitet, da es Daten, die mit <span class="docutils literal">threading.local</span>
 gespeichert wurden, zwischen Requests löscht).</p>
 </section>
-<section id="id3">
+<section id="pooleddb-pooled-db-1">
 <h3>PooledDB (pooled_db)</h3>
 <p>Wenn Sie das <span class="docutils literal">pooled_db</span>-Modul einsetzen möchten, müssen Sie zuerst einen
 Pool für die von Ihnen gewünschte Art von Datenbankverbindungen einrichten,
@@ -506,10 +498,10 @@ <h2>Links</h2>
 <section id="autoren">
 <h2>Autoren</h2>
 <dl class="field-list simple">
-<dt>Autor</dt>
+<dt>Autor<span class="colon">:</span></dt>
 <dd><p><a class="reference external" href="https://github.com/Cito">Christoph Zwerschke</a></p>
 </dd>
-<dt>Beiträge</dt>
+<dt>Beiträge<span class="colon">:</span></dt>
 <dd><p>DBUtils benutzt Code, Anmerkungen und Vorschläge von
 Ian Bicking, Chuck Esterbrook (Webware for Python), Dan Green (DBTools),
 Jay Love, Michael Palmer, Tom Schwaller, Geoffrey Talvola,
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 180da0e..4184e76 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -98,7 +98,7 @@ herunterzuladen und zu installieren::
 Anforderungen
 =============
 
-DBUtils unterstützt die Python_ Versionen 2.7 und 3.5 bis 3.9.
+DBUtils unterstützt die Python_ Versionen 2.7 und 3.5 bis 3.10.
 
 Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL_
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
diff --git a/docs/main.html b/docs/main.html
index 0b9fcd7..964126a 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8"/>
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
 <title>DBUtils User's Guide</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -11,47 +11,47 @@
 <main id="dbutils-user-s-guide">
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
-<dt class="version">Version</dt>
+<dt class="version">Version<span class="colon">:</span></dt>
 <dd class="version">2.0.2</dd>
-<dt class="translations">Translations</dt>
+<dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
 </dl>
-<div class="contents topic" id="contents">
+<nav class="contents" id="contents" role="doc-toc">
 <p class="topic-title">Contents</p>
 <ul class="simple">
-<li><p><a class="reference internal" href="#synopsis" id="id5">Synopsis</a></p></li>
-<li><p><a class="reference internal" href="#modules" id="id6">Modules</a></p></li>
-<li><p><a class="reference internal" href="#download" id="id7">Download</a></p></li>
-<li><p><a class="reference internal" href="#installation" id="id8">Installation</a></p>
+<li><p><a class="reference internal" href="#synopsis" id="toc-entry-1">Synopsis</a></p></li>
+<li><p><a class="reference internal" href="#modules" id="toc-entry-2">Modules</a></p></li>
+<li><p><a class="reference internal" href="#download" id="toc-entry-3">Download</a></p></li>
+<li><p><a class="reference internal" href="#installation" id="toc-entry-4">Installation</a></p>
 <ul>
-<li><p><a class="reference internal" href="#id1" id="id9">Installation</a></p></li>
+<li><p><a class="reference internal" href="#installation-1" id="toc-entry-5">Installation</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#requirements" id="id10">Requirements</a></p></li>
-<li><p><a class="reference internal" href="#functionality" id="id11">Functionality</a></p>
+<li><p><a class="reference internal" href="#requirements" id="toc-entry-6">Requirements</a></p></li>
+<li><p><a class="reference internal" href="#functionality" id="toc-entry-7">Functionality</a></p>
 <ul>
-<li><p><a class="reference internal" href="#simplepooleddb-simple-pooled-db" id="id12">SimplePooledDB (simple_pooled_db)</a></p></li>
-<li><p><a class="reference internal" href="#steadydbconnection-steady-db" id="id13">SteadyDBConnection (steady_db)</a></p></li>
-<li><p><a class="reference internal" href="#persistentdb-persistent-db" id="id14">PersistentDB (persistent_db)</a></p></li>
-<li><p><a class="reference internal" href="#pooleddb-pooled-db" id="id15">PooledDB (pooled_db)</a></p></li>
-<li><p><a class="reference internal" href="#which-one-to-use" id="id16">Which one to use?</a></p></li>
+<li><p><a class="reference internal" href="#simplepooleddb-simple-pooled-db" id="toc-entry-8">SimplePooledDB (simple_pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#steadydbconnection-steady-db" id="toc-entry-9">SteadyDBConnection (steady_db)</a></p></li>
+<li><p><a class="reference internal" href="#persistentdb-persistent-db" id="toc-entry-10">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#pooleddb-pooled-db" id="toc-entry-11">PooledDB (pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#which-one-to-use" id="toc-entry-12">Which one to use?</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#usage" id="id17">Usage</a></p>
+<li><p><a class="reference internal" href="#usage" id="toc-entry-13">Usage</a></p>
 <ul>
-<li><p><a class="reference internal" href="#id2" id="id18">PersistentDB (persistent_db)</a></p></li>
-<li><p><a class="reference internal" href="#id3" id="id19">PooledDB (pooled_db)</a></p></li>
+<li><p><a class="reference internal" href="#persistentdb-persistent-db-1" id="toc-entry-14">PersistentDB (persistent_db)</a></p></li>
+<li><p><a class="reference internal" href="#pooleddb-pooled-db-1" id="toc-entry-15">PooledDB (pooled_db)</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#notes" id="id20">Notes</a></p></li>
-<li><p><a class="reference internal" href="#future" id="id21">Future</a></p></li>
-<li><p><a class="reference internal" href="#bug-reports-and-feedback" id="id22">Bug reports and feedback</a></p></li>
-<li><p><a class="reference internal" href="#links" id="id23">Links</a></p></li>
-<li><p><a class="reference internal" href="#credits" id="id24">Credits</a></p></li>
-<li><p><a class="reference internal" href="#copyright-and-license" id="id25">Copyright and License</a></p></li>
+<li><p><a class="reference internal" href="#notes" id="toc-entry-16">Notes</a></p></li>
+<li><p><a class="reference internal" href="#future" id="toc-entry-17">Future</a></p></li>
+<li><p><a class="reference internal" href="#bug-reports-and-feedback" id="toc-entry-18">Bug reports and feedback</a></p></li>
+<li><p><a class="reference internal" href="#links" id="toc-entry-19">Links</a></p></li>
+<li><p><a class="reference internal" href="#credits" id="toc-entry-20">Credits</a></p></li>
+<li><p><a class="reference internal" href="#copyright-and-license" id="toc-entry-21">Copyright and License</a></p></li>
 </ul>
-</div>
+</nav>
 <section id="synopsis">
 <h2>Synopsis</h2>
 <p><a class="reference external" href="https://github.com/WebwareForPython/DBUtils">DBUtils</a> is a suite of Python modules allowing to connect in a safe and
@@ -67,10 +67,6 @@ <h2>Modules</h2>
 two subsets of modules, one for use with arbitrary DB-API 2 modules,
 the other one for use with the classic PyGreSQL module.</p>
 <table>
-<colgroup>
-<col style="width: 30%" />
-<col style="width: 70%" />
-</colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Universal DB-API 2 variant</p></th>
 </tr>
@@ -91,10 +87,6 @@ <h2>Modules</h2>
 </tbody>
 </table>
 <table>
-<colgroup>
-<col style="width: 30%" />
-<col style="width: 70%" />
-</colgroup>
 <thead>
 <tr><th class="head" colspan="2"><p>Classic PyGreSQL variant</p></th>
 </tr>
@@ -131,7 +123,7 @@ <h2>Download</h2>
 </section>
 <section id="installation">
 <h2>Installation</h2>
-<section id="id1">
+<section id="installation-1">
 <h3>Installation</h3>
 <p>The package can be installed in the usual way:</p>
 <pre class="literal-block">python setup.py install</pre>
@@ -141,7 +133,7 @@ <h3>Installation</h3>
 </section>
 <section id="requirements">
 <h2>Requirements</h2>
-<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> version 2.7 and Python versions 3.5 to 3.9.</p>
+<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> version 2.7 and Python versions 3.5 to 3.10.</p>
 <p>The modules in the classic PyGreSQL variant need <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
 any Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> compliant database interface module.</p>
@@ -255,7 +247,7 @@ <h2>Usage</h2>
 display the documentation of the <span class="docutils literal">pooled_db</span> module as follows (this
 works analogously for the other modules):</p>
 <pre class="literal-block">help(pooled_db)</pre>
-<section id="id2">
+<section id="persistentdb-persistent-db-1">
 <h3>PersistentDB (persistent_db)</h3>
 <p>In order to make use of the <span class="docutils literal">persistent_db</span> module, you first need to set
 up a generator for your kind of database connections by creating an instance
@@ -313,7 +305,7 @@ <h3>PersistentDB (persistent_db)</h3>
 environments (for instance, <span class="docutils literal">mod_wsgi</span> is known to cause problems
 since it clears the <span class="docutils literal">threading.local</span> data between requests).</p>
 </section>
-<section id="id3">
+<section id="pooleddb-pooled-db-1">
 <h3>PooledDB (pooled_db)</h3>
 <p>In order to make use of the <span class="docutils literal">pooled_db</span> module, you first need to set up the
 database connection pool by creating an instance of <span class="docutils literal">pooled_db</span>, passing the
@@ -465,10 +457,10 @@ <h2>Links</h2>
 <section id="credits">
 <h2>Credits</h2>
 <dl class="field-list simple">
-<dt>Author</dt>
+<dt>Author<span class="colon">:</span></dt>
 <dd><p><a class="reference external" href="https://github.com/Cito">Christoph Zwerschke</a></p>
 </dd>
-<dt>Contributions</dt>
+<dt>Contributions<span class="colon">:</span></dt>
 <dd><p>DBUtils uses code, input and suggestions made by
 Ian Bicking, Chuck Esterbrook (Webware for Python), Dan Green (DBTools),
 Jay Love, Michael Palmer, Tom Schwaller, Geoffrey Talvola,
diff --git a/docs/main.rst b/docs/main.rst
index 11a7c7e..de88b1e 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -95,7 +95,7 @@ It is even easier to download and install the package in one go using `pip`_::
 Requirements
 ============
 
-DBUtils supports Python_ version 2.7 and Python versions 3.5 to 3.9.
+DBUtils supports Python_ version 2.7 and Python versions 3.5 to 3.10.
 
 The modules in the classic PyGreSQL variant need PyGreSQL_ version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
diff --git a/setup.py b/setup.py
index 2ef3df7..685ef5d 100755
--- a/setup.py
+++ b/setup.py
@@ -37,6 +37,7 @@
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
         'Topic :: Database',
         'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
         'Topic :: Software Development :: Libraries :: Python Modules'
diff --git a/tox.ini b/tox.ini
index a8b505b..f8a8e33 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py{27,35,36,37,38,39}, flake8, manifest, docs, spell
+envlist = py27,py3{5,6,7,8,9,10}, flake8, manifest, docs, spell
 
 [testenv]
 setenv =

From 9101cd140fd4dad3e5b18a59019f5cf8b00be6c8 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 26 Nov 2021 21:00:05 +0100
Subject: [PATCH 33/84] Prepare patch release

---
 .bumpversion.cfg    |  2 +-
 README.md           |  2 +-
 dbutils/__init__.py |  2 +-
 docs/changelog.html | 34 +++++++++++++++++++++-------------
 docs/changelog.rst  | 10 ++++++++++
 setup.py            |  2 +-
 6 files changed, 35 insertions(+), 17 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index e9be47b..9379a28 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 2.0.2
+current_version = 2.0.3
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/README.md b/README.md
index 1ebfde7..efb2289 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 2.0.2 of DBUtils supports Python versions 2.7 and 3.5 to 3.9.
+The current version 2.0.3 of DBUtils supports Python versions 2.7 and 3.5 to 3.10.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there are some breaking changes in version 2.0.**
 
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index 3a26d9b..4ae3298 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '2.0.2'
+__version__ = '2.0.3'
diff --git a/docs/changelog.html b/docs/changelog.html
index 4990b56..879ab73 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -12,6 +12,14 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="section-1">
+<h2>2.0.3</h2>
+<p>DBUtils 2.0.3 was released on November 26, 2021.</p>
+<p>Changes:</p>
+<ul class="simple">
+<li><p>Support Python version 3.10.</p></li>
+</ul>
+</section>
+<section id="section-2">
 <h2>2.0.2</h2>
 <p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
@@ -19,7 +27,7 @@ <h2>2.0.2</h2>
 <li><p>Allow using context managers for pooled connections.</p></li>
 </ul>
 </section>
-<section id="section-2">
+<section id="section-3">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
@@ -27,7 +35,7 @@ <h2>2.0.1</h2>
 <li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="section-3">
+<section id="section-4">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -43,7 +51,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="section-4">
+<section id="section-5">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -54,7 +62,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="section-5">
+<section id="section-6">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -63,12 +71,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="section-6">
+<section id="section-7">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="section-7">
+<section id="section-8">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -82,7 +90,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="section-8">
+<section id="section-9">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -111,7 +119,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="section-9">
+<section id="section-10">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -144,7 +152,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="section-10">
+<section id="section-11">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -153,7 +161,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="section-11">
+<section id="section-12">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -168,7 +176,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="section-12">
+<section id="section-13">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -178,7 +186,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="section-13">
+<section id="section-14">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -192,7 +200,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="section-14">
+<section id="section-15">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index d216900..5b0fd91 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,16 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+2.0.3
+=====
+
+DBUtils 2.0.3 was released on November 26, 2021.
+
+Changes:
+
+* Support Python version 3.10.
+
+
 2.0.2
 =====
 
diff --git a/setup.py b/setup.py
index 685ef5d..f377bdc 100755
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '2.0.2'
+__version__ = '2.0.3'
 
 readme = open('README.md').read()
 

From b120dd8842e2caa3615bc6391ef0a36092072260 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 26 Nov 2021 22:39:06 +0100
Subject: [PATCH 34/84] Cease support for Python 2 and 3.5, minor optimizations

---
 .bumpversion.cfg                      |  2 +-
 .github/workflows/publish_on_pypi.yml |  2 +-
 .github/workflows/test_with_tox.yml   |  2 +-
 README.md                             |  4 +--
 dbutils/__init__.py                   |  2 +-
 dbutils/pooled_db.py                  | 12 +++------
 dbutils/pooled_pg.py                  |  8 ++----
 dbutils/simple_pooled_db.py           | 19 ++++++--------
 dbutils/simple_pooled_pg.py           |  5 +---
 dbutils/steady_db.py                  | 17 ++++--------
 dbutils/steady_pg.py                  | 37 +++++++--------------------
 docs/changelog.html                   | 37 +++++++++++++++++----------
 docs/changelog.rst                    | 13 ++++++++--
 docs/main.de.html                     |  4 +--
 docs/main.de.rst                      |  4 +--
 docs/main.html                        |  4 +--
 docs/main.rst                         |  4 +--
 docs/make.py                          |  2 +-
 setup.py                              | 17 ++++++------
 tests/test_persistent_db.py           | 27 +++++++++----------
 tests/test_persistent_pg.py           | 27 +++++++++----------
 tests/test_pooled_db.py               |  9 +++----
 tests/test_pooled_pg.py               |  5 +---
 tests/test_simple_pooled_db.py        |  5 +---
 tests/test_simple_pooled_pg.py        |  5 +---
 tests/test_steady_db.py               | 16 ++++++------
 tests/test_steady_pg.py               | 12 ++++-----
 tox.ini                               |  2 +-
 28 files changed, 131 insertions(+), 172 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 9379a28..277da9f 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 2.0.3
+current_version = 3.0.0
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
index c9a7589..9d887fa 100644
--- a/.github/workflows/publish_on_pypi.yml
+++ b/.github/workflows/publish_on_pypi.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: ['3.9', '2.7']
+        python: ['3.9']
 
     steps:
     - uses: actions/checkout@v2
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index 904f2db..62246b9 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -7,7 +7,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
+        python: ['3.6', '3.7', '3.8', '3.9', '3.10']
 
     steps:
       - uses: actions/checkout@v2
diff --git a/README.md b/README.md
index efb2289..41b2119 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,8 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 2.0.3 of DBUtils supports Python versions 2.7 and 3.5 to 3.10.
+The current version 3.0.0 of DBUtils supports Python versions 3.6 to 3.10.
 
-**Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there are some breaking changes in version 2.0.**
+**Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there were some breaking changes in version 2.0.**
 
 The DBUtils home page can be found at https://webwareforpython.github.io/DBUtils/
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index 4ae3298..19a4627 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '2.0.3'
+__version__ = '3.0.0'
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index a442bdb..3108131 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -422,8 +422,7 @@ def __getattr__(self, name):
         """Proxy all members of the class."""
         if self._con:
             return getattr(self._con, name)
-        else:
-            raise InvalidConnection
+        raise InvalidConnection
 
     def __del__(self):
         """Delete the pooled connection."""
@@ -455,14 +454,12 @@ def __init__(self, con):
     def __lt__(self, other):
         if self.con._transaction == other.con._transaction:
             return self.shared < other.shared
-        else:
-            return not self.con._transaction
+        return not self.con._transaction
 
     def __le__(self, other):
         if self.con._transaction == other.con._transaction:
             return self.shared <= other.shared
-        else:
-            return not self.con._transaction
+        return not self.con._transaction
 
     def __eq__(self, other):
         return (self.con._transaction == other.con._transaction
@@ -517,8 +514,7 @@ def __getattr__(self, name):
         """Proxy all members of the class."""
         if self._con:
             return getattr(self._con, name)
-        else:
-            raise InvalidConnection
+        raise InvalidConnection
 
     def __del__(self):
         """Delete the pooled connection."""
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index 86c379d..f9776f7 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -112,10 +112,7 @@
 Licensed under the MIT license.
 """
 
-try:
-    from Queue import Queue, Empty, Full
-except ImportError:  # Python 3
-    from queue import Queue, Empty, Full
+from queue import Queue, Empty, Full
 
 from . import __version__
 from .steady_pg import SteadyPgConnection
@@ -289,8 +286,7 @@ def __getattr__(self, name):
         """Proxy all members of the class."""
         if self._con:
             return getattr(self._con, name)
-        else:
-            raise InvalidConnection
+        raise InvalidConnection
 
     def __del__(self):
         """Delete the pooled connection."""
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index b66cf7f..606e188 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -134,14 +134,11 @@ def __init__(self, dbapi, maxconnections, *args, **kwargs):
         if threadsafety == 0:
             raise NotSupportedError(
                 "Database module does not support any level of threading.")
-        elif threadsafety == 1:
+        if threadsafety == 1:
             # If there is no connection level safety, build
             # the pool using the synchronized queue class
             # that implements all the required locking semantics.
-            try:
-                from Queue import Queue
-            except ImportError:  # Python 3
-                from queue import Queue
+            from queue import Queue
             self._queue = Queue(maxconnections)  # create the queue
             self.connection = self._unthreadsafe_get_connection
             self.addConnection = self._unthreadsafe_add_connection
@@ -197,12 +194,12 @@ def _unthreadsafe_return_connection(self, con):
     def _threadsafe_get_connection(self):
         """Get a connection from the pool."""
         with self._lock:
-            next = self._nextConnection
-            con = PooledDBConnection(self, self._connections[next])
-            next += 1
-            if next >= len(self._connections):
-                next = 0
-            self._nextConnection = next
+            next_con = self._nextConnection
+            con = PooledDBConnection(self, self._connections[next_con])
+            next_con += 1
+            if next_con >= len(self._connections):
+                next_con = 0
+            self._nextConnection = next_con
             return con
 
     def _threadsafe_add_connection(self, con):
diff --git a/dbutils/simple_pooled_pg.py b/dbutils/simple_pooled_pg.py
index 844fdd0..b18ad7b 100644
--- a/dbutils/simple_pooled_pg.py
+++ b/dbutils/simple_pooled_pg.py
@@ -118,10 +118,7 @@ def __init__(self, maxconnections, *args, **kwargs):
         # Since there is no connection level safety, we
         # build the pool using the synchronized queue class
         # that implements all the required locking semantics.
-        try:
-            from Queue import Queue
-        except ImportError:  # Python 3
-            from queue import Queue
+        from queue import Queue
         self._queue = Queue(maxconnections)
         # Establish all database connections (it would be better to
         # only establish a part of them now, and the rest on demand).
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index e5a6b03..a744841 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -93,11 +93,6 @@
 
 from . import __version__
 
-try:
-    baseint = (int, long)
-except NameError:  # Python 3
-    baseint = int
-
 
 class SteadyDBError(Exception):
     """General SteadyDB error."""
@@ -173,10 +168,10 @@ def __init__(
             except AttributeError:
                 self._threadsafety = None
         if not callable(self._creator):
-            raise TypeError("%r is not a connection provider." % (creator,))
+            raise TypeError(f"{creator!r} is not a connection provider.")
         if maxusage is None:
             maxusage = 0
-        if not isinstance(maxusage, baseint):
+        if not isinstance(maxusage, int):
             raise TypeError("'maxusage' must be an integer value.")
         self._maxusage = maxusage
         self._setsession_sql = setsession
@@ -531,7 +526,7 @@ def __init__(self, con, *args, **kwargs):
         try:
             self._cursor = con._cursor(*args, **kwargs)
         except AttributeError:
-            raise TypeError("%r is not a SteadyDBConnection." % (con,))
+            raise TypeError(f"{con!r} is not a SteadyDBConnection.")
         self._closed = False
 
     def __enter__(self):
@@ -688,10 +683,8 @@ def __getattr__(self, name):
             if name.startswith(('execute', 'call')):
                 # make execution methods "tough"
                 return self._get_tough_method(name)
-            else:
-                return getattr(self._cursor, name)
-        else:
-            raise InvalidCursor
+            return getattr(self._cursor, name)
+        raise InvalidCursor
 
     def __del__(self):
         """Delete the steady cursor."""
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index 3a500ee..c1a6b71 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -73,11 +73,6 @@
 
 from . import __version__
 
-try:
-    baseint = (int, long)
-except NameError:  # Python 3
-    baseint = int
-
 
 class SteadyPgError(Exception):
     """General SteadyPg error."""
@@ -122,7 +117,7 @@ def __init__(
         # proper initialization of the connection
         if maxusage is None:
             maxusage = 0
-        if not isinstance(maxusage, baseint):
+        if not isinstance(maxusage, int):
             raise TypeError("'maxusage' must be an integer value.")
         self._maxusage = maxusage
         self._setsession_sql = setsession
@@ -229,10 +224,7 @@ def begin(self, sql=None):
             return self._con.query(sql or 'begin')
         else:
             # use existing method if available
-            if sql:
-                return begin(sql=sql)
-            else:
-                return begin()
+            return begin(sql=sql) if sql else begin()
 
     def end(self, sql=None):
         """Commit the current transaction."""
@@ -242,10 +234,7 @@ def end(self, sql=None):
         except AttributeError:
             return self._con.query(sql or 'end')
         else:
-            if sql:
-                return end(sql=sql)
-            else:
-                return end()
+            return end(sql=sql) if sql else end()
 
     def commit(self, sql=None):
         """Commit the current transaction."""
@@ -255,10 +244,7 @@ def commit(self, sql=None):
         except AttributeError:
             return self._con.query(sql or 'commit')
         else:
-            if sql:
-                return commit(sql=sql)
-            else:
-                return commit()
+            return commit(sql=sql) if sql else commit()
 
     def rollback(self, sql=None):
         """Rollback the current transaction."""
@@ -268,10 +254,7 @@ def rollback(self, sql=None):
         except AttributeError:
             return self._con.query(sql or 'rollback')
         else:
-            if sql:
-                return rollback(sql=sql)
-            else:
-                return rollback()
+            return rollback(sql=sql) if sql else rollback()
 
     def _get_tough_method(self, method):
         """Return a "tough" version of a connection class method.
@@ -297,11 +280,10 @@ def tough_method(*args, **kwargs):
                 if transaction:  # inside a transaction
                     self._transaction = False
                     raise  # propagate the error
-                elif self._con.db.status:  # if it was not a connection problem
+                if self._con.db.status:  # if it was not a connection problem
                     raise  # then propagate the error
-                else:  # otherwise
-                    self.reset()  # reset the connection
-                    result = method(*args, **kwargs)  # and try one more time
+                self.reset()  # reset the connection
+                result = method(*args, **kwargs)  # and try one more time
             self._usage += 1
             return result
         return tough_method
@@ -317,8 +299,7 @@ def __getattr__(self, name):
                     or name.startswith('get_')):
                 attr = self._get_tough_method(attr)
             return attr
-        else:
-            raise InvalidConnection
+        raise InvalidConnection
 
     def __del__(self):
         """Delete the steady connection."""
diff --git a/docs/changelog.html b/docs/changelog.html
index 879ab73..ea10c93 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -12,6 +12,15 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="section-1">
+<h2>3.0.0</h2>
+<p>DBUtils 2.0.3 was released on November 26, 2021.</p>
+<p>It is intended to be used with Python versions 3.6 to 3.10.</p>
+<p>Changes:</p>
+<ul class="simple">
+<li><p>Cease support for Python 2 and 3.5, minor optimizations.</p></li>
+</ul>
+</section>
+<section id="section-2">
 <h2>2.0.3</h2>
 <p>DBUtils 2.0.3 was released on November 26, 2021.</p>
 <p>Changes:</p>
@@ -19,7 +28,7 @@ <h2>2.0.3</h2>
 <li><p>Support Python version 3.10.</p></li>
 </ul>
 </section>
-<section id="section-2">
+<section id="section-3">
 <h2>2.0.2</h2>
 <p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
@@ -27,7 +36,7 @@ <h2>2.0.2</h2>
 <li><p>Allow using context managers for pooled connections.</p></li>
 </ul>
 </section>
-<section id="section-3">
+<section id="section-4">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
@@ -35,7 +44,7 @@ <h2>2.0.1</h2>
 <li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="section-4">
+<section id="section-5">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -51,7 +60,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="section-5">
+<section id="section-6">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -62,7 +71,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="section-6">
+<section id="section-7">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -71,12 +80,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="section-7">
+<section id="section-8">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="section-8">
+<section id="section-9">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -90,7 +99,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="section-9">
+<section id="section-10">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -119,7 +128,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="section-10">
+<section id="section-11">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -152,7 +161,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="section-11">
+<section id="section-12">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -161,7 +170,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="section-12">
+<section id="section-13">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -176,7 +185,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="section-13">
+<section id="section-14">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -186,7 +195,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="section-14">
+<section id="section-15">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -200,7 +209,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="section-15">
+<section id="section-16">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 5b0fd91..495c6e1 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,17 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+3.0.0
+=====
+
+DBUtils 2.0.3 was released on November 26, 2021.
+
+It is intended to be used with Python versions 3.6 to 3.10.
+
+Changes:
+
+* Cease support for Python 2 and 3.5, minor optimizations.
+
 2.0.3
 =====
 
@@ -10,7 +21,6 @@ Changes:
 
 * Support Python version 3.10.
 
-
 2.0.2
 =====
 
@@ -20,7 +30,6 @@ Changes:
 
 * Allow using context managers for pooled connections.
 
-
 2.0.1
 =====
 
diff --git a/docs/main.de.html b/docs/main.de.html
index 5f72110..a285997 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -12,7 +12,7 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">2.0.2</dd>
+<dd class="version">3.0.0</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
@@ -136,7 +136,7 @@ <h3>Installation</h3>
 </section>
 <section id="anforderungen">
 <h2>Anforderungen</h2>
-<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 2.7 und 3.5 bis 3.10.</p>
+<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 3.6 bis 3.10.</p>
 <p>Die Module in der Variante für klassisches PyGreSQL benötigen <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a>
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
 für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten,
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 4184e76..ff23e5d 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -1,7 +1,7 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 2.0.2
+:Version: 3.0.0
 :Translations: English_ | German
 
 .. _English: main.html
@@ -98,7 +98,7 @@ herunterzuladen und zu installieren::
 Anforderungen
 =============
 
-DBUtils unterstützt die Python_ Versionen 2.7 und 3.5 bis 3.10.
+DBUtils unterstützt die Python_ Versionen 3.6 bis 3.10.
 
 Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL_
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
diff --git a/docs/main.html b/docs/main.html
index 964126a..2516340 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -12,7 +12,7 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">2.0.2</dd>
+<dd class="version">3.0.0</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
@@ -133,7 +133,7 @@ <h3>Installation</h3>
 </section>
 <section id="requirements">
 <h2>Requirements</h2>
-<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> version 2.7 and Python versions 3.5 to 3.10.</p>
+<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> versions 3.6 to 3.10.</p>
 <p>The modules in the classic PyGreSQL variant need <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
 any Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> compliant database interface module.</p>
diff --git a/docs/main.rst b/docs/main.rst
index de88b1e..d5efa8e 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -1,7 +1,7 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 2.0.2
+:Version: 3.0.0
 :Translations: English | German_
 
 .. _German: main.de.html
@@ -95,7 +95,7 @@ It is even easier to download and install the package in one go using `pip`_::
 Requirements
 ============
 
-DBUtils supports Python_ version 2.7 and Python versions 3.5 to 3.10.
+DBUtils supports Python_ versions 3.6 to 3.10.
 
 The modules in the classic PyGreSQL variant need PyGreSQL_ version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
diff --git a/docs/make.py b/docs/make.py
index c2a0494..d7b2cad 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3.8
+#!/usr/bin/python3.9
 
 """Build HMTL from reST files."""
 
diff --git a/setup.py b/setup.py
index f377bdc..8fbcc11 100755
--- a/setup.py
+++ b/setup.py
@@ -8,12 +8,13 @@
 from sys import version_info
 
 py_version = version_info[:2]
-if py_version != (2, 7) and not (3, 5) <= py_version < (4, 0):
-    raise ImportError('Python %d.%d is not supported by DBUtils.' % py_version)
+if not (3, 6) <= py_version < (4, 0):
+    raise ImportError(
+        'Python {}.{} is not supported by DBUtils.'.format(*py_version))
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '2.0.3'
+__version__ = '3.0.0'
 
 readme = open('README.md').read()
 
@@ -27,20 +28,18 @@
         'Development Status :: 5 - Production/Stable',
         'Environment :: Web Environment',
         'Intended Audience :: Developers',
+        'Topic :: Database',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+        'Topic :: Software Development :: Libraries :: Python Modules',
         'License :: OSI Approved :: MIT License',
         'Programming Language :: Python',
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
-        'Topic :: Database',
-        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
-        'Topic :: Software Development :: Libraries :: Python Modules'
+        'Operating System :: OS Independent',
     ],
     author='Christoph Zwerschke',
     author_email='cito@online.de',
diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index ed5bce6..6f27108 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -63,10 +63,7 @@ def test_connection(self):
     def test_threads(self):
         num_threads = 3
         persist = PersistentDB(dbapi, closeable=True)
-        try:
-            from queue import Queue, Empty
-        except ImportError:  # Python 2
-            from Queue import Queue, Empty
+        from queue import Queue, Empty
         query_queue, result_queue = [], []
         for i in range(num_threads):
             query_queue.append(Queue(1))
@@ -98,7 +95,7 @@ def run_queries(i):
                         cursor.execute(q)
                         r = cursor.fetchone()
                         cursor.close()
-                r = '%d(%d): %s' % (i, db._usage, r)
+                r = f'{i}({db._usage}): {r}'
                 try:
                     result_queue[i].put(r, 1, 1)
                 except TypeError:
@@ -121,17 +118,17 @@ def run_queries(i):
                 r = result_queue[i].get(1, 1)
             except TypeError:
                 r = result_queue[i].get(1)
-            self.assertEqual(r, '%d(0): ok - thread alive' % i)
+            self.assertEqual(r, f'{i}(0): ok - thread alive')
             self.assertTrue(threads[i].is_alive())
         for i in range(num_threads):
             for j in range(i + 1):
                 try:
-                    query_queue[i].put('select test%d' % j, 1, 1)
+                    query_queue[i].put(f'select test{j}', 1, 1)
                     r = result_queue[i].get(1, 1)
                 except TypeError:
-                    query_queue[i].put('select test%d' % j, 1)
+                    query_queue[i].put(f'select test{j}', 1)
                     r = result_queue[i].get(1)
-                self.assertEqual(r, '%d(%d): test%d' % (i, j + 1, j))
+                self.assertEqual(r, f'{i}({j + 1}): test{j}')
         try:
             query_queue[1].put('select test4', 1, 1)
         except TypeError:
@@ -150,12 +147,12 @@ def run_queries(i):
         self.assertEqual(r, '1(3): ok - connection closed')
         for j in range(2):
             try:
-                query_queue[1].put('select test%d' % j, 1, 1)
+                query_queue[1].put(f'select test{j}', 1, 1)
                 r = result_queue[1].get(1, 1)
             except TypeError:
-                query_queue[1].put('select test%d' % j, 1)
+                query_queue[1].put(f'select test{j}', 1)
                 r = result_queue[1].get(1)
-            self.assertEqual(r, '1(%d): test%d' % (j + 1, j))
+            self.assertEqual(r, f'1({j + 1}): test{j}')
         for i in range(num_threads):
             self.assertTrue(threads[i].is_alive())
             try:
@@ -167,7 +164,7 @@ def run_queries(i):
                 r = result_queue[i].get(1, 1)
             except TypeError:
                 r = result_queue[i].get(1)
-            self.assertEqual(r, '%d(%d): ok - thread alive' % (i, i + 1))
+            self.assertEqual(r, f'{i}({i + 1}): ok - thread alive')
             self.assertTrue(threads[i].is_alive())
         for i in range(num_threads):
             try:
@@ -181,10 +178,10 @@ def test_maxusage(self):
         self.assertEqual(db._maxusage, 20)
         for i in range(100):
             cursor = db.cursor()
-            cursor.execute('select test%d' % i)
+            cursor.execute(f'select test{i}')
             r = cursor.fetchone()
             cursor.close()
-            self.assertEqual(r, 'test%d' % i)
+            self.assertEqual(r, f'test{i}')
             self.assertTrue(db._con.valid)
             j = i % 20 + 1
             self.assertEqual(db._usage, j)
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index 78c85ca..3c8ce83 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -43,10 +43,7 @@ def test_close(self):
     def test_threads(self):
         num_threads = 3
         persist = PersistentPg()
-        try:
-            from queue import Queue, Empty
-        except ImportError:  # Python 2
-            from Queue import Queue, Empty
+        from queue import Queue, Empty
         query_queue, result_queue = [], []
         for i in range(num_threads):
             query_queue.append(Queue(1))
@@ -75,7 +72,7 @@ def run_queries(i):
                         r = 'ok - connection closed'
                     else:
                         r = db.query(q)
-                r = '%d(%d): %s' % (i, db._usage, r)
+                r = f'{i}({db._usage}): {r}'
                 try:
                     result_queue[i].put(r, 1, 1)
                 except TypeError:
@@ -98,17 +95,17 @@ def run_queries(i):
                 r = result_queue[i].get(1, 1)
             except TypeError:
                 r = result_queue[i].get(1)
-            self.assertEqual(r, '%d(0): ok - thread alive' % i)
+            self.assertEqual(r, f'{i}(0): ok - thread alive')
             self.assertTrue(threads[i].is_alive())
         for i in range(num_threads):
             for j in range(i + 1):
                 try:
-                    query_queue[i].put('select test%d' % j, 1, 1)
+                    query_queue[i].put(f'select test{j}', 1, 1)
                     r = result_queue[i].get(1, 1)
                 except TypeError:
-                    query_queue[i].put('select test%d' % j, 1)
+                    query_queue[i].put(f'select test{j}', 1)
                     r = result_queue[i].get(1)
-                self.assertEqual(r, '%d(%d): test%d' % (i, j + 1, j))
+                self.assertEqual(r, f'{i}({j + 1}): test{j}')
         try:
             query_queue[1].put('select test4', 1, 1)
             r = result_queue[1].get(1, 1)
@@ -125,12 +122,12 @@ def run_queries(i):
         self.assertEqual(r, '1(3): ok - connection closed')
         for j in range(2):
             try:
-                query_queue[1].put('select test%d' % j, 1, 1)
+                query_queue[1].put(f'select test{j}', 1, 1)
                 r = result_queue[1].get(1, 1)
             except TypeError:
-                query_queue[1].put('select test%d' % j, 1)
+                query_queue[1].put(f'select test{j}', 1)
                 r = result_queue[1].get(1)
-            self.assertEqual(r, '1(%d): test%d' % (j + 1, j))
+            self.assertEqual(r, f'1({j + 1}): test{j}')
         for i in range(num_threads):
             self.assertTrue(threads[i].is_alive())
             try:
@@ -142,7 +139,7 @@ def run_queries(i):
                 r = result_queue[i].get(1, 1)
             except TypeError:
                 r = result_queue[i].get(1)
-            self.assertEqual(r, '%d(%d): ok - thread alive' % (i, i + 1))
+            self.assertEqual(r, f'{i}({i + 1}): ok - thread alive')
             self.assertTrue(threads[i].is_alive())
         for i in range(num_threads):
             try:
@@ -155,8 +152,8 @@ def test_maxusage(self):
         db = persist.connection()
         self.assertEqual(db._maxusage, 20)
         for i in range(100):
-            r = db.query('select test%d' % i)
-            self.assertEqual(r, 'test%d' % i)
+            r = db.query(f'select test{i}')
+            self.assertEqual(r, f'test{i}')
             self.assertTrue(db.db.status)
             j = i % 20 + 1
             self.assertEqual(db._usage, j)
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 80a8b45..3beb2d4 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -868,9 +868,9 @@ def test_maxusage(self):
                 for i in range(20):
                     cursor = db.cursor()
                     self.assertEqual(db._con._con.open_cursors, 1)
-                    cursor.execute('select test%i' % i)
+                    cursor.execute(f'select test{i}')
                     r = cursor.fetchone()
-                    self.assertEqual(r, 'test%i' % i)
+                    self.assertEqual(r, f'test{i}')
                     cursor.close()
                     self.assertEqual(db._con._con.open_cursors, 0)
                     if maxusage:
@@ -976,10 +976,7 @@ def test_tnree_threads_two_connections(self):
         for threadsafety in (1, 2):
             dbapi.threadsafety = threadsafety
             pool = PooledDB(dbapi, 2, 2, 0, 2, True)
-            try:
-                from queue import Queue, Empty
-            except ImportError:  # Python 2
-                from Queue import Queue, Empty
+            from queue import Queue, Empty
             queue = Queue(3)
 
             def connection():
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index bf0e314..ec088a5 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -225,10 +225,7 @@ def test_one_thread_two_connections(self):
 
     def test_three_threads_two_connections(self):
         pool = PooledPg(2, 2, 2, True)
-        try:
-            from queue import Queue, Empty
-        except ImportError:  # Python 2
-            from Queue import Queue, Empty
+        from queue import Queue, Empty
         queue = Queue(3)
 
         def connection():
diff --git a/tests/test_simple_pooled_db.py b/tests/test_simple_pooled_db.py
index 564fda3..4cf82ae 100644
--- a/tests/test_simple_pooled_db.py
+++ b/tests/test_simple_pooled_db.py
@@ -103,10 +103,7 @@ def test_two_connections(self):
 
     def test_threadsafety_1(self):
         db_pool = self.my_db_pool(1, 2)
-        try:
-            from queue import Queue, Empty
-        except ImportError:  # Python 2
-            from Queue import Queue, Empty
+        from queue import Queue, Empty
         queue = Queue(3)
 
         def connection():
diff --git a/tests/test_simple_pooled_pg.py b/tests/test_simple_pooled_pg.py
index 7065d58..d3026cc 100644
--- a/tests/test_simple_pooled_pg.py
+++ b/tests/test_simple_pooled_pg.py
@@ -83,10 +83,7 @@ def test_two_connections(self):
 
     def test_threads(self):
         db_pool = self.my_db_pool(2)
-        try:
-            from queue import Queue, Empty
-        except ImportError:  # Python 2
-            from Queue import Queue, Empty
+        from queue import Queue, Empty
         queue = Queue(3)
 
         def connection():
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index e32a711..d1a2628 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -64,8 +64,8 @@ def test_mocked_connection(self):
         for i in range(3):
             self.assertEqual(db.num_uses, i)
             self.assertEqual(db.num_queries, i)
-            cursor.execute('select test%d' % i)
-            self.assertEqual(cursor.fetchone(), 'test%d' % i)
+            cursor.execute(f'select test{i}')
+            self.assertEqual(cursor.fetchone(), f'test{i}')
         self.assertTrue(cursor.valid)
         self.assertEqual(db.open_cursors, 1)
         for i in range(4):
@@ -173,8 +173,8 @@ def test_connection(self):
             self.assertEqual(db._usage, i)
             self.assertEqual(db._con.num_uses, i)
             self.assertEqual(db._con.num_queries, i)
-            cursor.execute('select test%d' % i)
-            self.assertEqual(cursor.fetchone(), 'test%d' % i)
+            cursor.execute(f'select test{i}')
+            self.assertEqual(cursor.fetchone(), f'test{i}')
         self.assertTrue(cursor.valid)
         self.assertEqual(db._con.open_cursors, 1)
         for i in range(4):
@@ -292,9 +292,9 @@ def test_connection_maxusage(self):
         db = SteadyDBconnect(dbapi, 10)
         cursor = db.cursor()
         for i in range(100):
-            cursor.execute('select test%d' % i)
+            cursor.execute(f'select test{i}')
             r = cursor.fetchone()
-            self.assertEqual(r, 'test%d' % i)
+            self.assertEqual(r, f'test{i}')
             self.assertTrue(db._con.valid)
             j = i % 10 + 1
             self.assertEqual(db._usage, j)
@@ -315,9 +315,9 @@ def test_connection_maxusage(self):
         for i in range(10):
             if i == 7:
                 db._con.valid = cursor._cursor.valid = False
-            cursor.execute('select test%d' % i)
+            cursor.execute(f'select test{i}')
             r = cursor.fetchone()
-            self.assertEqual(r, 'test%d' % i)
+            self.assertEqual(r, f'test{i}')
             j = i % 7 + 1
             self.assertEqual(db._usage, j)
             self.assertEqual(db._con.num_uses, j)
diff --git a/tests/test_steady_pg.py b/tests/test_steady_pg.py
index f798f43..2b8a1ad 100644
--- a/tests/test_steady_pg.py
+++ b/tests/test_steady_pg.py
@@ -58,7 +58,7 @@ def test_mocked_connection(self):
         for i in range(3):
             self.assertEqual(db.num_queries, i)
             self.assertEqual(
-                db.query('select test%d' % i), 'test%d' % i)
+                db.query(f'select test{i}'), f'test{i}')
         self.assertTrue(db.db.status)
         db.reopen()
         self.assertTrue(db.db.status)
@@ -135,7 +135,7 @@ def test_connection(self):
             self.assertEqual(db._usage, i)
             self.assertEqual(db.num_queries, i)
             self.assertEqual(
-                db.query('select test%d' % i), 'test%d' % i)
+                db.query(f'select test{i}'), f'test{i}')
         self.assertTrue(db.db.status)
         self.assertEqual(db.get_tables(), 'test')
         self.assertTrue(db.db.status)
@@ -200,8 +200,8 @@ def test_connection_context_handler(self):
     def test_connection_maxusage(self):
         db = SteadyPgConnection(10)
         for i in range(100):
-            r = db.query('select test%d' % i)
-            self.assertEqual(r, 'test%d' % i)
+            r = db.query(f'select test{i}')
+            self.assertEqual(r, f'test{i}')
             self.assertTrue(db.db.status)
             j = i % 10 + 1
             self.assertEqual(db._usage, j)
@@ -220,8 +220,8 @@ def test_connection_maxusage(self):
         for i in range(10):
             if i == 7:
                 db.db.status = False
-            r = db.query('select test%d' % i)
-            self.assertEqual(r, 'test%d' % i)
+            r = db.query(f'select test{i}')
+            self.assertEqual(r, f'test{i}')
             j = i % 7 + 1
             self.assertEqual(db._usage, j)
             self.assertEqual(db.num_queries, j)
diff --git a/tox.ini b/tox.ini
index f8a8e33..404b6c5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py27,py3{5,6,7,8,9,10}, flake8, manifest, docs, spell
+envlist = py3{6,7,8,9,10}, flake8, manifest, docs, spell
 
 [testenv]
 setenv =

From f818201267cf530c1a4b7a1b713143834ace2c23 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 27 Nov 2021 12:10:42 +0100
Subject: [PATCH 35/84] Fix version number in the changelog

---
 docs/changelog.html | 2 +-
 docs/changelog.rst  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/changelog.html b/docs/changelog.html
index ea10c93..5be9854 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -13,7 +13,7 @@ <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="section-1">
 <h2>3.0.0</h2>
-<p>DBUtils 2.0.3 was released on November 26, 2021.</p>
+<p>DBUtils 3.0.0 was released on November 26, 2021.</p>
 <p>It is intended to be used with Python versions 3.6 to 3.10.</p>
 <p>Changes:</p>
 <ul class="simple">
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 495c6e1..b8a0a5a 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,7 +4,7 @@
 3.0.0
 =====
 
-DBUtils 2.0.3 was released on November 26, 2021.
+DBUtils 3.0.0 was released on November 26, 2021.
 
 It is intended to be used with Python versions 3.6 to 3.10.
 

From d94d869368696bf9118f85330d5d910b75e30675 Mon Sep 17 00:00:00 2001
From: qizidog <45590672+qizidog@users.noreply.github.com>
Date: Sun, 5 Dec 2021 02:42:42 +0800
Subject: [PATCH 36/84] Fix a tutorial bug (#35)

db.cursor is a method, not a property.
---
 docs/main.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/main.html b/docs/main.html
index 2516340..f0109a1 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -384,7 +384,7 @@ <h3>PooledDB (pooled_db)</h3>
 db.close()  # or del db</pre>
 <p>You can also use context managers for simpler code:</p>
 <pre class="literal-block">with pool.connection() as db:
-    with db.cursor as cur:
+    with db.cursor() as cur:
         cur.execute(...)
         res = cur.fetchone()</pre>
 <p>Note that you need to explicitly start transactions by calling the

From 07b340d656eb0d68a186f70cf4e6711329f8a7e4 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 4 Dec 2021 19:48:20 +0100
Subject: [PATCH 37/84] Fix source and German version of tutorial (#35)

---
 docs/main.de.html | 2 +-
 docs/main.de.rst  | 2 +-
 docs/main.rst     | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/main.de.html b/docs/main.de.html
index a285997..2df8d6d 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -422,7 +422,7 @@ <h3>PooledDB (pooled_db)</h3>
 db.close()  # oder del db</pre>
 <p>Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen:</p>
 <pre class="literal-block">with pool.connection() as db:
-    with db.cursor as cur:
+    with db.cursor() as cur:
         cur.execute(...)
         res = cur.fetchone()</pre>
 <p>Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
diff --git a/docs/main.de.rst b/docs/main.de.rst
index ff23e5d..0c7077a 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -446,7 +446,7 @@ sie gebraucht werden, etwa so::
 Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen::
 
   with pool.connection() as db:
-      with db.cursor as cur:
+      with db.cursor() as cur:
           cur.execute(...)
           res = cur.fetchone()
 
diff --git a/docs/main.rst b/docs/main.rst
index d5efa8e..81c6427 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -408,7 +408,7 @@ object stays alive as long as you are using it, like that::
 You can also use context managers for simpler code::
 
   with pool.connection() as db:
-      with db.cursor as cur:
+      with db.cursor() as cur:
           cur.execute(...)
           res = cur.fetchone()
 

From c5f639a35c2f3c62e80c9463c06f9550ede569ad Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Wed, 22 Dec 2021 17:33:57 +0100
Subject: [PATCH 38/84] Add InterfaceError to the default failures list (#36)

---
 dbutils/persistent_db.py |  6 ++++--
 dbutils/pooled_db.py     |  6 ++++--
 dbutils/steady_db.py     |  9 +++++++--
 docs/changelog.html      | 37 ++++++++++++++++++++++---------------
 docs/changelog.rst       | 10 ++++++++++
 docs/main.de.html        | 10 ++++++----
 docs/main.de.rst         | 10 ++++++----
 docs/main.html           |  6 ++++--
 docs/main.rst            |  6 ++++--
 tests/mock_db.py         |  4 ++++
 tests/test_steady_db.py  |  8 ++++++--
 11 files changed, 77 insertions(+), 35 deletions(-)

diff --git a/dbutils/persistent_db.py b/dbutils/persistent_db.py
index 8ebf43d..6873905 100644
--- a/dbutils/persistent_db.py
+++ b/dbutils/persistent_db.py
@@ -42,7 +42,8 @@
         prepare the session, e.g. ["set datestyle to german", ...].
     failures: an optional exception class or a tuple of exception classes
         for which the connection failover mechanism shall be applied,
-        if the default (OperationalError, InternalError) is not adequate
+        if the default (OperationalError, InterfaceError, InternalError)
+        is not adequate for the used database module
     ping: an optional flag controlling when connections are checked
         with the ping() method if such a method is available
         (0 = None = never, 1 = default = whenever it is requested,
@@ -155,7 +156,8 @@ def __init__(
             the session, e.g. ["set datestyle to ...", "set time zone ..."]
         failures: an optional exception class or a tuple of exception classes
             for which the connection failover mechanism shall be applied,
-            if the default (OperationalError, InternalError) is not adequate
+            if the default (OperationalError, InterfaceError, InternalError)
+            is not adequate for the used database module
         ping: determines when the connection should be checked with ping()
             (0 = None = never, 1 = default = whenever it is requested,
             2 = when a cursor is created, 4 = when a query is executed,
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 3108131..1b47f2b 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -57,7 +57,8 @@
         the default value True always issues a rollback for safety's sake)
     failures: an optional exception class or a tuple of exception classes
         for which the connection failover mechanism shall be applied,
-        if the default (OperationalError, InternalError) is not adequate
+        if the default (OperationalError, InterfaceError, InternalError)
+        is not adequate for the used database module
     ping: an optional flag controlling when connections are checked
         with the ping() method if such a method is available
         (0 = None = never, 1 = default = whenever fetched from the pool,
@@ -210,7 +211,8 @@ def __init__(
             True to always issue a rollback for safety's sake)
         failures: an optional exception class or a tuple of exception classes
             for which the connection failover mechanism shall be applied,
-            if the default (OperationalError, InternalError) is not adequate
+            if the default (OperationalError, InterfaceError, InternalError)
+            is not adequate for the used database module
         ping: determines when the connection should be checked with ping()
             (0 = None = never, 1 = default = whenever fetched from the pool,
             2 = when a cursor is created, 4 = when a query is executed,
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index a744841..8df2acb 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -117,7 +117,8 @@ def connect(
         the session, e.g. ["set datestyle to german", "set time zone mez"]
     failures: an optional exception class or a tuple of exception classes
         for which the failover mechanism shall be applied, if the default
-        (OperationalError, InternalError) is not adequate
+        (OperationalError, InternalError, Interface) is not adequate
+        for the used database module
     ping: determines when the connection should be checked with ping()
         (0 = None = never, 1 = default = when _ping_check() is called,
         2 = whenever a cursor is created, 4 = when a query is executed,
@@ -258,16 +259,20 @@ def _create(self):
                 try:
                     self._failures = (
                         self._dbapi.OperationalError,
+                        self._dbapi.InterfaceError,
                         self._dbapi.InternalError)
                 except AttributeError:
                     try:
                         self._failures = (
                             self._creator.OperationalError,
+                            self._creator.InterfaceError,
                             self._creator.InternalError)
                     except AttributeError:
                         try:
                             self._failures = (
-                                con.OperationalError, con.InternalError)
+                                con.OperationalError,
+                                con.InterfaceError,
+                                con.InternalError)
                         except AttributeError:
                             raise AttributeError(
                                 "Could not determine failure exceptions"
diff --git a/docs/changelog.html b/docs/changelog.html
index 5be9854..13eda8b 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -12,6 +12,13 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="section-1">
+<h2>3.0.1</h2>
+<p>DBUtils 3.0.1 was released on December 22, 2021.</p>
+<p>It includes <span class="docutils literal">InterfaceError</span> to the default list of exceptions
+for which the connection failover mechanism is applied.
+You can override this with the <span class="docutils literal">failures</span> parameter.</p>
+</section>
+<section id="section-2">
 <h2>3.0.0</h2>
 <p>DBUtils 3.0.0 was released on November 26, 2021.</p>
 <p>It is intended to be used with Python versions 3.6 to 3.10.</p>
@@ -20,7 +27,7 @@ <h2>3.0.0</h2>
 <li><p>Cease support for Python 2 and 3.5, minor optimizations.</p></li>
 </ul>
 </section>
-<section id="section-2">
+<section id="section-3">
 <h2>2.0.3</h2>
 <p>DBUtils 2.0.3 was released on November 26, 2021.</p>
 <p>Changes:</p>
@@ -28,7 +35,7 @@ <h2>2.0.3</h2>
 <li><p>Support Python version 3.10.</p></li>
 </ul>
 </section>
-<section id="section-3">
+<section id="section-4">
 <h2>2.0.2</h2>
 <p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
@@ -36,7 +43,7 @@ <h2>2.0.2</h2>
 <li><p>Allow using context managers for pooled connections.</p></li>
 </ul>
 </section>
-<section id="section-4">
+<section id="section-5">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
@@ -44,7 +51,7 @@ <h2>2.0.1</h2>
 <li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="section-5">
+<section id="section-6">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -60,7 +67,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="section-6">
+<section id="section-7">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -71,7 +78,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="section-7">
+<section id="section-8">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -80,12 +87,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="section-8">
+<section id="section-9">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="section-9">
+<section id="section-10">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -99,7 +106,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="section-10">
+<section id="section-11">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -128,7 +135,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="section-11">
+<section id="section-12">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -161,7 +168,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="section-12">
+<section id="section-13">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -170,7 +177,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="section-13">
+<section id="section-14">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -185,7 +192,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="section-14">
+<section id="section-15">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -195,7 +202,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="section-15">
+<section id="section-16">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -209,7 +216,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="section-16">
+<section id="section-17">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index b8a0a5a..85a2f19 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,16 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+
+3.0.1
+=====
+
+DBUtils 3.0.1 was released on December 22, 2021.
+
+It includes ``InterfaceError`` to the default list of exceptions
+for which the connection failover mechanism is applied.
+You can override this with the ``failures`` parameter.
+
 3.0.0
 =====
 
diff --git a/docs/main.de.html b/docs/main.de.html
index 2df8d6d..4728326 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -282,9 +282,10 @@ <h3>PersistentDB (persistent_db)</h3>
 </li>
 <li><p><span class="docutils literal">setsession</span>: eine optionale Liste von SQL-Befehlen zur Initialisierung
 der Datenbanksitzung, z.B. <span class="docutils literal">[&quot;set datestyle to german&quot;, <span class="pre">...]</span></span></p></li>
-<li><p><span class="docutils literal">failures</span>: eine optionale Exception-Klasse oder ein Tupel von Exceptions
+<li><p><span class="docutils literal">failures</span>: eine optionale Exception-Klasse oder ein Tupel von Exceptions,
 bei denen die Ausfallsicherung zum Tragen kommen soll, falls die Vorgabe
-(OperationalError, InternalError) nicht geeignet sein sollte</p></li>
+(OperationalError, InterfaceError, InternalError) für das verwendete
+Datenbankadapter-Modul nicht geeignet sein sollte</p></li>
 <li><p><span class="docutils literal">ping</span>: mit diesem Parameter kann eingestellt werden, wann Verbindungen
 mit der <span class="docutils literal">ping()</span>-Methode geprüft werden, falls eine solche vorhanden ist
 (<span class="docutils literal">0</span> = <span class="docutils literal">None</span> = nie, <span class="docutils literal">1</span> = Standardwert = immer wenn neu angefragt,
@@ -371,9 +372,10 @@ <h3>PooledDB (pooled_db)</h3>
 in den Verbindungspool zurückgegeben werden (<span class="docutils literal">False</span> oder <span class="docutils literal">None</span>
 um mit <span class="docutils literal">begin()</span> gestartete Transaktionen zurückzurollen, der Standardwert
 <span class="docutils literal">True</span> rollt sicherheitshalber mögliche Transaktionen immer zurück)</p></li>
-<li><p><span class="docutils literal">failures</span>: eine optionale Exception-Klasse oder ein Tupel von Exceptions
+<li><p><span class="docutils literal">failures</span>: eine optionale Exception-Klasse oder ein Tupel von Exceptions,
 bei denen die Ausfallsicherung zum Tragen kommen soll, falls die Vorgabe
-(OperationalError, InternalError) nicht geeignet sein sollte</p></li>
+(OperationalError, InterfaceError, InternalError) für das verwendete
+Datenbankadapter-Modul nicht geeignet sein sollte</p></li>
 <li><p><span class="docutils literal">ping</span>: mit diesem Parameter kann eingestellt werden, wann Verbindungen
 mit der <span class="docutils literal">ping()</span>-Methode geprüft werden, falls eine solche vorhanden ist
 (<span class="docutils literal">0</span> = <span class="docutils literal">None</span> = nie, <span class="docutils literal">1</span> = Standardwert = immer wenn neu angefragt,
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 0c7077a..3f6d8a3 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -269,9 +269,10 @@ Parameter angeben müssen:
 * ``setsession``: eine optionale Liste von SQL-Befehlen zur Initialisierung
   der Datenbanksitzung, z.B. ``["set datestyle to german", ...]``
 
-* ``failures``: eine optionale Exception-Klasse oder ein Tupel von Exceptions
+* ``failures``: eine optionale Exception-Klasse oder ein Tupel von Exceptions,
   bei denen die Ausfallsicherung zum Tragen kommen soll, falls die Vorgabe
-  (OperationalError, InternalError) nicht geeignet sein sollte
+  (OperationalError, InterfaceError, InternalError) für das verwendete
+  Datenbankadapter-Modul nicht geeignet sein sollte
 
 * ``ping``: mit diesem Parameter kann eingestellt werden, wann Verbindungen
   mit der ``ping()``-Methode geprüft werden, falls eine solche vorhanden ist
@@ -378,9 +379,10 @@ Parameter angeben müssen:
   um mit ``begin()`` gestartete Transaktionen zurückzurollen, der Standardwert
   ``True`` rollt sicherheitshalber mögliche Transaktionen immer zurück)
 
-* ``failures``: eine optionale Exception-Klasse oder ein Tupel von Exceptions
+* ``failures``: eine optionale Exception-Klasse oder ein Tupel von Exceptions,
   bei denen die Ausfallsicherung zum Tragen kommen soll, falls die Vorgabe
-  (OperationalError, InternalError) nicht geeignet sein sollte
+  (OperationalError, InterfaceError, InternalError) für das verwendete
+  Datenbankadapter-Modul nicht geeignet sein sollte
 
 * ``ping``: mit diesem Parameter kann eingestellt werden, wann Verbindungen
   mit der ``ping()``-Methode geprüft werden, falls eine solche vorhanden ist
diff --git a/docs/main.html b/docs/main.html
index f0109a1..e0ee8c8 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -263,7 +263,8 @@ <h3>PersistentDB (persistent_db)</h3>
 prepare the session, e.g. <span class="docutils literal">[&quot;set datestyle to german&quot;, <span class="pre">...]</span></span></p></li>
 <li><p><span class="docutils literal">failures</span>: an optional exception class or a tuple of exception classes
 for which the connection failover mechanism shall be applied,
-if the default (OperationalError, InternalError) is not adequate</p></li>
+if the default (OperationalError, InterfaceError, InternalError)
+is not adequate for the used database module</p></li>
 <li><p><span class="docutils literal">ping</span>: an optional flag controlling when connections are checked
 with the <span class="docutils literal">ping()</span> method if such a method is available
 (<span class="docutils literal">0</span> = <span class="docutils literal">None</span> = never, <span class="docutils literal">1</span> = default = whenever it is requested,
@@ -340,7 +341,8 @@ <h3>PooledDB (pooled_db)</h3>
 the default value <span class="docutils literal">True</span> always issues a rollback for safety's sake)</p></li>
 <li><p><span class="docutils literal">failures</span>: an optional exception class or a tuple of exception classes
 for which the connection failover mechanism shall be applied,
-if the default (OperationalError, InternalError) is not adequate</p></li>
+if the default (OperationalError, InterfaceError, InternalError)
+is not adequate for the used database module</p></li>
 <li><p><span class="docutils literal">ping</span>: an optional flag controlling when connections are checked
 with the <span class="docutils literal">ping()</span> method if such a method is available
 (<span class="docutils literal">0</span> = <span class="docutils literal">None</span> = never, <span class="docutils literal">1</span> = default = whenever fetched from the pool,
diff --git a/docs/main.rst b/docs/main.rst
index 81c6427..6fdf6d2 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -250,7 +250,8 @@ of ``persistent_db``, passing the following parameters:
 
 * ``failures``: an optional exception class or a tuple of exception classes
   for which the connection failover mechanism shall be applied,
-  if the default (OperationalError, InternalError) is not adequate
+  if the default (OperationalError, InterfaceError, InternalError)
+  is not adequate for the used database module
 
 * ``ping``: an optional flag controlling when connections are checked
   with the ``ping()`` method if such a method is available
@@ -347,7 +348,8 @@ following parameters:
 
 * ``failures``: an optional exception class or a tuple of exception classes
   for which the connection failover mechanism shall be applied,
-  if the default (OperationalError, InternalError) is not adequate
+  if the default (OperationalError, InterfaceError, InternalError)
+  is not adequate for the used database module
 
 * ``ping``: an optional flag controlling when connections are checked
   with the ``ping()`` method if such a method is available
diff --git a/tests/mock_db.py b/tests/mock_db.py
index 42296cb..ceca9e3 100644
--- a/tests/mock_db.py
+++ b/tests/mock_db.py
@@ -15,6 +15,10 @@ class OperationalError(DatabaseError):
     pass
 
 
+class InterfaceError(DatabaseError):
+    pass
+
+
 class InternalError(DatabaseError):
     pass
 
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index d1a2628..6a22db2 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -406,8 +406,12 @@ def test_connection_failures(self):
         db = SteadyDBconnect(dbapi, failures=dbapi.OperationalError)
         db.close()
         self.assertRaises(dbapi.InternalError, db.cursor)
-        db = SteadyDBconnect(
-            dbapi, failures=(dbapi.OperationalError, dbapi.InternalError))
+        db = SteadyDBconnect(dbapi, failures=(
+            dbapi.OperationalError, dbapi.InterfaceError))
+        db.close()
+        self.assertRaises(dbapi.InternalError, db.cursor)
+        db = SteadyDBconnect(dbapi, failures=(
+            dbapi.OperationalError, dbapi.InterfaceError, dbapi.InternalError))
         db.close()
         db.cursor()
 

From b147745d709e7a7bfbc929ae7bfb1d41bd81ae15 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Wed, 22 Dec 2021 18:15:56 +0100
Subject: [PATCH 39/84] Prepare patch release

---
 .bumpversion.cfg    | 2 +-
 README.md           | 2 +-
 dbutils/__init__.py | 2 +-
 docs/main.de.html   | 2 +-
 docs/main.de.rst    | 2 +-
 docs/main.html      | 2 +-
 docs/main.rst       | 2 +-
 setup.py            | 2 +-
 8 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 277da9f..1bbd524 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 3.0.0
+current_version = 3.0.1
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/README.md b/README.md
index 41b2119..761e944 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 3.0.0 of DBUtils supports Python versions 3.6 to 3.10.
+The current version 3.0.1 of DBUtils supports Python versions 3.6 to 3.10.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there were some breaking changes in version 2.0.**
 
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index 19a4627..b326f99 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '3.0.0'
+__version__ = '3.0.1'
diff --git a/docs/main.de.html b/docs/main.de.html
index 4728326..020585e 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -12,7 +12,7 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.0</dd>
+<dd class="version">3.0.1</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 3f6d8a3..2df620c 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -1,7 +1,7 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 3.0.0
+:Version: 3.0.1
 :Translations: English_ | German
 
 .. _English: main.html
diff --git a/docs/main.html b/docs/main.html
index e0ee8c8..4c7a09e 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -12,7 +12,7 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.0</dd>
+<dd class="version">3.0.1</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
diff --git a/docs/main.rst b/docs/main.rst
index 6fdf6d2..d2c0202 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -1,7 +1,7 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 3.0.0
+:Version: 3.0.1
 :Translations: English | German_
 
 .. _German: main.de.html
diff --git a/setup.py b/setup.py
index 8fbcc11..2144f7e 100755
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '3.0.0'
+__version__ = '3.0.1'
 
 readme = open('README.md').read()
 

From ac477276d6667ce84b0a7e26d4cdb2dfac4cb303 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Wed, 12 Jan 2022 14:34:12 +0100
Subject: [PATCH 40/84] Support optional iterator protocol on cursors (#38)

Use the original iterator if the cursor is already iterable,
make it iterable otherwise.
---
 dbutils/steady_db.py    |  8 ++++++++
 tests/test_steady_db.py | 30 ++++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 8df2acb..3ad3b73 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -542,6 +542,14 @@ def __exit__(self, *exc):
         """Exit the runtime context for the cursor object."""
         self.close()
 
+    def __iter__(self):
+        """Make cursor compatible to the iteration protocol."""
+        cursor = self._cursor
+        try:  # use iterator provided by original cursor
+            return iter(cursor)
+        except TypeError:  # create iterator if not provided
+            return iter(cursor.fetchone, None)
+
     def setinputsizes(self, sizes):
         """Store input sizes in case cursor needs to be reopened."""
         self._inputsizes = sizes
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index 6a22db2..3c21cfd 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -273,6 +273,36 @@ def test_cursor_context_handler(self):
             self.assertEqual(cursor.fetchone(), 'test')
         self.assertEqual(db._con.open_cursors, 0)
 
+    def test_cursor_as_iterator_provided(self):
+        db = SteadyDBconnect(
+            dbapi, 0, None, None, None, True,
+            'SteadyDBTestDB', user='SteadyDBTestUser')
+        self.assertEqual(db._con.open_cursors, 0)
+        cursor = db.cursor()
+        self.assertEqual(db._con.open_cursors, 1)
+        cursor.execute('select test')
+        _cursor = cursor._cursor
+        try:
+            assert not hasattr(_cursor, 'iter')
+            _cursor.__iter__ = lambda: ['test-iter']
+            assert list(iter(cursor)) == ['test']
+        finally:
+            del _cursor.__iter__
+        cursor.close()
+        self.assertEqual(db._con.open_cursors, 0)
+
+    def test_cursor_as_iterator_created(self):
+        db = SteadyDBconnect(
+            dbapi, 0, None, None, None, True,
+            'SteadyDBTestDB', user='SteadyDBTestUser')
+        self.assertEqual(db._con.open_cursors, 0)
+        cursor = db.cursor()
+        self.assertEqual(db._con.open_cursors, 1)
+        cursor.execute('select test')
+        assert list(iter(cursor)) == ['test']
+        cursor.close()
+        self.assertEqual(db._con.open_cursors, 0)
+
     def test_connection_creator_function(self):
         db1 = SteadyDBconnect(
             dbapi, 0, None, None, None, True,

From 798d5ad3bdfccdebc22fd3d7b16c8b9e86bdcaf7 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 14 Jan 2022 21:58:03 +0100
Subject: [PATCH 41/84] Prepare patch release

---
 .bumpversion.cfg    |  2 +-
 LICENSE             |  2 +-
 README.md           |  2 +-
 dbutils/__init__.py |  2 +-
 docs/changelog.html | 37 +++++++++++++++++++++----------------
 docs/changelog.rst  |  6 ++++++
 docs/main.de.html   |  2 +-
 docs/main.de.rst    |  2 +-
 docs/main.html      |  4 ++--
 docs/main.rst       |  4 ++--
 setup.py            |  2 +-
 11 files changed, 38 insertions(+), 27 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 1bbd524..64494c4 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 3.0.1
+current_version = 3.0.2
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/LICENSE b/LICENSE
index 641db5d..6c2a74b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2021 Christoph Zwerschke
+Copyright (c) 2022 Christoph Zwerschke
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 761e944..ac3ce03 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 3.0.1 of DBUtils supports Python versions 3.6 to 3.10.
+The current version 3.0.2 of DBUtils supports Python versions 3.6 to 3.10.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there were some breaking changes in version 2.0.**
 
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index b326f99..7a0bdf2 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '3.0.1'
+__version__ = '3.0.2'
diff --git a/docs/changelog.html b/docs/changelog.html
index 13eda8b..82ceecf 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -12,13 +12,18 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="section-1">
+<h2>3.0.2</h2>
+<p>DBUtils 3.0.2 was released on January 14, 2022.</p>
+<p>The the optional iterator protocol on cursors is now supported.</p>
+</section>
+<section id="section-2">
 <h2>3.0.1</h2>
 <p>DBUtils 3.0.1 was released on December 22, 2021.</p>
 <p>It includes <span class="docutils literal">InterfaceError</span> to the default list of exceptions
 for which the connection failover mechanism is applied.
 You can override this with the <span class="docutils literal">failures</span> parameter.</p>
 </section>
-<section id="section-2">
+<section id="section-3">
 <h2>3.0.0</h2>
 <p>DBUtils 3.0.0 was released on November 26, 2021.</p>
 <p>It is intended to be used with Python versions 3.6 to 3.10.</p>
@@ -27,7 +32,7 @@ <h2>3.0.0</h2>
 <li><p>Cease support for Python 2 and 3.5, minor optimizations.</p></li>
 </ul>
 </section>
-<section id="section-3">
+<section id="section-4">
 <h2>2.0.3</h2>
 <p>DBUtils 2.0.3 was released on November 26, 2021.</p>
 <p>Changes:</p>
@@ -35,7 +40,7 @@ <h2>2.0.3</h2>
 <li><p>Support Python version 3.10.</p></li>
 </ul>
 </section>
-<section id="section-4">
+<section id="section-5">
 <h2>2.0.2</h2>
 <p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
@@ -43,7 +48,7 @@ <h2>2.0.2</h2>
 <li><p>Allow using context managers for pooled connections.</p></li>
 </ul>
 </section>
-<section id="section-5">
+<section id="section-6">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
@@ -51,7 +56,7 @@ <h2>2.0.1</h2>
 <li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="section-6">
+<section id="section-7">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -67,7 +72,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="section-7">
+<section id="section-8">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -78,7 +83,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="section-8">
+<section id="section-9">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -87,12 +92,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="section-9">
+<section id="section-10">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="section-10">
+<section id="section-11">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -106,7 +111,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="section-11">
+<section id="section-12">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -135,7 +140,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="section-12">
+<section id="section-13">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -168,7 +173,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="section-13">
+<section id="section-14">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -177,7 +182,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="section-14">
+<section id="section-15">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -192,7 +197,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="section-15">
+<section id="section-16">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -202,7 +207,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="section-16">
+<section id="section-17">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -216,7 +221,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="section-17">
+<section id="section-18">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 85a2f19..7706d89 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,12 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+3.0.2
+=====
+
+DBUtils 3.0.2 was released on January 14, 2022.
+
+The the optional iterator protocol on cursors is now supported.
 
 3.0.1
 =====
diff --git a/docs/main.de.html b/docs/main.de.html
index 020585e..e60a2e7 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -12,7 +12,7 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.1</dd>
+<dd class="version">3.0.2</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 2df620c..574eb3f 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -1,7 +1,7 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 3.0.1
+:Version: 3.0.2
 :Translations: English_ | German
 
 .. _English: main.html
diff --git a/docs/main.html b/docs/main.html
index 4c7a09e..42fdfa9 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -12,7 +12,7 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.1</dd>
+<dd class="version">3.0.2</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
@@ -473,7 +473,7 @@ <h2>Credits</h2>
 </section>
 <section id="copyright-and-license">
 <h2>Copyright and License</h2>
-<p>Copyright © 2005-2021 by Christoph Zwerschke.
+<p>Copyright © 2005-2022 by Christoph Zwerschke.
 All Rights Reserved.</p>
 <p>DBUtils is free and open source software,
 licensed under the <a class="reference external" href="https://opensource.org/licenses/MIT">MIT license</a>.</p>
diff --git a/docs/main.rst b/docs/main.rst
index d2c0202..150ef3c 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -1,7 +1,7 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 3.0.1
+:Version: 3.0.2
 :Translations: English | German_
 
 .. _German: main.de.html
@@ -521,7 +521,7 @@ Credits
 Copyright and License
 =====================
 
-Copyright © 2005-2021 by Christoph Zwerschke.
+Copyright © 2005-2022 by Christoph Zwerschke.
 All Rights Reserved.
 
 DBUtils is free and open source software,
diff --git a/setup.py b/setup.py
index 2144f7e..c6c1d28 100755
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '3.0.1'
+__version__ = '3.0.2'
 
 readme = open('README.md').read()
 

From 3ba9f4975a92c3f562c57d452777acb6da6d8179 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Tue, 25 Apr 2023 11:24:17 +0200
Subject: [PATCH 42/84] Fix small typo in code and docs

---
 .gitignore           | 1 +
 dbutils/pooled_db.py | 4 ++--
 dbutils/pooled_pg.py | 2 +-
 dbutils/steady_pg.py | 2 +-
 docs/main.html       | 2 +-
 docs/main.rst        | 2 +-
 6 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore
index 1663b67..b9cc250 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
 
 build
 dist
+local
 
 .idea
 .tox
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 1b47f2b..f395a57 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -53,7 +53,7 @@
     setsession: an optional list of SQL commands that may serve to
         prepare the session, e.g. ["set datestyle to german", ...]
     reset: how connections should be reset when returned to the pool
-        (False or None to rollback transcations started with begin(),
+        (False or None to rollback transactions started with begin(),
         the default value True always issues a rollback for safety's sake)
     failures: an optional exception class or a tuple of exception classes
         for which the connection failover mechanism shall be applied,
@@ -207,7 +207,7 @@ def __init__(
         setsession: optional list of SQL commands that may serve to prepare
             the session, e.g. ["set datestyle to ...", "set time zone ..."]
         reset: how connections should be reset when returned to the pool
-            (False or None to rollback transcations started with begin(),
+            (False or None to rollback transactions started with begin(),
             True to always issue a rollback for safety's sake)
         failures: an optional exception class or a tuple of exception classes
             for which the connection failover mechanism shall be applied,
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index f9776f7..7bab407 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -162,7 +162,7 @@ def __init__(
         setsession: optional list of SQL commands that may serve to prepare
             the session, e.g. ["set datestyle to ...", "set time zone ..."]
         reset: how connections should be reset when returned to the pool
-            (0 or None to rollback transcations started with begin(),
+            (0 or None to rollback transactions started with begin(),
             1 to always issue a rollback, 2 for a complete reset)
         args, kwargs: the parameters that shall be used to establish
             the PostgreSQL connections using class PyGreSQL pg.DB()
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index c1a6b71..a553078 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -183,7 +183,7 @@ def reopen(self):
         try:
             self._con.reopen()
         except Exception:
-            if self._transcation:
+            if self._transaction:
                 self._transaction = False
                 try:
                     self._con.query('rollback')
diff --git a/docs/main.html b/docs/main.html
index 42fdfa9..a677b7b 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -337,7 +337,7 @@ <h3>PooledDB (pooled_db)</h3>
 <li><p><span class="docutils literal">setsession</span>: an optional list of SQL commands that may serve to
 prepare the session, e.g. <span class="docutils literal">[&quot;set datestyle to german&quot;, <span class="pre">...]</span></span></p></li>
 <li><p><span class="docutils literal">reset</span>: how connections should be reset when returned to the pool
-(<span class="docutils literal">False</span> or <span class="docutils literal">None</span> to rollback transcations started with <span class="docutils literal">begin()</span>,
+(<span class="docutils literal">False</span> or <span class="docutils literal">None</span> to rollback transactions started with <span class="docutils literal">begin()</span>,
 the default value <span class="docutils literal">True</span> always issues a rollback for safety's sake)</p></li>
 <li><p><span class="docutils literal">failures</span>: an optional exception class or a tuple of exception classes
 for which the connection failover mechanism shall be applied,
diff --git a/docs/main.rst b/docs/main.rst
index 150ef3c..bb77fa5 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -343,7 +343,7 @@ following parameters:
   prepare the session, e.g. ``["set datestyle to german", ...]``
 
 * ``reset``: how connections should be reset when returned to the pool
-  (``False`` or ``None`` to rollback transcations started with ``begin()``,
+  (``False`` or ``None`` to rollback transactions started with ``begin()``,
   the default value ``True`` always issues a rollback for safety's sake)
 
 * ``failures``: an optional exception class or a tuple of exception classes

From c699443d31fce1ad20f27658b4a5887863a0764e Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Tue, 25 Apr 2023 11:39:22 +0200
Subject: [PATCH 43/84] Minor improvement in determining thread safety

---
 dbutils/persistent_db.py | 13 ++++++++-----
 dbutils/pooled_db.py     | 13 ++++++++-----
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/dbutils/persistent_db.py b/dbutils/persistent_db.py
index 6873905..9082488 100644
--- a/dbutils/persistent_db.py
+++ b/dbutils/persistent_db.py
@@ -174,12 +174,15 @@ def __init__(
             threadsafety = creator.threadsafety
         except AttributeError:
             try:
-                if not callable(creator.connect):
-                    raise AttributeError
+                threadsafety = creator.dbapi.threadsafety
             except AttributeError:
-                threadsafety = 1
-            else:
-                threadsafety = 0
+                try:
+                    if not callable(creator.connect):
+                        raise AttributeError
+                except AttributeError:
+                    threadsafety = 1
+                else:
+                    threadsafety = 0
         if not threadsafety:
             raise NotSupportedError("Database module is not thread-safe.")
         self._creator = creator
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index f395a57..fb39ab4 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -224,12 +224,15 @@ def __init__(
             threadsafety = creator.threadsafety
         except AttributeError:
             try:
-                if not callable(creator.connect):
-                    raise AttributeError
+                threadsafety = creator.dbapi.threadsafety
             except AttributeError:
-                threadsafety = 2
-            else:
-                threadsafety = 0
+                try:
+                    if not callable(creator.connect):
+                        raise AttributeError
+                except AttributeError:
+                    threadsafety = 1
+                else:
+                    threadsafety = 0
         if not threadsafety:
             raise NotSupportedError("Database module is not thread-safe.")
         self._creator = creator

From f2186a8086834a365089a1cfcb09cca4ca8bd0ac Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Tue, 25 Apr 2023 11:46:25 +0200
Subject: [PATCH 44/84] Support Python 3.11

---
 .flake8                               |  2 +-
 .github/workflows/publish_on_pypi.yml |  8 ++++----
 .github/workflows/test_with_tox.yml   |  8 ++++----
 README.md                             |  2 +-
 docs/main.de.html                     |  2 +-
 docs/main.de.rst                      |  2 +-
 docs/main.html                        |  2 +-
 docs/main.rst                         |  2 +-
 setup.py                              |  1 +
 tox.ini                               | 10 +++++-----
 10 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/.flake8 b/.flake8
index 578558b..e25de91 100644
--- a/.flake8
+++ b/.flake8
@@ -1,4 +1,4 @@
 [flake8]
 ignore = E722,W503
-exclude = .git,.pytest_cache,.tox,.venv,.idea,__pycache__,build,dist,docs
+exclude = .git,.pytest_cache,.tox,.venv,.idea,__pycache__,build,build,dist,docs,local
 max-line-length = 79
diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
index 9d887fa..cd0df25 100644
--- a/.github/workflows/publish_on_pypi.yml
+++ b/.github/workflows/publish_on_pypi.yml
@@ -10,18 +10,18 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: ['3.9']
+        python: ['3.10']
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Set up Python ${{ matrix.python }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python }}
 
     - name: Build source tarball
-      if: matrix.python == 3.9
+      if: matrix.python == 3.10
       run: python setup.py sdist
 
     - name: Build wheel
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index 62246b9..24d7690 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -7,13 +7,13 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: ['3.6', '3.7', '3.8', '3.9', '3.10']
+        python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
       - name: Setup Python ${{ matrix.python }}
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python }}
 
@@ -21,5 +21,5 @@ jobs:
 
       - run: tox -e py
 
-      - if: matrix.python == 3.9
+      - if: matrix.python == 3.10
         run: TOXENV=flake8,manifest,docs,spell tox
diff --git a/README.md b/README.md
index ac3ce03..ff2d82c 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 3.0.2 of DBUtils supports Python versions 3.6 to 3.10.
+The current version 3.0.2 of DBUtils supports Python versions 3.6 to 3.11.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there were some breaking changes in version 2.0.**
 
diff --git a/docs/main.de.html b/docs/main.de.html
index e60a2e7..c28fe0a 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -136,7 +136,7 @@ <h3>Installation</h3>
 </section>
 <section id="anforderungen">
 <h2>Anforderungen</h2>
-<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 3.6 bis 3.10.</p>
+<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 3.6 bis 3.11.</p>
 <p>Die Module in der Variante für klassisches PyGreSQL benötigen <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a>
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
 für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten,
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 574eb3f..f76f654 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -98,7 +98,7 @@ herunterzuladen und zu installieren::
 Anforderungen
 =============
 
-DBUtils unterstützt die Python_ Versionen 3.6 bis 3.10.
+DBUtils unterstützt die Python_ Versionen 3.6 bis 3.11.
 
 Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL_
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
diff --git a/docs/main.html b/docs/main.html
index a677b7b..16fcc4d 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -133,7 +133,7 @@ <h3>Installation</h3>
 </section>
 <section id="requirements">
 <h2>Requirements</h2>
-<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> versions 3.6 to 3.10.</p>
+<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> versions 3.6 to 3.11.</p>
 <p>The modules in the classic PyGreSQL variant need <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
 any Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> compliant database interface module.</p>
diff --git a/docs/main.rst b/docs/main.rst
index bb77fa5..8d25542 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -95,7 +95,7 @@ It is even easier to download and install the package in one go using `pip`_::
 Requirements
 ============
 
-DBUtils supports Python_ versions 3.6 to 3.10.
+DBUtils supports Python_ versions 3.6 to 3.11.
 
 The modules in the classic PyGreSQL variant need PyGreSQL_ version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
diff --git a/setup.py b/setup.py
index c6c1d28..9baee37 100755
--- a/setup.py
+++ b/setup.py
@@ -39,6 +39,7 @@
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
+        'Programming Language :: Python :: 3.11',
         'Operating System :: OS Independent',
     ],
     author='Christoph Zwerschke',
diff --git a/tox.ini b/tox.ini
index 404b6c5..6143fb1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py3{6,7,8,9,10}, flake8, manifest, docs, spell
+envlist = py3{6,7,8,9,10,11}, flake8, manifest, docs, spell
 
 [testenv]
 setenv =
@@ -9,25 +9,25 @@ commands =
     pytest {posargs}
 
 [testenv:spell]
-basepython = python3.9
+basepython = python3.10
 deps = codespell
 commands =
     codespell .
 
 [testenv:flake8]
-basepython = python3.9
+basepython = python3.10
 deps = flake8
 commands =
     flake8 .
 
 [testenv:manifest]
-basepython = python3.9
+basepython = python3.10
 deps = check-manifest
 commands =
     check-manifest -v
 
 [testenv:docs]
-basepython = python3.9
+basepython = python3.10
 deps = docutils
 changedir = docs
 commands =

From 6d517e2a8092a52a89274f6aca3815efacffeec0 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Tue, 25 Apr 2023 11:55:42 +0200
Subject: [PATCH 45/84] Improve determination of DBAPI module

---
 dbutils/steady_db.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 3ad3b73..0e34a9b 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -148,7 +148,11 @@ def __init__(
         # proper initialization of the connection
         try:
             self._creator = creator.connect
-            self._dbapi = creator
+            try:
+                if creator.dbapi.connect:
+                    self._dbapi = creator.dbapi
+            except AttributeError:
+                self._dbapi = creator
         except AttributeError:
             # try finding the DB-API 2 module via the connection creator
             self._creator = creator

From 8d401797e8e8ad6cc1ea410a37a45f972a417f6e Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Tue, 25 Apr 2023 12:01:32 +0200
Subject: [PATCH 46/84] Fix spellcheck issue

---
 .codespellrc        | 2 +-
 docs/changelog.html | 4 ++--
 docs/main.de.html   | 4 ++--
 docs/main.html      | 4 ++--
 docs/make.py        | 2 +-
 5 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/.codespellrc b/.codespellrc
index 15761d2..ab81ffd 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -1,3 +1,3 @@
 [codespell]
-skip = .tox,.venv,*.de.html,*.de.rst
+skip = .git,.tox,.venv,*.de.html,*.de.rst,build,dist,local
 quiet-level = 2
diff --git a/docs/changelog.html b/docs/changelog.html
index 82ceecf..e623513 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
-<meta charset="utf-8"/>
+<meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
 <title>Changelog for DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
diff --git a/docs/main.de.html b/docs/main.de.html
index c28fe0a..1b43bff 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
 <head>
-<meta charset="utf-8"/>
+<meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
 <title>Benutzeranleitung für DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
diff --git a/docs/main.html b/docs/main.html
index 16fcc4d..ebf28a3 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
-<meta charset="utf-8"/>
+<meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
+<meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
 <title>DBUtils User's Guide</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
diff --git a/docs/make.py b/docs/make.py
index d7b2cad..28b9625 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python3.9
 
-"""Build HMTL from reST files."""
+"""Build HTML from reST files."""
 
 from glob import glob
 from os.path import splitext

From dfada0f9041e18e5ee50d1e5395dfd45a29a6474 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 27 Apr 2023 18:55:00 +0200
Subject: [PATCH 47/84] Add section on advanced usage to docs (#45)

---
 docs/main.de.html | 34 ++++++++++++++++++++++++++++------
 docs/main.de.rst  | 23 +++++++++++++++++++++++
 docs/main.html    | 33 +++++++++++++++++++++++++++------
 docs/main.rst     | 22 ++++++++++++++++++++++
 4 files changed, 100 insertions(+), 12 deletions(-)

diff --git a/docs/main.de.html b/docs/main.de.html
index 1b43bff..633b308 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -44,12 +44,13 @@ <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <li><p><a class="reference internal" href="#pooleddb-pooled-db-1" id="toc-entry-15">PooledDB (pooled_db)</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#anmerkungen" id="toc-entry-16">Anmerkungen</a></p></li>
-<li><p><a class="reference internal" href="#zukunft" id="toc-entry-17">Zukunft</a></p></li>
-<li><p><a class="reference internal" href="#fehlermeldungen-und-feedback" id="toc-entry-18">Fehlermeldungen und Feedback</a></p></li>
-<li><p><a class="reference internal" href="#links" id="toc-entry-19">Links</a></p></li>
-<li><p><a class="reference internal" href="#autoren" id="toc-entry-20">Autoren</a></p></li>
-<li><p><a class="reference internal" href="#copyright-und-lizenz" id="toc-entry-21">Copyright und Lizenz</a></p></li>
+<li><p><a class="reference internal" href="#besonderheiten-bei-der-benutzung" id="toc-entry-16">Besonderheiten bei der Benutzung</a></p></li>
+<li><p><a class="reference internal" href="#anmerkungen" id="toc-entry-17">Anmerkungen</a></p></li>
+<li><p><a class="reference internal" href="#zukunft" id="toc-entry-18">Zukunft</a></p></li>
+<li><p><a class="reference internal" href="#fehlermeldungen-und-feedback" id="toc-entry-19">Fehlermeldungen und Feedback</a></p></li>
+<li><p><a class="reference internal" href="#links" id="toc-entry-20">Links</a></p></li>
+<li><p><a class="reference internal" href="#autoren" id="toc-entry-21">Autoren</a></p></li>
+<li><p><a class="reference internal" href="#copyright-und-lizenz" id="toc-entry-22">Copyright und Lizenz</a></p></li>
 </ul>
 </nav>
 <section id="zusammenfassung">
@@ -435,6 +436,27 @@ <h3>PooledDB (pooled_db)</h3>
 wieder an den Verbindungspool zurückgegeben wird.</p>
 </section>
 </section>
+<section id="besonderheiten-bei-der-benutzung">
+<h2>Besonderheiten bei der Benutzung</h2>
+<p>Manchmal möchte man Datenbankverbindung besonders vorbereiten, bevor sie
+von DBUtils verwendet werden, und dies ist nicht immer durch Verwendung
+der passenden Parameter möglich. Zum Beispiel kann es <span class="docutils literal">pyodb</span> erfordern,
+dass man die Methode <span class="docutils literal">setencoding()</span> der Datenbankverbindung aufruft.
+Sie können dies erreichen, indem Sie eine modifizierte Version der
+Funktion <span class="docutils literal">connect()</span> verwenden und diese als <span class="docutils literal">creator</span> (dem ersten
+Argument) an <span class="docutils literal">PersistentDB</span> oder <span class="docutils literal">PooledDB</span> übergeben, etwa so:</p>
+<pre class="literal-block">from pyodbc import connect
+from dbutils.pooled_db import PooledDB
+
+def creator():
+    con = connect(...)
+    con.setdecoding(...)
+    return con
+
+creator.dbapi = pyodbc
+
+db_pool = PooledDB(creator, mincached=5)</pre>
+</section>
 <section id="anmerkungen">
 <h2>Anmerkungen</h2>
 <p>Wenn Sie einen der bekannten &quot;Object-Relational Mapper&quot; <a class="reference external" href="http://sqlobject.org/">SQLObject</a> oder
diff --git a/docs/main.de.rst b/docs/main.de.rst
index f76f654..147ce5e 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -461,6 +461,29 @@ ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
 wieder an den Verbindungspool zurückgegeben wird.
 
 
+Besonderheiten bei der Benutzung
+================================
+Manchmal möchte man Datenbankverbindung besonders vorbereiten, bevor sie
+von DBUtils verwendet werden, und dies ist nicht immer durch Verwendung
+der passenden Parameter möglich. Zum Beispiel kann es ``pyodb`` erfordern,
+dass man die Methode ``setencoding()`` der Datenbankverbindung aufruft.
+Sie können dies erreichen, indem Sie eine modifizierte Version der
+Funktion ``connect()`` verwenden und diese als ``creator`` (dem ersten
+Argument) an ``PersistentDB`` oder ``PooledDB`` übergeben, etwa so::
+
+    from pyodbc import connect
+    from dbutils.pooled_db import PooledDB
+
+    def creator():
+        con = connect(...)
+        con.setdecoding(...)
+        return con
+
+    creator.dbapi = pyodbc
+
+    db_pool = PooledDB(creator, mincached=5)
+
+
 Anmerkungen
 ===========
 Wenn Sie einen der bekannten "Object-Relational Mapper" SQLObject_ oder
diff --git a/docs/main.html b/docs/main.html
index ebf28a3..a1b2628 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -44,12 +44,13 @@ <h1 class="title">DBUtils User's Guide</h1>
 <li><p><a class="reference internal" href="#pooleddb-pooled-db-1" id="toc-entry-15">PooledDB (pooled_db)</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#notes" id="toc-entry-16">Notes</a></p></li>
-<li><p><a class="reference internal" href="#future" id="toc-entry-17">Future</a></p></li>
-<li><p><a class="reference internal" href="#bug-reports-and-feedback" id="toc-entry-18">Bug reports and feedback</a></p></li>
-<li><p><a class="reference internal" href="#links" id="toc-entry-19">Links</a></p></li>
-<li><p><a class="reference internal" href="#credits" id="toc-entry-20">Credits</a></p></li>
-<li><p><a class="reference internal" href="#copyright-and-license" id="toc-entry-21">Copyright and License</a></p></li>
+<li><p><a class="reference internal" href="#advanced-usage" id="toc-entry-16">Advanced Usage</a></p></li>
+<li><p><a class="reference internal" href="#notes" id="toc-entry-17">Notes</a></p></li>
+<li><p><a class="reference internal" href="#future" id="toc-entry-18">Future</a></p></li>
+<li><p><a class="reference internal" href="#bug-reports-and-feedback" id="toc-entry-19">Bug reports and feedback</a></p></li>
+<li><p><a class="reference internal" href="#links" id="toc-entry-20">Links</a></p></li>
+<li><p><a class="reference internal" href="#credits" id="toc-entry-21">Credits</a></p></li>
+<li><p><a class="reference internal" href="#copyright-and-license" id="toc-entry-22">Copyright and License</a></p></li>
 </ul>
 </nav>
 <section id="synopsis">
@@ -396,6 +397,26 @@ <h3>PooledDB (pooled_db)</h3>
 back before being given back to the connection pool.</p>
 </section>
 </section>
+<section id="advanced-usage">
+<h2>Advanced Usage</h2>
+<p>Sometimes you may want to prepare connections before they are used by
+DBUtils, in ways that are not possible by just using the right parameters.
+For instance, <span class="docutils literal">pyodbc</span> may require to configure connections by calling
+the <span class="docutils literal">setencoding()</span> method of the connection. You can do this by passing
+a modified <span class="docutils literal">connect()</span> function to <span class="docutils literal">PersistentDB</span> or <span class="docutils literal">PooledDB</span> as
+<span class="docutils literal">creator</span> (the first argument), like this:</p>
+<pre class="literal-block">from pyodbc import connect
+from dbutils.pooled_db import PooledDB
+
+def creator():
+    con = connect(...)
+    con.setdecoding(...)
+    return con
+
+creator.dbapi = pyodbc
+
+db_pool = PooledDB(creator, mincached=5)</pre>
+</section>
 <section id="notes">
 <h2>Notes</h2>
 <p>If you are using one of the popular object-relational mappers <a class="reference external" href="http://www.sqlobject.org/">SQLObject</a>
diff --git a/docs/main.rst b/docs/main.rst
index 8d25542..2549118 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -421,6 +421,28 @@ until the end of the transaction, and that the connection will be rolled
 back before being given back to the connection pool.
 
 
+Advanced Usage
+==============
+Sometimes you may want to prepare connections before they are used by
+DBUtils, in ways that are not possible by just using the right parameters.
+For instance, ``pyodbc`` may require to configure connections by calling
+the ``setencoding()`` method of the connection. You can do this by passing
+a modified ``connect()`` function to ``PersistentDB`` or ``PooledDB`` as
+``creator`` (the first argument), like this::
+
+    from pyodbc import connect
+    from dbutils.pooled_db import PooledDB
+
+    def creator():
+        con = connect(...)
+        con.setdecoding(...)
+        return con
+
+    creator.dbapi = pyodbc
+
+    db_pool = PooledDB(creator, mincached=5)
+
+
 Notes
 =====
 If you are using one of the popular object-relational mappers SQLObject_

From 245cfbbb147bc0875384b0e3770fb0854ffc81aa Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 27 Apr 2023 19:14:43 +0200
Subject: [PATCH 48/84] Prepare patch release

---
 .bumpversion.cfg    |  2 +-
 LICENSE             |  2 +-
 README.md           |  2 +-
 dbutils/__init__.py |  2 +-
 docs/changelog.html | 46 +++++++++++++++++++++++++++------------------
 docs/changelog.rst  | 13 ++++++++++++-
 docs/main.de.html   |  4 ++--
 docs/main.de.rst    |  4 ++--
 docs/main.html      |  4 ++--
 docs/main.rst       |  4 ++--
 setup.py            |  2 +-
 11 files changed, 53 insertions(+), 32 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 64494c4..663f79f 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 3.0.2
+current_version = 3.0.3
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/LICENSE b/LICENSE
index 6c2a74b..15287a3 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2022 Christoph Zwerschke
+Copyright (c) 2023 Christoph Zwerschke
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index ff2d82c..09ccb37 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 3.0.2 of DBUtils supports Python versions 3.6 to 3.11.
+The current version 3.0.3 of DBUtils supports Python versions 3.6 to 3.11.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there were some breaking changes in version 2.0.**
 
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index 7a0bdf2..f7a4ee8 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '3.0.2'
+__version__ = '3.0.3'
diff --git a/docs/changelog.html b/docs/changelog.html
index e623513..1f5ce90 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -12,18 +12,28 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="section-1">
+<h2>3.0.3</h2>
+<p>DBUtils 3.0.3 was released on April 27, 2023.</p>
+<p>Changes:</p>
+<ul class="simple">
+<li><p>Support Python version 3.11.</p></li>
+<li><p>Improve determination of DB API module if creator is specified.</p></li>
+<li><p>Minor fixes and section an advanced usage in docs.</p></li>
+</ul>
+</section>
+<section id="section-2">
 <h2>3.0.2</h2>
 <p>DBUtils 3.0.2 was released on January 14, 2022.</p>
-<p>The the optional iterator protocol on cursors is now supported.</p>
+<p>The optional iterator protocol on cursors is now supported.</p>
 </section>
-<section id="section-2">
+<section id="section-3">
 <h2>3.0.1</h2>
 <p>DBUtils 3.0.1 was released on December 22, 2021.</p>
 <p>It includes <span class="docutils literal">InterfaceError</span> to the default list of exceptions
 for which the connection failover mechanism is applied.
 You can override this with the <span class="docutils literal">failures</span> parameter.</p>
 </section>
-<section id="section-3">
+<section id="section-4">
 <h2>3.0.0</h2>
 <p>DBUtils 3.0.0 was released on November 26, 2021.</p>
 <p>It is intended to be used with Python versions 3.6 to 3.10.</p>
@@ -32,7 +42,7 @@ <h2>3.0.0</h2>
 <li><p>Cease support for Python 2 and 3.5, minor optimizations.</p></li>
 </ul>
 </section>
-<section id="section-4">
+<section id="section-5">
 <h2>2.0.3</h2>
 <p>DBUtils 2.0.3 was released on November 26, 2021.</p>
 <p>Changes:</p>
@@ -40,7 +50,7 @@ <h2>2.0.3</h2>
 <li><p>Support Python version 3.10.</p></li>
 </ul>
 </section>
-<section id="section-5">
+<section id="section-6">
 <h2>2.0.2</h2>
 <p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
@@ -48,7 +58,7 @@ <h2>2.0.2</h2>
 <li><p>Allow using context managers for pooled connections.</p></li>
 </ul>
 </section>
-<section id="section-6">
+<section id="section-7">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
@@ -56,7 +66,7 @@ <h2>2.0.1</h2>
 <li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="section-7">
+<section id="section-8">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -72,7 +82,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="section-8">
+<section id="section-9">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -83,7 +93,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="section-9">
+<section id="section-10">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -92,12 +102,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="section-10">
+<section id="section-11">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="section-11">
+<section id="section-12">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -111,7 +121,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="section-12">
+<section id="section-13">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -140,7 +150,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="section-13">
+<section id="section-14">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -173,7 +183,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="section-14">
+<section id="section-15">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -182,7 +192,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="section-15">
+<section id="section-16">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -197,7 +207,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="section-16">
+<section id="section-17">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -207,7 +217,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="section-17">
+<section id="section-18">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -221,7 +231,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="section-18">
+<section id="section-19">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 7706d89..6d0833f 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,12 +1,23 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+3.0.3
+=====
+
+DBUtils 3.0.3 was released on April 27, 2023.
+
+Changes:
+
+* Support Python version 3.11.
+* Improve determination of DB API module if creator is specified.
+* Minor fixes and section an advanced usage in docs.
+
 3.0.2
 =====
 
 DBUtils 3.0.2 was released on January 14, 2022.
 
-The the optional iterator protocol on cursors is now supported.
+The optional iterator protocol on cursors is now supported.
 
 3.0.1
 =====
diff --git a/docs/main.de.html b/docs/main.de.html
index 633b308..1a2dda7 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -12,7 +12,7 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.2</dd>
+<dd class="version">3.0.3</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
@@ -536,7 +536,7 @@ <h2>Autoren</h2>
 </section>
 <section id="copyright-und-lizenz">
 <h2>Copyright und Lizenz</h2>
-<p>Copyright © 2005-2021 Christoph Zwerschke.
+<p>Copyright © 2005-2023 Christoph Zwerschke.
 Alle Rechte vorbehalten.</p>
 <p>DBUtils ist freie und quelloffene Software,
 lizenziert unter der <a class="reference external" href="https://opensource.org/licenses/MIT">MIT-Lizenz</a>.</p>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index 147ce5e..aa800d6 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -1,7 +1,7 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 3.0.2
+:Version: 3.0.3
 :Translations: English_ | German
 
 .. _English: main.html
@@ -583,7 +583,7 @@ Autoren
 Copyright und Lizenz
 ====================
 
-Copyright © 2005-2021 Christoph Zwerschke.
+Copyright © 2005-2023 Christoph Zwerschke.
 Alle Rechte vorbehalten.
 
 DBUtils ist freie und quelloffene Software,
diff --git a/docs/main.html b/docs/main.html
index a1b2628..c77a83b 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -12,7 +12,7 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.2</dd>
+<dd class="version">3.0.3</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
@@ -494,7 +494,7 @@ <h2>Credits</h2>
 </section>
 <section id="copyright-and-license">
 <h2>Copyright and License</h2>
-<p>Copyright © 2005-2022 by Christoph Zwerschke.
+<p>Copyright © 2005-2023 by Christoph Zwerschke.
 All Rights Reserved.</p>
 <p>DBUtils is free and open source software,
 licensed under the <a class="reference external" href="https://opensource.org/licenses/MIT">MIT license</a>.</p>
diff --git a/docs/main.rst b/docs/main.rst
index 2549118..4793d30 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -1,7 +1,7 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 3.0.2
+:Version: 3.0.3
 :Translations: English | German_
 
 .. _German: main.de.html
@@ -543,7 +543,7 @@ Credits
 Copyright and License
 =====================
 
-Copyright © 2005-2022 by Christoph Zwerschke.
+Copyright © 2005-2023 by Christoph Zwerschke.
 All Rights Reserved.
 
 DBUtils is free and open source software,
diff --git a/setup.py b/setup.py
index 9baee37..f46fef6 100755
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '3.0.2'
+__version__ = '3.0.3'
 
 readme = open('README.md').read()
 

From 705e9400c983de5f7571d5cd824091feefda27e1 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Thu, 27 Apr 2023 19:18:33 +0200
Subject: [PATCH 49/84] Do not test with Python 3.6 on GitHub any more

---
 .github/workflows/test_with_tox.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index 24d7690..b68431e 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -7,7 +7,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
+        python: ['3.7', '3.8', '3.9', '3.10', '3.11']
 
     steps:
       - uses: actions/checkout@v3

From 6f52641b4fd262c2f6e0c933d0c393a0a1ef27a8 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Sat, 29 Apr 2023 17:39:02 +0200
Subject: [PATCH 50/84] Replace flake8 with ruff (#46)

---
 .flake8                             |  4 --
 .github/workflows/test_with_tox.yml |  2 +-
 MANIFEST.in                         |  2 +-
 dbutils/pooled_pg.py                |  2 +-
 dbutils/steady_db.py                |  1 +
 docs/make.py                        | 13 ++---
 pyproject.toml                      | 80 +++++++++++++++++++++++++++++
 setup.py                            |  3 ++
 tests/mock_pg.py                    |  2 +
 tox.ini                             |  8 +--
 10 files changed, 100 insertions(+), 17 deletions(-)
 delete mode 100644 .flake8
 create mode 100644 pyproject.toml

diff --git a/.flake8 b/.flake8
deleted file mode 100644
index e25de91..0000000
--- a/.flake8
+++ /dev/null
@@ -1,4 +0,0 @@
-[flake8]
-ignore = E722,W503
-exclude = .git,.pytest_cache,.tox,.venv,.idea,__pycache__,build,build,dist,docs,local
-max-line-length = 79
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index b68431e..dbc4ace 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -22,4 +22,4 @@ jobs:
       - run: tox -e py
 
       - if: matrix.python == 3.10
-        run: TOXENV=flake8,manifest,docs,spell tox
+        run: TOXENV=ruff,manifest,docs,spell tox
diff --git a/MANIFEST.in b/MANIFEST.in
index be66fba..6408840 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,7 +5,7 @@ include README.md
 
 include .bumpversion.cfg
 include .codespellrc
-include .flake8
+include pyproject.toml
 include tox.ini
 
 recursive-include tests *.py
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index 7bab407..e2389c7 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -112,7 +112,7 @@
 Licensed under the MIT license.
 """
 
-from queue import Queue, Empty, Full
+from queue import Empty, Full, Queue
 
 from . import __version__
 from .steady_pg import SteadyPgConnection
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 0e34a9b..15ddd6d 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -372,6 +372,7 @@ def _ping_check(self, ping=1, reconnect=True):
                     self._store(con)
                     alive = True
             return alive
+        return None
 
     def dbapi(self):
         """Return the underlying DB-API 2 module of the connection."""
diff --git a/docs/make.py b/docs/make.py
index 28b9625..f6736b7 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -4,6 +4,7 @@
 
 from glob import glob
 from os.path import splitext
+
 from docutils.core import publish_file
 
 print("Creating the documentation...")
@@ -25,11 +26,11 @@
             output = publish_file(
                 writer_name='html5', source=source, destination=destination,
                 enable_exit_status=True,
-                settings_overrides=dict(
-                    stylesheet_path='doc.css',
-                    embed_stylesheet=False,
-                    toc_backlinks=False,
-                    language_code=lang,
-                    exit_status_level=2))
+                settings_overrides={
+                    "stylesheet_path": 'doc.css',
+                    "embed_stylesheet": False,
+                    "toc_backlinks": False,
+                    "language_code": lang,
+                    "exit_status_level": 2})
 
 print("Done.")
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..ce67283
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,80 @@
+[tool.ruff]
+select = [
+  "A",      # flake8-builtins
+  "ARG",    # flake8-unused-arguments
+  "B",      # flake8-bugbear
+  "C4",     # flake8-comprehensions
+  "C90",    # McCabe cyclomatic complexity
+  "DTZ",    # flake8-datetimez
+  "E",      # pycodestyle
+  "EXE",    # flake8-executable
+  "F",      # Pyflakes
+  "G",      # flake8-logging-format
+  "I",      # isort
+  "ICN",    # flake8-import-conventions
+  "INP",    # flake8-no-pep420
+  "INT",    # flake8-gettext
+  "ISC",    # flake8-implicit-str-concat
+  "N",      # pep8-naming
+  "PGH",    # pygrep-hooks
+  "PIE",    # flake8-pie
+  "PL",     # Pylint
+  "PT",     # flake8-pytest-style
+  "PTH",    # flake8-use-pathlib
+  "PYI",    # flake8-pyi
+  "RET",    # flake8-return
+  "RSE",    # flake8-raise
+  "RUF",    # Ruff-specific rules
+  "S",      # flake8-bandit
+  "T10",    # flake8-debugger
+  "TCH",    # flake8-type-checking
+  "TID",    # flake8-tidy-imports
+  "UP",     # pyupgrade
+  "W",      # pycodestyle
+  "YTT",    # flake8-2020
+  # "ANN",  # flake8-annotations
+  # "BLE",  # flake8-blind-except
+  # "COM",  # flake8-commas
+  # "D",    # pydocstyle
+  # "DJ",   # flake8-django
+  # "EM",   # flake8-errmsg
+  # "ERA",  # eradicate
+  # "FBT",  # flake8-boolean-trap
+  # "NPY",  # NumPy-specific rules
+  # "PD",   # pandas-vet
+  # "Q",    # flake8-quotes
+  # "SIM",  # flake8-simplify
+  # "SLF",  # flake8-self
+  # "T20",  # flake8-print
+  # "TRY",  # tryceratops
+]
+# When removing rules from the `ignore` list, do `ruff rule ARG002` to see the docs
+ignore = [
+  "ARG002",
+  "B007",
+  "B904",
+  "E722",
+  "N811",
+  "N818",
+  "PIE790",
+  "PLR5501",
+  "PTH122",
+  "PTH123",
+  "S110",
+]
+line-length = 79
+target-version = "py37"
+
+[tool.ruff.mccabe]
+max-complexity = 31
+
+[tool.ruff.pylint]
+allow-magic-value-types = ["int", "str"]
+max-args = 12
+max-branches = 41
+max-statements = 95
+
+[tool.ruff.per-file-ignores]
+"docs/make.py" = ["INP001"]
+"tests/*" = ["B023", "I001", "N8", "PGH004", "PLR0915", "PT009", "S101"]
+"tests/mock_pg.py" = ["RET505"]
diff --git a/setup.py b/setup.py
index f46fef6..6a3c273 100755
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,9 @@
+#!/usr/bin/env python3
+
 """Setup Script for DBUtils"""
 
 import warnings
+
 try:
     from setuptools import setup
 except ImportError:
diff --git a/tests/mock_pg.py b/tests/mock_pg.py
index 7b4fcdc..3763aa9 100644
--- a/tests/mock_pg.py
+++ b/tests/mock_pg.py
@@ -59,11 +59,13 @@ def query(self, qstr):
             raise InternalError
         if qstr in ('begin', 'end', 'commit', 'rollback'):
             self.session.append(qstr)
+            return None
         elif qstr.startswith('select '):
             self.num_queries += 1
             return qstr[7:]
         elif qstr.startswith('set '):
             self.session.append(qstr[4:])
+            return None
         else:
             raise ProgrammingError
 
diff --git a/tox.ini b/tox.ini
index 6143fb1..42bd7a7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py3{6,7,8,9,10,11}, flake8, manifest, docs, spell
+envlist = py3{6,7,8,9,10,11}, ruff, manifest, docs, spell
 
 [testenv]
 setenv =
@@ -14,11 +14,11 @@ deps = codespell
 commands =
     codespell .
 
-[testenv:flake8]
+[testenv:ruff]
 basepython = python3.10
-deps = flake8
+deps = ruff
 commands =
-    flake8 .
+    ruff .
 
 [testenv:manifest]
 basepython = python3.10

From 594b9fe70e0191251fccf29e475dfeac4119de4e Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 17:43:43 +0200
Subject: [PATCH 51/84] Move to new minor release

---
 .bumpversion.cfg | 2 +-
 setup.py         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 663f79f..7d741e2 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 3.0.3
+current_version = 3.1.0b1
 
 [bumpversion:file:setup.py]
 search = __version__ = '{current_version}'
diff --git a/setup.py b/setup.py
index 6a3c273..603cee3 100755
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@
 
 warnings.filterwarnings('ignore', 'Unknown distribution option')
 
-__version__ = '3.0.3'
+__version__ = '3.1.0b1'
 
 readme = open('README.md').read()
 

From 6e0a4d0c496fa86da8ec8488d35a8e103972c111 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 18:01:37 +0200
Subject: [PATCH 52/84] Require Python 3.7

---
 setup.py | 6 ++++--
 tox.ini  | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/setup.py b/setup.py
index 603cee3..4725628 100755
--- a/setup.py
+++ b/setup.py
@@ -37,7 +37,6 @@
         'License :: OSI Approved :: MIT License',
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
@@ -60,5 +59,8 @@
             'https://github.com/WebwareForPython/DBUtils'},
     platforms=['any'],
     license='MIT License',
-    packages=['dbutils']
+    packages=['dbutils'],
+    extras_require={
+        "pg": ["PyGreSQL >= 5"]
+    }
 )
diff --git a/tox.ini b/tox.ini
index 42bd7a7..1b03272 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py3{6,7,8,9,10,11}, ruff, manifest, docs, spell
+envlist = py3{7,8,9,10,11}, ruff, manifest, docs, spell
 
 [testenv]
 setenv =

From 540cc738389f0c6a2e0e509151f995391d1baa65 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 21:07:36 +0200
Subject: [PATCH 53/84] Use pytest instead of unittest

---
 setup.py                       |    5 +-
 tests/__init__.py              |    5 +-
 tests/mock_db.py               |   15 +
 tests/mock_pg.py               |    1 +
 tests/test_persistent_db.py    |  516 +++----
 tests/test_persistent_pg.py    |  335 ++---
 tests/test_pooled_db.py        | 2505 ++++++++++++++++----------------
 tests/test_pooled_pg.py        |  573 ++++----
 tests/test_simple_pooled_db.py |  260 ++--
 tests/test_simple_pooled_pg.py |  199 +--
 tests/test_steady_db.py        | 1344 ++++++++---------
 tests/test_steady_pg.py        |  582 ++++----
 tests/test_threading_local.py  |  155 +-
 tox.ini                        |    2 +-
 14 files changed, 3312 insertions(+), 3185 deletions(-)

diff --git a/setup.py b/setup.py
index 4725628..e5bb435 100755
--- a/setup.py
+++ b/setup.py
@@ -61,6 +61,7 @@
     license='MIT License',
     packages=['dbutils'],
     extras_require={
-        "pg": ["PyGreSQL >= 5"]
-    }
+        "pg": ["PyGreSQL>=5"]
+    },
+    tests_require=["pytest>=7", "ruff"]
 )
diff --git a/tests/__init__.py b/tests/__init__.py
index ec99602..49922b8 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +1,4 @@
-# DBUtils tests
+"""DBUtils tests"""
+
+# make sure the mock pg module is installed
+from . import mock_pg as pg  # noqa: F401
diff --git a/tests/mock_db.py b/tests/mock_db.py
index ceca9e3..e3965a6 100644
--- a/tests/mock_db.py
+++ b/tests/mock_db.py
@@ -1,8 +1,23 @@
 """This module serves as a mock object for the DB-API 2 module"""
 
+import sys
+
+import pytest
+
+__all__ = ['dbapi']
+
+
 threadsafety = 2
 
 
+@pytest.fixture()
+def dbapi():
+    """Get mock DB API 2 module."""
+    mock_db = sys.modules[__name__]
+    mock_db.threadsafety = 2
+    return mock_db
+
+
 class Error(Exception):
     pass
 
diff --git a/tests/mock_pg.py b/tests/mock_pg.py
index 3763aa9..bbb393c 100644
--- a/tests/mock_pg.py
+++ b/tests/mock_pg.py
@@ -2,6 +2,7 @@
 
 import sys
 
+
 sys.modules['pg'] = sys.modules[__name__]
 
 
diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index 6f27108..25897fd 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -10,287 +10,293 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
+from queue import Queue, Empty
+from threading import Thread
 
-from . import mock_db as dbapi
+import pytest
 
-from dbutils.persistent_db import PersistentDB, local
+from .mock_db import dbapi  # noqa: F401
 
+from dbutils.persistent_db import NotSupportedError, PersistentDB, local
 
-class TestPersistentDB(unittest.TestCase):
 
-    def setUp(self):
-        dbapi.threadsafety = 1
+def test_version():
+    from dbutils import __version__, persistent_db
+    assert persistent_db.__version__ == __version__
+    assert PersistentDB.version == __version__
 
-    def test_version(self):
-        from dbutils import __version__, persistent_db
-        self.assertEqual(persistent_db.__version__, __version__)
-        self.assertEqual(PersistentDB.version, __version__)
 
-    def test_no_threadsafety(self):
-        from dbutils.persistent_db import NotSupportedError
-        for dbapi.threadsafety in (None, 0):
-            self.assertRaises(NotSupportedError, PersistentDB, dbapi)
+@pytest.mark.parametrize("threadsafety", [None, 0])
+def test_no_threadsafety(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    with pytest.raises(NotSupportedError):
+        PersistentDB(dbapi)
 
-    def test_close(self):
-        for closeable in (False, True):
-            persist = PersistentDB(dbapi, closeable=closeable)
-            db = persist.connection()
-            self.assertTrue(db._con.valid)
-            db.close()
-            self.assertTrue(closeable ^ db._con.valid)
-            db.close()
-            self.assertTrue(closeable ^ db._con.valid)
-            db._close()
-            self.assertFalse(db._con.valid)
-            db._close()
-            self.assertFalse(db._con.valid)
-
-    def test_connection(self):
-        persist = PersistentDB(dbapi)
-        db = persist.connection()
-        db_con = db._con
-        self.assertIsNone(db_con.database)
-        self.assertIsNone(db_con.user)
-        db2 = persist.connection()
-        self.assertEqual(db, db2)
-        db3 = persist.dedicated_connection()
-        self.assertEqual(db, db3)
-        db3.close()
-        db2.close()
-        db.close()
-
-    def test_threads(self):
-        num_threads = 3
-        persist = PersistentDB(dbapi, closeable=True)
-        from queue import Queue, Empty
-        query_queue, result_queue = [], []
-        for i in range(num_threads):
-            query_queue.append(Queue(1))
-            result_queue.append(Queue(1))
-
-        def run_queries(i):
-            this_db = persist.connection()
-            while 1:
-                try:
-                    try:
-                        q = query_queue[i].get(1, 1)
-                    except TypeError:
-                        q = query_queue[i].get(1)
-                except Empty:
-                    q = None
-                if not q:
-                    break
-                db = persist.connection()
-                if db != this_db:
-                    r = 'error - not persistent'
-                else:
-                    if q == 'ping':
-                        r = 'ok - thread alive'
-                    elif q == 'close':
-                        db.close()
-                        r = 'ok - connection closed'
-                    else:
-                        cursor = db.cursor()
-                        cursor.execute(q)
-                        r = cursor.fetchone()
-                        cursor.close()
-                r = f'{i}({db._usage}): {r}'
+
+@pytest.mark.parametrize("closeable", [False, True])
+def test_close(dbapi, closeable):  # noqa: F811
+    persist = PersistentDB(dbapi, closeable=closeable)
+    db = persist.connection()
+    assert db._con.valid is True
+    db.close()
+    assert closeable ^ db._con.valid
+    db.close()
+    assert closeable ^ db._con.valid
+    db._close()
+    assert db._con.valid is False
+    db._close()
+    assert db._con.valid is False
+
+
+def test_connection(dbapi):  # noqa: F811
+    persist = PersistentDB(dbapi)
+    db = persist.connection()
+    db_con = db._con
+    assert db_con.database is None
+    assert db_con.user is None
+    db2 = persist.connection()
+    assert db == db2
+    db3 = persist.dedicated_connection()
+    assert db == db3
+    db3.close()
+    db2.close()
+    db.close()
+
+
+def test_threads(dbapi):  # noqa: F811
+    num_threads = 3
+    persist = PersistentDB(dbapi, closeable=True)
+    query_queue, result_queue = [], []
+    for i in range(num_threads):
+        query_queue.append(Queue(1))
+        result_queue.append(Queue(1))
+
+    def run_queries(i):
+        this_db = persist.connection()
+        db = None
+        while True:
+            try:
                 try:
-                    result_queue[i].put(r, 1, 1)
+                    q = query_queue[i].get(1, 1)
                 except TypeError:
-                    result_queue[i].put(r, 1)
-            db.close()
-
-        from threading import Thread
-        threads = []
-        for i in range(num_threads):
-            thread = Thread(target=run_queries, args=(i,))
-            threads.append(thread)
-            thread.start()
-        for i in range(num_threads):
+                    q = query_queue[i].get(1)
+            except Empty:
+                q = None
+            if not q:
+                break
+            db = persist.connection()
+            if db != this_db:
+                r = 'error - not persistent'
+            else:
+                if q == 'ping':
+                    r = 'ok - thread alive'
+                elif q == 'close':
+                    db.close()
+                    r = 'ok - connection closed'
+                else:
+                    cursor = db.cursor()
+                    cursor.execute(q)
+                    r = cursor.fetchone()
+                    cursor.close()
+            r = f'{i}({db._usage}): {r}'
             try:
-                query_queue[i].put('ping', 1, 1)
+                result_queue[i].put(r, 1, 1)
             except TypeError:
-                query_queue[i].put('ping', 1)
-        for i in range(num_threads):
+                result_queue[i].put(r, 1)
+        if db:
+            db.close()
+
+    threads = []
+    for i in range(num_threads):
+        thread = Thread(target=run_queries, args=(i,))
+        threads.append(thread)
+        thread.start()
+    for i in range(num_threads):
+        try:
+            query_queue[i].put('ping', 1, 1)
+        except TypeError:
+            query_queue[i].put('ping', 1)
+    for i in range(num_threads):
+        try:
+            r = result_queue[i].get(1, 1)
+        except TypeError:
+            r = result_queue[i].get(1)
+        assert r == f'{i}(0): ok - thread alive'
+        assert threads[i].is_alive()
+    for i in range(num_threads):
+        for j in range(i + 1):
             try:
+                query_queue[i].put(f'select test{j}', 1, 1)
                 r = result_queue[i].get(1, 1)
             except TypeError:
+                query_queue[i].put(f'select test{j}', 1)
                 r = result_queue[i].get(1)
-            self.assertEqual(r, f'{i}(0): ok - thread alive')
-            self.assertTrue(threads[i].is_alive())
-        for i in range(num_threads):
-            for j in range(i + 1):
-                try:
-                    query_queue[i].put(f'select test{j}', 1, 1)
-                    r = result_queue[i].get(1, 1)
-                except TypeError:
-                    query_queue[i].put(f'select test{j}', 1)
-                    r = result_queue[i].get(1)
-                self.assertEqual(r, f'{i}({j + 1}): test{j}')
-        try:
-            query_queue[1].put('select test4', 1, 1)
-        except TypeError:
-            query_queue[1].put('select test4', 1)
+            assert r == f'{i}({j + 1}): test{j}'
+    try:
+        query_queue[1].put('select test4', 1, 1)
+    except TypeError:
+        query_queue[1].put('select test4', 1)
+    try:
+        r = result_queue[1].get(1, 1)
+    except TypeError:
+        r = result_queue[1].get(1)
+    assert r == '1(3): test4'
+    try:
+        query_queue[1].put('close', 1, 1)
+        r = result_queue[1].get(1, 1)
+    except TypeError:
+        query_queue[1].put('close', 1)
+        r = result_queue[1].get(1)
+    assert r == '1(3): ok - connection closed'
+    for j in range(2):
         try:
+            query_queue[1].put(f'select test{j}', 1, 1)
             r = result_queue[1].get(1, 1)
         except TypeError:
+            query_queue[1].put(f'select test{j}', 1)
             r = result_queue[1].get(1)
-        self.assertEqual(r, '1(3): test4')
+        assert r == f'1({j + 1}): test{j}'
+    for i in range(num_threads):
+        assert threads[i].is_alive()
         try:
-            query_queue[1].put('close', 1, 1)
-            r = result_queue[1].get(1, 1)
+            query_queue[i].put('ping', 1, 1)
         except TypeError:
-            query_queue[1].put('close', 1)
-            r = result_queue[1].get(1)
-        self.assertEqual(r, '1(3): ok - connection closed')
-        for j in range(2):
-            try:
-                query_queue[1].put(f'select test{j}', 1, 1)
-                r = result_queue[1].get(1, 1)
-            except TypeError:
-                query_queue[1].put(f'select test{j}', 1)
-                r = result_queue[1].get(1)
-            self.assertEqual(r, f'1({j + 1}): test{j}')
-        for i in range(num_threads):
-            self.assertTrue(threads[i].is_alive())
-            try:
-                query_queue[i].put('ping', 1, 1)
-            except TypeError:
-                query_queue[i].put('ping', 1)
-        for i in range(num_threads):
-            try:
-                r = result_queue[i].get(1, 1)
-            except TypeError:
-                r = result_queue[i].get(1)
-            self.assertEqual(r, f'{i}({i + 1}): ok - thread alive')
-            self.assertTrue(threads[i].is_alive())
-        for i in range(num_threads):
-            try:
-                query_queue[i].put(None, 1, 1)
-            except TypeError:
-                query_queue[i].put(None, 1)
-
-    def test_maxusage(self):
-        persist = PersistentDB(dbapi, 20)
-        db = persist.connection()
-        self.assertEqual(db._maxusage, 20)
-        for i in range(100):
-            cursor = db.cursor()
-            cursor.execute(f'select test{i}')
-            r = cursor.fetchone()
-            cursor.close()
-            self.assertEqual(r, f'test{i}')
-            self.assertTrue(db._con.valid)
-            j = i % 20 + 1
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db._con.num_uses, j)
-            self.assertEqual(db._con.num_queries, j)
-
-    def test_setsession(self):
-        persist = PersistentDB(dbapi, 3, ('set datestyle',))
-        db = persist.connection()
-        self.assertEqual(db._maxusage, 3)
-        self.assertEqual(db._setsession_sql, ('set datestyle',))
-        self.assertEqual(db._con.session, ['datestyle'])
+            query_queue[i].put('ping', 1)
+    for i in range(num_threads):
+        try:
+            r = result_queue[i].get(1, 1)
+        except TypeError:
+            r = result_queue[i].get(1)
+        assert r == f'{i}({i + 1}): ok - thread alive'
+        assert threads[i].is_alive()
+    for i in range(num_threads):
+        try:
+            query_queue[i].put(None, 1, 1)
+        except TypeError:
+            query_queue[i].put(None, 1)
+
+
+def test_maxusage(dbapi):  # noqa: F811
+    persist = PersistentDB(dbapi, 20)
+    db = persist.connection()
+    assert db._maxusage == 20
+    for i in range(100):
         cursor = db.cursor()
-        cursor.execute('set test')
-        cursor.fetchone()
+        cursor.execute(f'select test{i}')
+        r = cursor.fetchone()
         cursor.close()
-        for i in range(3):
-            self.assertEqual(db._con.session, ['datestyle', 'test'])
-            cursor = db.cursor()
-            cursor.execute('select test')
-            cursor.fetchone()
-            cursor.close()
-        self.assertEqual(db._con.session, ['datestyle'])
-
-    def test_threadlocal(self):
-        persist = PersistentDB(dbapi)
-        self.assertTrue(isinstance(persist.thread, local))
-
-        class threadlocal:
-            pass
-
-        persist = PersistentDB(dbapi, threadlocal=threadlocal)
-        self.assertTrue(isinstance(persist.thread, threadlocal))
-
-    def test_ping_check(self):
-        Connection = dbapi.Connection
-        Connection.has_ping = True
-        Connection.num_pings = 0
-        persist = PersistentDB(dbapi, 0, None, None, 0, True)
-        db = persist.connection()
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 0)
-        db.close()
-        db = persist.connection()
-        self.assertFalse(db._con.valid)
-        self.assertEqual(Connection.num_pings, 0)
-        persist = PersistentDB(dbapi, 0, None, None, 1, True)
-        db = persist.connection()
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 1)
-        db.close()
-        db = persist.connection()
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 2)
-        persist = PersistentDB(dbapi, 0, None, None, 2, True)
-        db = persist.connection()
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 2)
-        db.close()
-        db = persist.connection()
-        self.assertFalse(db._con.valid)
-        self.assertEqual(Connection.num_pings, 2)
-        cursor = db.cursor()
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 3)
-        cursor.execute('select test')
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 3)
-        persist = PersistentDB(dbapi, 0, None, None, 4, True)
-        db = persist.connection()
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 3)
-        db.close()
-        db = persist.connection()
-        self.assertFalse(db._con.valid)
-        self.assertEqual(Connection.num_pings, 3)
-        cursor = db.cursor()
-        db._con.close()
-        self.assertFalse(db._con.valid)
-        self.assertEqual(Connection.num_pings, 3)
-        cursor.execute('select test')
-        self.assertTrue(db._con.valid)
-        self.assertEqual(Connection.num_pings, 4)
-        Connection.has_ping = False
-        Connection.num_pings = 0
-
-    def test_failed_transaction(self):
-        persist = PersistentDB(dbapi)
-        db = persist.connection()
+        assert r == f'test{i}'
+        assert db._con.valid is True
+        j = i % 20 + 1
+        assert db._usage == j
+        assert db._con.num_uses == j
+        assert db._con.num_queries == j
+
+
+def test_setsession(dbapi):  # noqa: F811
+    persist = PersistentDB(dbapi, 3, ('set datestyle',))
+    db = persist.connection()
+    assert db._maxusage == 3
+    assert db._setsession_sql == ('set datestyle',)
+    assert db._con.session == ['datestyle']
+    cursor = db.cursor()
+    cursor.execute('set test')
+    cursor.fetchone()
+    cursor.close()
+    for i in range(3):
+        assert db._con.session == ['datestyle', 'test']
         cursor = db.cursor()
-        db._con.close()
-        cursor.execute('select test')
-        db.begin()
-        db._con.close()
-        self.assertRaises(dbapi.InternalError, cursor.execute, 'select test')
-        cursor.execute('select test')
-        db.begin()
-        db.cancel()
-        db._con.close()
         cursor.execute('select test')
+        cursor.fetchone()
+        cursor.close()
+    assert db._con.session == ['datestyle']
+
+
+def test_threadlocal(dbapi):  # noqa: F811
+    persist = PersistentDB(dbapi)
+    assert isinstance(persist.thread, local)
 
-    def test_context_manager(self):
-        persist = PersistentDB(dbapi)
-        with persist.connection() as db:
-            with db.cursor() as cursor:
-                cursor.execute('select test')
-                r = cursor.fetchone()
-            self.assertEqual(r, 'test')
+    class Threadlocal:
+        pass
 
+    persist = PersistentDB(dbapi, threadlocal=Threadlocal)
+    assert isinstance(persist.thread, Threadlocal)
 
-if __name__ == '__main__':
-    unittest.main()
+
+def test_ping_check(dbapi):  # noqa: F811
+    con_cls = dbapi.Connection
+    con_cls.has_ping = True
+    con_cls.num_pings = 0
+    persist = PersistentDB(dbapi, 0, None, None, 0, True)
+    db = persist.connection()
+    assert db._con.valid is True
+    assert con_cls.num_pings == 0
+    db.close()
+    db = persist.connection()
+    assert db._con.valid is False
+    assert con_cls.num_pings == 0
+    persist = PersistentDB(dbapi, 0, None, None, 1, True)
+    db = persist.connection()
+    assert db._con.valid is True
+    assert con_cls.num_pings == 1
+    db.close()
+    db = persist.connection()
+    assert db._con.valid is True
+    assert con_cls.num_pings == 2
+    persist = PersistentDB(dbapi, 0, None, None, 2, True)
+    db = persist.connection()
+    assert db._con.valid is True
+    assert con_cls.num_pings == 2
+    db.close()
+    db = persist.connection()
+    assert db._con.valid is False
+    assert con_cls.num_pings == 2
+    cursor = db.cursor()
+    assert db._con.valid is True
+    assert con_cls.num_pings == 3
+    cursor.execute('select test')
+    assert db._con.valid is True
+    assert con_cls.num_pings == 3
+    persist = PersistentDB(dbapi, 0, None, None, 4, True)
+    db = persist.connection()
+    assert db._con.valid is True
+    assert con_cls.num_pings == 3
+    db.close()
+    db = persist.connection()
+    assert db._con.valid is False
+    assert con_cls.num_pings == 3
+    cursor = db.cursor()
+    db._con.close()
+    assert db._con.valid is False
+    assert con_cls.num_pings == 3
+    cursor.execute('select test')
+    assert db._con.valid is True
+    assert con_cls.num_pings == 4
+    con_cls.has_ping = False
+    con_cls.num_pings = 0
+
+
+def test_failed_transaction(dbapi):  # noqa: F811
+    persist = PersistentDB(dbapi)
+    db = persist.connection()
+    cursor = db.cursor()
+    db._con.close()
+    cursor.execute('select test')
+    db.begin()
+    db._con.close()
+    with pytest.raises(dbapi.InternalError):
+        cursor.execute('select test')
+    cursor.execute('select test')
+    db.begin()
+    db.cancel()
+    db._con.close()
+    cursor.execute('select test')
+
+
+def test_context_manager(dbapi):  # noqa: F811
+    persist = PersistentDB(dbapi)
+    with persist.connection() as db:
+        with db.cursor() as cursor:
+            cursor.execute('select test')
+            r = cursor.fetchone()
+        assert r == 'test'
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index 3c8ce83..bd6c21a 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -10,188 +10,191 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
+from queue import Queue, Empty
+from threading import Thread
 
-from . import mock_pg as pg
+import pg
+import pytest
 
 from dbutils.persistent_pg import PersistentPg
 
 
-class TestPersistentPg(unittest.TestCase):
-
-    def test_version(self):
-        from dbutils import __version__, persistent_pg
-        self.assertEqual(persistent_pg.__version__, __version__)
-        self.assertEqual(PersistentPg.version, __version__)
-
-    def test_close(self):
-        for closeable in (False, True):
-            persist = PersistentPg(closeable=closeable)
-            db = persist.connection()
-            self.assertTrue(db._con.db and db._con.valid)
-            db.close()
-            self.assertTrue(
-                closeable ^ (db._con.db is not None and db._con.valid))
-            db.close()
-            self.assertTrue(
-                closeable ^ (db._con.db is not None and db._con.valid))
-            db._close()
-            self.assertFalse(db._con.db and db._con.valid)
-            db._close()
-            self.assertFalse(db._con.db and db._con.valid)
-
-    def test_threads(self):
-        num_threads = 3
-        persist = PersistentPg()
-        from queue import Queue, Empty
-        query_queue, result_queue = [], []
-        for i in range(num_threads):
-            query_queue.append(Queue(1))
-            result_queue.append(Queue(1))
-
-        def run_queries(i):
-            this_db = persist.connection().db
-            while 1:
-                try:
-                    try:
-                        q = query_queue[i].get(1, 1)
-                    except TypeError:
-                        q = query_queue[i].get(1)
-                except Empty:
-                    q = None
-                if not q:
-                    break
-                db = persist.connection()
-                if db.db != this_db:
-                    r = 'error - not persistent'
-                else:
-                    if q == 'ping':
-                        r = 'ok - thread alive'
-                    elif q == 'close':
-                        db.db.close()
-                        r = 'ok - connection closed'
-                    else:
-                        r = db.query(q)
-                r = f'{i}({db._usage}): {r}'
+def test_version():
+    from dbutils import __version__, persistent_pg
+    assert persistent_pg.__version__ == __version__
+    assert PersistentPg.version == __version__
+
+
+@pytest.mark.parametrize("closeable", [False, True])
+def test_close(closeable):
+    persist = PersistentPg(closeable=closeable)
+    db = persist.connection()
+    assert db._con.db
+    assert db._con.valid is True
+    db.close()
+    assert closeable ^ (db._con.db is not None and db._con.valid)
+    db.close()
+    assert closeable ^ (db._con.db is not None and db._con.valid)
+    db._close()
+    assert not db._con.db
+    db._close()
+    assert not db._con.db
+
+
+def test_threads():
+    num_threads = 3
+    persist = PersistentPg()
+    query_queue, result_queue = [], []
+    for i in range(num_threads):
+        query_queue.append(Queue(1))
+        result_queue.append(Queue(1))
+
+    def run_queries(i):
+        this_db = persist.connection().db
+        db = None
+        while True:
+            try:
                 try:
-                    result_queue[i].put(r, 1, 1)
+                    q = query_queue[i].get(1, 1)
                 except TypeError:
-                    result_queue[i].put(r, 1)
-            db.close()
-
-        from threading import Thread
-        threads = []
-        for i in range(num_threads):
-            thread = Thread(target=run_queries, args=(i,))
-            threads.append(thread)
-            thread.start()
-        for i in range(num_threads):
+                    q = query_queue[i].get(1)
+            except Empty:
+                q = None
+            if not q:
+                break
+            db = persist.connection()
+            if db.db != this_db:
+                r = 'error - not persistent'
+            else:
+                if q == 'ping':
+                    r = 'ok - thread alive'
+                elif q == 'close':
+                    db.db.close()
+                    r = 'ok - connection closed'
+                else:
+                    r = db.query(q)
+            r = f'{i}({db._usage}): {r}'
             try:
-                query_queue[i].put('ping', 1, 1)
+                result_queue[i].put(r, 1, 1)
             except TypeError:
-                query_queue[i].put('ping', 1)
-        for i in range(num_threads):
+                result_queue[i].put(r, 1)
+        if db:
+            db.close()
+
+    threads = []
+    for i in range(num_threads):
+        thread = Thread(target=run_queries, args=(i,))
+        threads.append(thread)
+        thread.start()
+    for i in range(num_threads):
+        try:
+            query_queue[i].put('ping', 1, 1)
+        except TypeError:
+            query_queue[i].put('ping', 1)
+    for i in range(num_threads):
+        try:
+            r = result_queue[i].get(1, 1)
+        except TypeError:
+            r = result_queue[i].get(1)
+        assert r == f'{i}(0): ok - thread alive'
+        assert threads[i].is_alive()
+    for i in range(num_threads):
+        for j in range(i + 1):
             try:
+                query_queue[i].put(f'select test{j}', 1, 1)
                 r = result_queue[i].get(1, 1)
             except TypeError:
+                query_queue[i].put(f'select test{j}', 1)
                 r = result_queue[i].get(1)
-            self.assertEqual(r, f'{i}(0): ok - thread alive')
-            self.assertTrue(threads[i].is_alive())
-        for i in range(num_threads):
-            for j in range(i + 1):
-                try:
-                    query_queue[i].put(f'select test{j}', 1, 1)
-                    r = result_queue[i].get(1, 1)
-                except TypeError:
-                    query_queue[i].put(f'select test{j}', 1)
-                    r = result_queue[i].get(1)
-                self.assertEqual(r, f'{i}({j + 1}): test{j}')
+            assert r == f'{i}({j + 1}): test{j}'
+    try:
+        query_queue[1].put('select test4', 1, 1)
+        r = result_queue[1].get(1, 1)
+    except TypeError:
+        query_queue[1].put('select test4', 1)
+        r = result_queue[1].get(1)
+    assert r == '1(3): test4'
+    try:
+        query_queue[1].put('close', 1, 1)
+        r = result_queue[1].get(1, 1)
+    except TypeError:
+        query_queue[1].put('close', 1)
+        r = result_queue[1].get(1)
+    assert r == '1(3): ok - connection closed'
+    for j in range(2):
         try:
-            query_queue[1].put('select test4', 1, 1)
+            query_queue[1].put(f'select test{j}', 1, 1)
             r = result_queue[1].get(1, 1)
         except TypeError:
-            query_queue[1].put('select test4', 1)
+            query_queue[1].put(f'select test{j}', 1)
             r = result_queue[1].get(1)
-        self.assertEqual(r, '1(3): test4')
+        assert r == f'1({j + 1}): test{j}'
+    for i in range(num_threads):
+        assert threads[i].is_alive()
         try:
-            query_queue[1].put('close', 1, 1)
-            r = result_queue[1].get(1, 1)
+            query_queue[i].put('ping', 1, 1)
         except TypeError:
-            query_queue[1].put('close', 1)
-            r = result_queue[1].get(1)
-        self.assertEqual(r, '1(3): ok - connection closed')
-        for j in range(2):
-            try:
-                query_queue[1].put(f'select test{j}', 1, 1)
-                r = result_queue[1].get(1, 1)
-            except TypeError:
-                query_queue[1].put(f'select test{j}', 1)
-                r = result_queue[1].get(1)
-            self.assertEqual(r, f'1({j + 1}): test{j}')
-        for i in range(num_threads):
-            self.assertTrue(threads[i].is_alive())
-            try:
-                query_queue[i].put('ping', 1, 1)
-            except TypeError:
-                query_queue[i].put('ping', 1)
-        for i in range(num_threads):
-            try:
-                r = result_queue[i].get(1, 1)
-            except TypeError:
-                r = result_queue[i].get(1)
-            self.assertEqual(r, f'{i}({i + 1}): ok - thread alive')
-            self.assertTrue(threads[i].is_alive())
-        for i in range(num_threads):
-            try:
-                query_queue[i].put(None, 1, 1)
-            except TypeError:
-                query_queue[i].put(None, 1)
-
-    def test_maxusage(self):
-        persist = PersistentPg(20)
-        db = persist.connection()
-        self.assertEqual(db._maxusage, 20)
-        for i in range(100):
-            r = db.query(f'select test{i}')
-            self.assertEqual(r, f'test{i}')
-            self.assertTrue(db.db.status)
-            j = i % 20 + 1
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db.num_queries, j)
-
-    def test_setsession(self):
-        persist = PersistentPg(3, ('set datestyle',))
-        db = persist.connection()
-        self.assertEqual(db._maxusage, 3)
-        self.assertEqual(db._setsession_sql, ('set datestyle',))
-        self.assertEqual(db.db.session, ['datestyle'])
-        db.query('set test')
-        for i in range(3):
-            self.assertEqual(db.db.session, ['datestyle', 'test'])
-            db.query('select test')
-        self.assertEqual(db.db.session, ['datestyle'])
-
-    def test_failed_transaction(self):
-        persist = PersistentPg()
-        db = persist.connection()
-        db._con.close()
-        self.assertEqual(db.query('select test'), 'test')
-        db.begin()
-        db._con.close()
-        self.assertRaises(pg.InternalError, db.query, 'select test')
-        self.assertEqual(db.query('select test'), 'test')
-        db.begin()
-        self.assertEqual(db.query('select test'), 'test')
-        db.rollback()
-        db._con.close()
-        self.assertEqual(db.query('select test'), 'test')
-
-    def test_context_manager(self):
-        persist = PersistentPg()
-        with persist.connection() as db:
-            db.query('select test')
-            self.assertEqual(db.num_queries, 1)
-
-
-if __name__ == '__main__':
-    unittest.main()
+            query_queue[i].put('ping', 1)
+    for i in range(num_threads):
+        try:
+            r = result_queue[i].get(1, 1)
+        except TypeError:
+            r = result_queue[i].get(1)
+        assert r == f'{i}({i + 1}): ok - thread alive'
+        assert threads[i].is_alive()
+    for i in range(num_threads):
+        try:
+            query_queue[i].put(None, 1, 1)
+        except TypeError:
+            query_queue[i].put(None, 1)
+
+
+def test_maxusage():
+    persist = PersistentPg(20)
+    db = persist.connection()
+    assert db._maxusage == 20
+    for i in range(100):
+        r = db.query(f'select test{i}')
+        assert r == f'test{i}'
+        assert db.db.status
+        j = i % 20 + 1
+        assert db._usage == j
+        assert db.num_queries == j
+
+
+def test_setsession():
+    persist = PersistentPg(3, ('set datestyle',))
+    db = persist.connection()
+    assert db._maxusage == 3
+    assert db._setsession_sql == ('set datestyle',)
+    assert db.db.session == ['datestyle']
+    db.query('set test')
+    for i in range(3):
+        assert db.db.session == ['datestyle', 'test']
+        db.query('select test')
+    assert db.db.session == ['datestyle']
+
+
+def test_failed_transaction():
+    persist = PersistentPg()
+    db = persist.connection()
+    db._con.close()
+    assert db.query('select test') == 'test'
+    db.begin()
+    db._con.close()
+    with pytest.raises(pg.InternalError):
+        db.query('select test')
+    assert db.query('select test') == 'test'
+    db.begin()
+    assert db.query('select test') == 'test'
+    db.rollback()
+    db._con.close()
+    assert db.query('select test') == 'test'
+
+
+def test_context_manager():
+    persist = PersistentPg()
+    with persist.connection() as db:
+        db.query('select test')
+        assert db.num_queries == 1
+
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 3beb2d4..ebbe88d 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -10,1248 +10,1295 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
+from queue import Queue, Empty
+from threading import Thread
 
-from . import mock_db as dbapi
+import pytest
+
+from .mock_db import dbapi  # noqa: F401
 
 from dbutils.pooled_db import (
-    PooledDB, SharedDBConnection, InvalidConnection, TooManyConnections)
-
-
-class TestPooledDB(unittest.TestCase):
-
-    def test_version(self):
-        from dbutils import __version__, pooled_db
-        self.assertEqual(pooled_db.__version__, __version__)
-        self.assertEqual(PooledDB.version, __version__)
-
-    def test_no_threadsafety(self):
-        from dbutils.pooled_db import NotSupportedError
-        for threadsafety in (None, 0):
-            dbapi.threadsafety = threadsafety
-            self.assertRaises(NotSupportedError, PooledDB, dbapi)
-
-    def test_threadsafety(self):
-        for threadsafety in (1, 2, 3):
-            dbapi.threadsafety = threadsafety
-            pool = PooledDB(dbapi, 0, 0, 1)
-            self.assertTrue(hasattr(pool, '_maxshared'))
-            if threadsafety > 1:
-                self.assertEqual(pool._maxshared, 1)
-                self.assertTrue(hasattr(pool, '_shared_cache'))
-            else:
-                self.assertEqual(pool._maxshared, 0)
-                self.assertFalse(hasattr(pool, '_shared_cache'))
-
-    def test_create_connection(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(
-                dbapi, 1, 1, 1, 0, False, None, None, True, None, None,
-                'PooledDBTestDB', user='PooledDBTestUser')
-            self.assertTrue(hasattr(pool, '_idle_cache'))
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertTrue(hasattr(pool, '_shared_cache'))
-                self.assertEqual(len(pool._shared_cache), 0)
-            else:
-                self.assertFalse(hasattr(pool, '_shared_cache'))
-            self.assertTrue(hasattr(pool, '_maxusage'))
-            self.assertIsNone(pool._maxusage)
-            self.assertTrue(hasattr(pool, '_setsession'))
-            self.assertIsNone(pool._setsession)
-            con = pool._idle_cache[0]
-            from dbutils.steady_db import SteadyDBConnection
-            self.assertTrue(isinstance(con, SteadyDBConnection))
-            self.assertTrue(hasattr(con, '_maxusage'))
-            self.assertEqual(con._maxusage, 0)
-            self.assertTrue(hasattr(con, '_setsession_sql'))
-            self.assertIsNone(con._setsession_sql)
-            db = pool.connection()
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-            self.assertTrue(hasattr(db, '_con'))
-            self.assertEqual(db._con, con)
-            self.assertTrue(hasattr(db, 'cursor'))
-            self.assertTrue(hasattr(db, '_usage'))
-            self.assertEqual(db._usage, 0)
-            self.assertTrue(hasattr(con, '_con'))
-            db_con = con._con
-            self.assertTrue(hasattr(db_con, 'database'))
-            self.assertEqual(db_con.database, 'PooledDBTestDB')
-            self.assertTrue(hasattr(db_con, 'user'))
-            self.assertEqual(db_con.user, 'PooledDBTestUser')
-            self.assertTrue(hasattr(db_con, 'open_cursors'))
-            self.assertEqual(db_con.open_cursors, 0)
-            self.assertTrue(hasattr(db_con, 'num_uses'))
-            self.assertEqual(db_con.num_uses, 0)
-            self.assertTrue(hasattr(db_con, 'num_queries'))
-            self.assertEqual(db_con.num_queries, 0)
-            cursor = db.cursor()
-            self.assertEqual(db_con.open_cursors, 1)
-            cursor.execute('select test')
-            r = cursor.fetchone()
-            cursor.close()
-            self.assertEqual(db_con.open_cursors, 0)
-            self.assertEqual(r, 'test')
-            self.assertEqual(db_con.num_queries, 1)
-            self.assertEqual(db._usage, 1)
-            cursor = db.cursor()
-            self.assertEqual(db_con.open_cursors, 1)
-            cursor.execute('set sessiontest')
-            cursor2 = db.cursor()
-            self.assertEqual(db_con.open_cursors, 2)
-            cursor2.close()
-            self.assertEqual(db_con.open_cursors, 1)
-            cursor.close()
-            self.assertEqual(db_con.open_cursors, 0)
-            self.assertEqual(db_con.num_queries, 1)
-            self.assertEqual(db._usage, 2)
-            self.assertEqual(
-                db_con.session, ['rollback', 'sessiontest'])
-            pool = PooledDB(dbapi, 1, 1, 1)
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db = pool.connection()
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-            db.close()
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db = pool.connection(True)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-            db.close()
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db = pool.connection(False)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            self.assertEqual(db._usage, 0)
-            db_con = db._con._con
-            self.assertIsNone(db_con.database)
-            self.assertIsNone(db_con.user)
-            db.close()
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db = pool.dedicated_connection()
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            self.assertEqual(db._usage, 0)
-            db_con = db._con._con
-            self.assertIsNone(db_con.database)
-            self.assertIsNone(db_con.user)
-            db.close()
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            pool = PooledDB(dbapi, 0, 0, 0, 0, False, 3, ('set datestyle',))
-            self.assertEqual(pool._maxusage, 3)
-            self.assertEqual(pool._setsession, ('set datestyle',))
-            con = pool.connection()._con
-            self.assertEqual(con._maxusage, 3)
-            self.assertEqual(con._setsession_sql, ('set datestyle',))
-
-    def test_close_connection(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(
-                dbapi, 0, 1, 1, 0, False, None, None, True, None, None,
-                'PooledDBTestDB', user='PooledDBTestUser')
-            self.assertTrue(hasattr(pool, '_idle_cache'))
-            self.assertEqual(len(pool._idle_cache), 0)
-            db = pool.connection()
-            self.assertTrue(hasattr(db, '_con'))
-            con = db._con
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-                self.assertTrue(hasattr(db, '_shared_con'))
-                shared_con = db._shared_con
-                self.assertEqual(pool._shared_cache[0], shared_con)
-                self.assertTrue(hasattr(shared_con, 'shared'))
-                self.assertEqual(shared_con.shared, 1)
-                self.assertTrue(hasattr(shared_con, 'con'))
-                self.assertEqual(shared_con.con, con)
-            from dbutils.steady_db import SteadyDBConnection
-            self.assertTrue(isinstance(con, SteadyDBConnection))
-            self.assertTrue(hasattr(con, '_con'))
-            db_con = con._con
-            self.assertTrue(hasattr(db_con, 'num_queries'))
-            self.assertEqual(db._usage, 0)
-            self.assertEqual(db_con.num_queries, 0)
-            db.cursor().execute('select test')
-            self.assertEqual(db._usage, 1)
-            self.assertEqual(db_con.num_queries, 1)
-            db.close()
-            self.assertIsNone(db._con)
-            if shareable:
-                self.assertIsNone(db._shared_con)
-                self.assertEqual(shared_con.shared, 0)
-            self.assertRaises(InvalidConnection, getattr, db, '_usage')
-            self.assertFalse(hasattr(db_con, '_num_queries'))
-            self.assertEqual(len(pool._idle_cache), 1)
-            self.assertEqual(pool._idle_cache[0]._con, db_con)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db.close()
-            if shareable:
-                self.assertEqual(shared_con.shared, 0)
-            db = pool.connection()
-            self.assertEqual(db._con, con)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-                shared_con = db._shared_con
-                self.assertEqual(pool._shared_cache[0], shared_con)
-                self.assertEqual(shared_con.con, con)
-                self.assertEqual(shared_con.shared, 1)
-            self.assertEqual(db._usage, 1)
-            self.assertEqual(db_con.num_queries, 1)
-            self.assertTrue(hasattr(db_con, 'database'))
-            self.assertEqual(db_con.database, 'PooledDBTestDB')
-            self.assertTrue(hasattr(db_con, 'user'))
-            self.assertEqual(db_con.user, 'PooledDBTestUser')
-            db.cursor().execute('select test')
-            self.assertEqual(db_con.num_queries, 2)
-            db.cursor().execute('select test')
-            self.assertEqual(db_con.num_queries, 3)
-            db.close()
-            self.assertEqual(len(pool._idle_cache), 1)
-            self.assertEqual(pool._idle_cache[0]._con, db_con)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db = pool.connection(False)
-            self.assertEqual(db._con, con)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db.close()
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-
-    def test_close_all(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi, 10)
-            self.assertEqual(len(pool._idle_cache), 10)
-            pool.close()
-            self.assertEqual(len(pool._idle_cache), 0)
-            pool = PooledDB(dbapi, 10)
-            closed = ['no']
-
-            def close(what=closed):
-                what[0] = 'yes'
-
-            pool._idle_cache[7]._con.close = close
-            self.assertEqual(closed, ['no'])
-            del pool
-            self.assertEqual(closed, ['yes'])
-            pool = PooledDB(dbapi, 10, 10, 5)
-            self.assertEqual(len(pool._idle_cache), 10)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            cache = []
-            for i in range(5):
-                cache.append(pool.connection())
-            self.assertEqual(len(pool._idle_cache), 5)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 5)
-            else:
-                self.assertEqual(len(pool._idle_cache), 5)
-            pool.close()
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            pool = PooledDB(dbapi, 10, 10, 5)
-            closed = []
-
-            def close_idle(what=closed):
-                what.append('idle')
-
-            def close_shared(what=closed):
-                what.append('shared')
-
-            if shareable:
-                cache = []
-                for i in range(5):
-                    cache.append(pool.connection())
-                pool._shared_cache[3].con.close = close_shared
-            else:
-                pool._idle_cache[7]._con.close = close_shared
-            pool._idle_cache[3]._con.close = close_idle
-            self.assertEqual(closed, [])
-            del pool
-            if shareable:
-                del cache
-            self.assertEqual(closed, ['idle', 'shared'])
-
-    def test_shareable_connection(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi, 0, 1, 2)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db1 = pool.connection()
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-            db2 = pool.connection()
-            self.assertNotEqual(db1._con, db2._con)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 2)
-            db3 = pool.connection()
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 2)
-                self.assertEqual(db3._con, db1._con)
-                self.assertEqual(db1._shared_con.shared, 2)
-                self.assertEqual(db2._shared_con.shared, 1)
-            else:
-                self.assertNotEqual(db3._con, db1._con)
-                self.assertNotEqual(db3._con, db2._con)
-            db4 = pool.connection()
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 2)
-                self.assertEqual(db4._con, db2._con)
-                self.assertEqual(db1._shared_con.shared, 2)
-                self.assertEqual(db2._shared_con.shared, 2)
-            else:
-                self.assertNotEqual(db4._con, db1._con)
-                self.assertNotEqual(db4._con, db2._con)
-                self.assertNotEqual(db4._con, db3._con)
-            db5 = pool.connection(False)
-            self.assertNotEqual(db5._con, db1._con)
-            self.assertNotEqual(db5._con, db2._con)
-            self.assertNotEqual(db5._con, db3._con)
-            self.assertNotEqual(db5._con, db4._con)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 2)
-                self.assertEqual(db1._shared_con.shared, 2)
-                self.assertEqual(db2._shared_con.shared, 2)
-            db5.close()
-            self.assertEqual(len(pool._idle_cache), 1)
-            db5 = pool.connection()
-            if shareable:
-                self.assertEqual(len(pool._idle_cache), 1)
-                self.assertEqual(len(pool._shared_cache), 2)
-                self.assertEqual(db5._shared_con.shared, 3)
-            else:
-                self.assertEqual(len(pool._idle_cache), 0)
-            pool = PooledDB(dbapi, 0, 0, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            db1 = pool.connection(False)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db2 = pool.connection()
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-            db3 = pool.connection()
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-                self.assertEqual(db2._con, db3._con)
-            else:
-                self.assertNotEqual(db2._con, db3._con)
-            del db3
-            if shareable:
-                self.assertEqual(len(pool._idle_cache), 0)
-                self.assertEqual(len(pool._shared_cache), 1)
-            else:
-                self.assertEqual(len(pool._idle_cache), 1)
-            del db2
-            if shareable:
-                self.assertEqual(len(pool._idle_cache), 1)
-                self.assertEqual(len(pool._shared_cache), 0)
-            else:
-                self.assertEqual(len(pool._idle_cache), 2)
-            del db1
-            if shareable:
-                self.assertEqual(len(pool._idle_cache), 2)
-                self.assertEqual(len(pool._shared_cache), 0)
-            else:
-                self.assertEqual(len(pool._idle_cache), 3)
-
-    def test_min_max_cached(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi, 3)
-            self.assertEqual(len(pool._idle_cache), 3)
-            cache = [pool.connection() for i in range(3)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            cache = [pool.connection() for i in range(6)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 6)
-            pool = PooledDB(dbapi, 0, 3)
-            self.assertEqual(len(pool._idle_cache), 0)
-            cache = [pool.connection() for i in range(3)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            cache = [pool.connection() for i in range(6)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            pool = PooledDB(dbapi, 3, 3)
-            self.assertEqual(len(pool._idle_cache), 3)
-            cache = [pool.connection() for i in range(3)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            cache = [pool.connection() for i in range(6)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            pool = PooledDB(dbapi, 3, 2)
-            self.assertEqual(len(pool._idle_cache), 3)
-            cache = [pool.connection() for i in range(4)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            pool = PooledDB(dbapi, 2, 5)
-            self.assertEqual(len(pool._idle_cache), 2)
-            cache = [pool.connection() for i in range(10)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 5)
-            pool = PooledDB(dbapi, 1, 2, 3)
-            self.assertEqual(len(pool._idle_cache), 1)
-            cache = [pool.connection(False) for i in range(4)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 2)
-            cache = [pool.connection() for i in range(10)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 3)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 2)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            pool = PooledDB(dbapi, 1, 3, 2)
-            self.assertEqual(len(pool._idle_cache), 1)
-            cache = [pool.connection(False) for i in range(4)]
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            cache = [pool.connection() for i in range(10)]
-            if shareable:
-                self.assertEqual(len(pool._idle_cache), 1)
-                self.assertEqual(len(pool._shared_cache), 2)
-            else:
-                self.assertEqual(len(pool._idle_cache), 0)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 3)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-
-    def test_max_shared(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi)
-            self.assertEqual(len(pool._idle_cache), 0)
-            cache = [pool.connection() for i in range(10)]
-            self.assertEqual(len(cache), 10)
-            self.assertEqual(len(pool._idle_cache), 0)
-            pool = PooledDB(dbapi, 1, 1, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            cache = [pool.connection() for i in range(10)]
-            self.assertEqual(len(cache), 10)
-            self.assertEqual(len(pool._idle_cache), 0)
-            pool = PooledDB(dbapi, 0, 0, 1)
-            cache = [pool.connection() for i in range(10)]
-            self.assertEqual(len(cache), 10)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-            pool = PooledDB(dbapi, 1, 1, 1)
-            self.assertEqual(len(pool._idle_cache), 1)
-            cache = [pool.connection() for i in range(10)]
-            self.assertEqual(len(cache), 10)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-            pool = PooledDB(dbapi, 0, 0, 7)
-            cache = [pool.connection(False) for i in range(3)]
-            self.assertEqual(len(cache), 3)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            cache = [pool.connection() for i in range(10)]
-            self.assertEqual(len(cache), 10)
-            self.assertEqual(len(pool._idle_cache), 3)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 7)
-
-    def test_sort_shared(self):
-        dbapi.threadsafety = 2
-        pool = PooledDB(dbapi, 0, 4, 4)
+    NotSupportedError, PooledDB, SharedDBConnection,
+    InvalidConnection, TooManyConnections)
+from dbutils.steady_db import SteadyDBConnection
+
+
+def test_version():
+    from dbutils import __version__, pooled_db
+    assert pooled_db.__version__ == __version__
+    assert PooledDB.version == __version__
+
+
+@pytest.mark.parametrize("threadsafety", [None, 0])
+def test_no_threadsafety(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    with pytest.raises(NotSupportedError):
+        PooledDB(dbapi)
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2, 3])
+def test_threadsafety(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    pool = PooledDB(dbapi, 0, 0, 1)
+    assert hasattr(pool, '_maxshared')
+    if threadsafety > 1:
+        assert pool._maxshared == 1
+        assert hasattr(pool, '_shared_cache')
+    else:
+        assert pool._maxshared == 0
+        assert not hasattr(pool, '_shared_cache')
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_create_connection(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(
+        dbapi, 1, 1, 1, 0, False, None, None, True, None, None,
+        'PooledDBTestDB', user='PooledDBTestUser')
+    assert hasattr(pool, '_idle_cache')
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert hasattr(pool, '_shared_cache')
+        assert len(pool._shared_cache) == 0
+    else:
+        assert not hasattr(pool, '_shared_cache')
+    assert hasattr(pool, '_maxusage')
+    assert pool._maxusage is None
+    assert hasattr(pool, '_setsession')
+    assert pool._setsession is None
+    con = pool._idle_cache[0]
+    assert isinstance(con, SteadyDBConnection)
+    assert hasattr(con, '_maxusage')
+    assert con._maxusage == 0
+    assert hasattr(con, '_setsession_sql')
+    assert con._setsession_sql is None
+    db = pool.connection()
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+    assert hasattr(db, '_con')
+    assert db._con == con
+    assert hasattr(db, 'cursor')
+    assert hasattr(db, '_usage')
+    assert db._usage == 0
+    assert hasattr(con, '_con')
+    db_con = con._con
+    assert hasattr(db_con, 'database')
+    assert db_con.database == 'PooledDBTestDB'
+    assert hasattr(db_con, 'user')
+    assert db_con.user == 'PooledDBTestUser'
+    assert hasattr(db_con, 'open_cursors')
+    assert db_con.open_cursors == 0
+    assert hasattr(db_con, 'num_uses')
+    assert db_con.num_uses == 0
+    assert hasattr(db_con, 'num_queries')
+    assert db_con.num_queries == 0
+    cursor = db.cursor()
+    assert db_con.open_cursors == 1
+    cursor.execute('select test')
+    r = cursor.fetchone()
+    cursor.close()
+    assert db_con.open_cursors == 0
+    assert r == 'test'
+    assert db_con.num_queries == 1
+    assert db._usage == 1
+    cursor = db.cursor()
+    assert db_con.open_cursors == 1
+    cursor.execute('set sessiontest')
+    cursor2 = db.cursor()
+    assert db_con.open_cursors == 2
+    cursor2.close()
+    assert db_con.open_cursors == 1
+    cursor.close()
+    assert db_con.open_cursors == 0
+    assert db_con.num_queries == 1
+    assert db._usage == 2
+    assert db_con.session == ['rollback', 'sessiontest']
+    pool = PooledDB(dbapi, 1, 1, 1)
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db = pool.connection()
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+    db.close()
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db = pool.connection(True)
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+    db.close()
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db = pool.connection(False)
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    assert db._usage == 0
+    db_con = db._con._con
+    assert db_con.database is None
+    assert db_con.user is None
+    db.close()
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db = pool.dedicated_connection()
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    assert db._usage == 0
+    db_con = db._con._con
+    assert db_con.database is None
+    assert db_con.user is None
+    db.close()
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    pool = PooledDB(dbapi, 0, 0, 0, 0, False, 3, ('set datestyle',))
+    assert pool._maxusage == 3
+    assert pool._setsession == ('set datestyle',)
+    con = pool.connection()._con
+    assert con._maxusage == 3
+    assert con._setsession_sql == ('set datestyle',)
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_close_connection(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(
+        dbapi, 0, 1, 1, 0, False, None, None, True, None, None,
+        'PooledDBTestDB', user='PooledDBTestUser')
+    assert hasattr(pool, '_idle_cache')
+    assert len(pool._idle_cache) == 0
+    db = pool.connection()
+    assert hasattr(db, '_con')
+    con = db._con
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+        assert hasattr(db, '_shared_con')
+        shared_con = db._shared_con
+        assert pool._shared_cache[0] == shared_con
+        assert hasattr(shared_con, 'shared')
+        assert shared_con.shared == 1
+        assert hasattr(shared_con, 'con')
+        assert shared_con.con == con
+    assert isinstance(con, SteadyDBConnection)
+    assert hasattr(con, '_con')
+    db_con = con._con
+    assert hasattr(db_con, 'num_queries')
+    assert db._usage == 0
+    assert db_con.num_queries == 0
+    db.cursor().execute('select test')
+    assert db._usage == 1
+    assert db_con.num_queries == 1
+    db.close()
+    assert db._con is None
+    if shareable:
+        assert db._shared_con is None
+        assert shared_con.shared == 0
+    with pytest.raises(InvalidConnection):
+        assert db._usage
+    assert not hasattr(db_con, '_num_queries')
+    assert len(pool._idle_cache) == 1
+    assert pool._idle_cache[0]._con == db_con
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db.close()
+    if shareable:
+        assert shared_con.shared == 0
+    db = pool.connection()
+    assert db._con == con
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+        shared_con = db._shared_con
+        assert pool._shared_cache[0] == shared_con
+        assert shared_con.con == con
+        assert shared_con.shared == 1
+    assert db._usage == 1
+    assert db_con.num_queries == 1
+    assert hasattr(db_con, 'database')
+    assert db_con.database == 'PooledDBTestDB'
+    assert hasattr(db_con, 'user')
+    assert db_con.user == 'PooledDBTestUser'
+    db.cursor().execute('select test')
+    assert db_con.num_queries == 2
+    db.cursor().execute('select test')
+    assert db_con.num_queries == 3
+    db.close()
+    assert len(pool._idle_cache) == 1
+    assert pool._idle_cache[0]._con == db_con
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db = pool.connection(False)
+    assert db._con == con
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db.close()
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_close_all(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi, 10)
+    assert len(pool._idle_cache) == 10
+    pool.close()
+    assert len(pool._idle_cache) == 0
+    pool = PooledDB(dbapi, 10)
+    closed = ['no']
+
+    def close(what=closed):
+        what[0] = 'yes'
+
+    pool._idle_cache[7]._con.close = close
+    assert closed == ['no']
+    del pool
+    assert closed == ['yes']
+    pool = PooledDB(dbapi, 10, 10, 5)
+    assert len(pool._idle_cache) == 10
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    cache = []
+    for i in range(5):
+        cache.append(pool.connection())
+    assert len(pool._idle_cache) == 5
+    if shareable:
+        assert len(pool._shared_cache) == 5
+    else:
+        assert len(pool._idle_cache) == 5
+    pool.close()
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    pool = PooledDB(dbapi, 10, 10, 5)
+    closed = []
+
+    def close_idle(what=closed):
+        what.append('idle')
+
+    def close_shared(what=closed):
+        what.append('shared')
+
+    if shareable:
         cache = []
-        for i in range(6):
-            db = pool.connection()
-            db.cursor().execute('select test')
-            cache.append(db)
-        for i, db in enumerate(cache):
-            self.assertEqual(db._shared_con.shared, 1 if 2 <= i < 4 else 2)
-        cache[2].begin()
-        cache[3].begin()
-        db = pool.connection()
-        self.assertIs(db._con, cache[0]._con)
-        db.close()
-        cache[3].rollback()
-        db = pool.connection()
-        self.assertIs(db._con, cache[3]._con)
-
-    def test_equally_shared(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi, 5, 5, 5)
-            self.assertEqual(len(pool._idle_cache), 5)
-            for i in range(15):
-                db = pool.connection(False)
-                db.cursor().execute('select test')
-                db.close()
-            self.assertEqual(len(pool._idle_cache), 5)
-            for i in range(5):
-                con = pool._idle_cache[i]
-                self.assertEqual(con._usage, 3)
-                self.assertEqual(con._con.num_queries, 3)
-            cache = []
-            for i in range(35):
-                db = pool.connection()
-                db.cursor().execute('select test')
-                cache.append(db)
-                del db
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 5)
-                for i in range(5):
-                    con = pool._shared_cache[i]
-                    self.assertEqual(con.shared, 7)
-                    con = con.con
-                    self.assertEqual(con._usage, 10)
-                    self.assertEqual(con._con.num_queries, 10)
-            del cache
-            self.assertEqual(len(pool._idle_cache), 5)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-
-    def test_many_shared(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi, 0, 0, 5)
-            cache = []
-            for i in range(35):
-                db = pool.connection()
-                db.cursor().execute('select test1')
-                db.cursor().execute('select test2')
-                db.cursor().callproc('test3')
-                cache.append(db)
-                del db
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 5)
-                for i in range(5):
-                    con = pool._shared_cache[i]
-                    self.assertEqual(con.shared, 7)
-                    con = con.con
-                    self.assertEqual(con._usage, 21)
-                    self.assertEqual(con._con.num_queries, 14)
-                cache[3] = cache[8] = cache[33] = None
-                cache[12] = cache[17] = cache[34] = None
-                self.assertEqual(len(pool._shared_cache), 5)
-                self.assertEqual(pool._shared_cache[0].shared, 7)
-                self.assertEqual(pool._shared_cache[1].shared, 7)
-                self.assertEqual(pool._shared_cache[2].shared, 5)
-                self.assertEqual(pool._shared_cache[3].shared, 4)
-                self.assertEqual(pool._shared_cache[4].shared, 6)
-                for db in cache:
-                    if db:
-                        db.cursor().callproc('test4')
-                for i in range(6):
-                    db = pool.connection()
-                    db.cursor().callproc('test4')
-                    cache.append(db)
-                    del db
-                for i in range(5):
-                    con = pool._shared_cache[i]
-                    self.assertEqual(con.shared, 7)
-                    con = con.con
-                    self.assertEqual(con._usage, 28)
-                    self.assertEqual(con._con.num_queries, 14)
-            del cache
-            if shareable:
-                self.assertEqual(len(pool._idle_cache), 5)
-                self.assertEqual(len(pool._shared_cache), 0)
-            else:
-                self.assertEqual(len(pool._idle_cache), 35)
-
-    def test_rollback(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            pool = PooledDB(dbapi, 0, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            db = pool.connection(False)
-            self.assertEqual(len(pool._idle_cache), 0)
-            self.assertEqual(db._con._con.open_cursors, 0)
-            cursor = db.cursor()
-            self.assertEqual(db._con._con.open_cursors, 1)
-            cursor.execute('set doit1')
-            db.commit()
-            cursor.execute('set dont1')
-            cursor.close()
-            self.assertEqual(db._con._con.open_cursors, 0)
-            del db
-            self.assertEqual(len(pool._idle_cache), 1)
-            db = pool.connection(False)
-            self.assertEqual(len(pool._idle_cache), 0)
-            self.assertEqual(db._con._con.open_cursors, 0)
-            cursor = db.cursor()
-            self.assertEqual(db._con._con.open_cursors, 1)
-            cursor.execute('set doit2')
-            cursor.close()
-            self.assertEqual(db._con._con.open_cursors, 0)
-            db.commit()
-            session = db._con._con.session
-            db.close()
-            self.assertEqual(session, [
-                'doit1', 'commit', 'dont1', 'rollback',
-                'doit2', 'commit', 'rollback'])
-
-    def test_maxconnections(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi, 1, 2, 2, 3)
-            self.assertTrue(hasattr(pool, '_maxconnections'))
-            self.assertEqual(pool._maxconnections, 3)
-            self.assertTrue(hasattr(pool, '_connections'))
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            cache = []
-            for i in range(3):
-                cache.append(pool.connection(False))
-            self.assertEqual(pool._connections, 3)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            self.assertRaises(TooManyConnections, pool.connection)
-            cache = []
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 2)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            for i in range(3):
-                cache.append(pool.connection())
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(pool._connections, 2)
-                self.assertEqual(len(pool._shared_cache), 2)
-                cache.append(pool.connection(False))
-                self.assertEqual(pool._connections, 3)
-                self.assertEqual(len(pool._shared_cache), 2)
-            else:
-                self.assertEqual(pool._connections, 3)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            if shareable:
-                cache.append(pool.connection(True))
-                self.assertEqual(pool._connections, 3)
-            else:
-                self.assertRaises(TooManyConnections, pool.connection)
-            del cache
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 2)
-            pool = PooledDB(dbapi, 0, 1, 1, 1)
-            self.assertEqual(pool._maxconnections, 1)
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 0)
-            db = pool.connection(False)
-            self.assertEqual(pool._connections, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            self.assertRaises(TooManyConnections, pool.connection)
-            del db
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            cache = [pool.connection()]
-            self.assertEqual(pool._connections, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 1)
-                cache.append(pool.connection())
-                self.assertEqual(pool._connections, 1)
-                self.assertEqual(len(pool._shared_cache), 1)
-                self.assertEqual(pool._shared_cache[0].shared, 2)
-            else:
-                self.assertRaises(TooManyConnections, pool.connection)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            if shareable:
-                cache.append(pool.connection(True))
-                self.assertEqual(pool._connections, 1)
-                self.assertEqual(len(pool._shared_cache), 1)
-                self.assertEqual(pool._shared_cache[0].shared, 3)
-            else:
-                self.assertRaises(TooManyConnections, pool.connection, 1)
-            del cache
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db = pool.connection(False)
-            self.assertEqual(pool._connections, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            del db
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            pool = PooledDB(dbapi, 1, 2, 2, 1)
-            self.assertEqual(pool._maxconnections, 2)
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            cache = []
-            cache.append(pool.connection(False))
-            self.assertEqual(pool._connections, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            cache.append(pool.connection(False))
-            self.assertEqual(pool._connections, 2)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            self.assertRaises(TooManyConnections, pool.connection)
-            pool = PooledDB(dbapi, 4, 3, 2, 1, False)
-            self.assertEqual(pool._maxconnections, 4)
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 4)
-            cache = []
-            for i in range(4):
-                cache.append(pool.connection(False))
-            self.assertEqual(pool._connections, 4)
-            self.assertEqual(len(pool._idle_cache), 0)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            self.assertRaises(TooManyConnections, pool.connection)
-            pool = PooledDB(dbapi, 1, 2, 3, 4, False)
-            self.assertEqual(pool._maxconnections, 4)
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            for i in range(4):
-                cache.append(pool.connection())
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(pool._connections, 3)
-                self.assertEqual(len(pool._shared_cache), 3)
-                cache.append(pool.connection())
-                self.assertEqual(pool._connections, 3)
-                cache.append(pool.connection(False))
-                self.assertEqual(pool._connections, 4)
-            else:
-                self.assertEqual(pool._connections, 4)
-                self.assertRaises(TooManyConnections, pool.connection)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            pool = PooledDB(dbapi, 0, 0, 3, 3, False)
-            self.assertEqual(pool._maxconnections, 3)
-            self.assertEqual(pool._connections, 0)
-            cache = []
-            for i in range(3):
-                cache.append(pool.connection(False))
-            self.assertEqual(pool._connections, 3)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            self.assertRaises(TooManyConnections, pool.connection, 1)
-            cache = []
-            self.assertEqual(pool._connections, 0)
-            for i in range(3):
-                cache.append(pool.connection())
-            self.assertEqual(pool._connections, 3)
-            if shareable:
-                for i in range(3):
-                    cache.append(pool.connection())
-                self.assertEqual(pool._connections, 3)
-            else:
-                self.assertRaises(TooManyConnections, pool.connection)
-            self.assertRaises(TooManyConnections, pool.connection, 0)
-            pool = PooledDB(dbapi, 0, 0, 3)
-            self.assertEqual(pool._maxconnections, 0)
-            self.assertEqual(pool._connections, 0)
-            cache = []
-            for i in range(10):
-                cache.append(pool.connection(False))
-                cache.append(pool.connection())
-            if shareable:
-                self.assertEqual(pool._connections, 13)
-                self.assertEqual(len(pool._shared_cache), 3)
-            else:
-                self.assertEqual(pool._connections, 20)
-            pool = PooledDB(dbapi, 1, 1, 1, 1, True)
-            self.assertEqual(pool._maxconnections, 1)
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            db = pool.connection(False)
-            self.assertEqual(pool._connections, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-
-            def connection():
-                db = pool.connection()
-                cursor = db.cursor()
-                cursor.execute('set thread')
-                cursor.close()
-                db.close()
-
-            from threading import Thread
-            thread = Thread(target=connection)
-            thread.start()
-            thread.join(0.1)
-            self.assertTrue(thread.is_alive())
-            self.assertEqual(pool._connections, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            session = db._con._con.session
-            self.assertEqual(session, ['rollback'])
-            del db
-            thread.join(0.1)
-            self.assertFalse(thread.is_alive())
-            self.assertEqual(pool._connections, 0)
-            self.assertEqual(len(pool._idle_cache), 1)
-            if shareable:
-                self.assertEqual(len(pool._shared_cache), 0)
-            db = pool.connection(False)
-            self.assertEqual(pool._connections, 1)
-            self.assertEqual(len(pool._idle_cache), 0)
-            self.assertEqual(
-                session, ['rollback', 'rollback', 'thread', 'rollback'])
-            del db
+        for i in range(5):
+            cache.append(pool.connection())
+        pool._shared_cache[3].con.close = close_shared
+    else:
+        pool._idle_cache[7]._con.close = close_shared
+    pool._idle_cache[3]._con.close = close_idle
+    assert closed == []
+    del pool
+    if shareable:
+        del cache
+    assert closed == ['idle', 'shared']
 
-    def test_maxusage(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            for maxusage in (0, 3, 7):
-                pool = PooledDB(dbapi, 0, 0, 0, 1, False, maxusage)
-                self.assertEqual(pool._maxusage, maxusage)
-                self.assertEqual(len(pool._idle_cache), 0)
-                db = pool.connection(False)
-                self.assertEqual(db._con._maxusage, maxusage)
-                self.assertEqual(len(pool._idle_cache), 0)
-                self.assertEqual(db._con._con.open_cursors, 0)
-                self.assertEqual(db._usage, 0)
-                self.assertEqual(db._con._con.num_uses, 0)
-                self.assertEqual(db._con._con.num_queries, 0)
-                for i in range(20):
-                    cursor = db.cursor()
-                    self.assertEqual(db._con._con.open_cursors, 1)
-                    cursor.execute(f'select test{i}')
-                    r = cursor.fetchone()
-                    self.assertEqual(r, f'test{i}')
-                    cursor.close()
-                    self.assertEqual(db._con._con.open_cursors, 0)
-                    if maxusage:
-                        j = i % maxusage + 1
-                    else:
-                        j = i + 1
-                    self.assertEqual(db._usage, j)
-                    self.assertEqual(db._con._con.num_uses, j)
-                    self.assertEqual(db._con._con.num_queries, j)
-                db.cursor().callproc('test')
-                self.assertEqual(db._con._con.open_cursors, 0)
-                self.assertEqual(db._usage, j + 1)
-                self.assertEqual(db._con._con.num_uses, j + 1)
-                self.assertEqual(db._con._con.num_queries, j)
-
-    def test_setsession(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            setsession = ('set time zone', 'set datestyle')
-            pool = PooledDB(dbapi, 0, 0, 0, 1, False, None, setsession)
-            self.assertEqual(pool._setsession, setsession)
-            db = pool.connection(False)
-            self.assertEqual(db._setsession_sql, setsession)
-            self.assertEqual(
-                db._con._con.session, ['time zone', 'datestyle'])
-            db.cursor().execute('select test')
-            db.cursor().execute('set test1')
-            self.assertEqual(db._usage, 2)
-            self.assertEqual(db._con._con.num_uses, 4)
-            self.assertEqual(db._con._con.num_queries, 1)
-            self.assertEqual(
-                db._con._con.session, ['time zone', 'datestyle', 'test1'])
-            db.close()
-            db = pool.connection(False)
-            self.assertEqual(db._setsession_sql, setsession)
-            self.assertEqual(
-                db._con._con.session,
-                ['time zone', 'datestyle', 'test1', 'rollback'])
-            db._con._con.close()
-            db.cursor().execute('select test')
-            db.cursor().execute('set test2')
-            self.assertEqual(
-                db._con._con.session, ['time zone', 'datestyle', 'test2'])
-
-    def test_one_thread_two_connections(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            shareable = threadsafety > 1
-            pool = PooledDB(dbapi, 2)
-            db1 = pool.connection()
-            for i in range(5):
-                db1.cursor().execute('select test')
-            db2 = pool.connection()
-            self.assertNotEqual(db1, db2)
-            self.assertNotEqual(db1._con, db2._con)
-            for i in range(7):
-                db2.cursor().execute('select test')
-            self.assertEqual(db1._con._con.num_queries, 5)
-            self.assertEqual(db2._con._con.num_queries, 7)
-            del db1
-            db1 = pool.connection()
-            self.assertNotEqual(db1, db2)
-            self.assertNotEqual(db1._con, db2._con)
-            for i in range(3):
-                db1.cursor().execute('select test')
-            self.assertEqual(db1._con._con.num_queries, 8)
-            db2.cursor().execute('select test')
-            self.assertEqual(db2._con._con.num_queries, 8)
-            pool = PooledDB(dbapi, 0, 0, 2)
-            db1 = pool.connection()
-            for i in range(5):
-                db1.cursor().execute('select test')
-            db2 = pool.connection()
-            self.assertNotEqual(db1, db2)
-            self.assertNotEqual(db1._con, db2._con)
-            for i in range(7):
-                db2.cursor().execute('select test')
-            self.assertEqual(db1._con._con.num_queries, 5)
-            self.assertEqual(db2._con._con.num_queries, 7)
-            del db1
-            db1 = pool.connection()
-            self.assertNotEqual(db1, db2)
-            self.assertNotEqual(db1._con, db2._con)
-            for i in range(3):
-                db1.cursor().execute('select test')
-            self.assertEqual(db1._con._con.num_queries, 8)
-            db2.cursor().execute('select test')
-            self.assertEqual(db2._con._con.num_queries, 8)
-            pool = PooledDB(dbapi, 0, 0, 1)
-            db1 = pool.connection()
-            db2 = pool.connection()
-            self.assertNotEqual(db1, db2)
-            if shareable:
-                self.assertEqual(db1._con, db2._con)
-            else:
-                self.assertNotEqual(db1._con, db2._con)
-            del db1
-            db1 = pool.connection(False)
-            self.assertNotEqual(db1, db2)
-            self.assertNotEqual(db1._con, db2._con)
-
-    def test_tnree_threads_two_connections(self):
-        for threadsafety in (1, 2):
-            dbapi.threadsafety = threadsafety
-            pool = PooledDB(dbapi, 2, 2, 0, 2, True)
-            from queue import Queue, Empty
-            queue = Queue(3)
-
-            def connection():
-                try:
-                    queue.put(pool.connection(), 1, 1)
-                except Exception:
-                    queue.put(pool.connection(), 1)
-
-            from threading import Thread
-            for i in range(3):
-                Thread(target=connection).start()
-            try:
-                db1 = queue.get(1, 1)
-                db2 = queue.get(1, 1)
-            except TypeError:
-                db1 = queue.get(1)
-                db2 = queue.get(1)
-            self.assertNotEqual(db1, db2)
-            db1_con = db1._con
-            db2_con = db2._con
-            self.assertNotEqual(db1_con, db2_con)
-            try:
-                self.assertRaises(Empty, queue.get, 1, 0.1)
-            except TypeError:
-                self.assertRaises(Empty, queue.get, 0)
-            del db1
-            try:
-                db1 = queue.get(1, 1)
-            except TypeError:
-                db1 = queue.get(1)
-            self.assertNotEqual(db1, db2)
-            self.assertNotEqual(db1._con, db2._con)
-            self.assertEqual(db1._con, db1_con)
-            pool = PooledDB(dbapi, 2, 2, 1, 2, True)
-            db1 = pool.connection(False)
-            db2 = pool.connection(False)
-            self.assertNotEqual(db1, db2)
-            db1_con = db1._con
-            db2_con = db2._con
-            self.assertNotEqual(db1_con, db2_con)
-            Thread(target=connection).start()
-            try:
-                self.assertRaises(Empty, queue.get, 1, 0.1)
-            except TypeError:
-                self.assertRaises(Empty, queue.get, 0)
-            del db1
-            try:
-                db1 = queue.get(1, 1)
-            except TypeError:
-                db1 = queue.get(1)
-            self.assertNotEqual(db1, db2)
-            self.assertNotEqual(db1._con, db2._con)
-            self.assertEqual(db1._con, db1_con)
-
-    def test_ping_check(self):
-        Connection = dbapi.Connection
-        Connection.has_ping = True
-        Connection.num_pings = 0
-        dbapi.threadsafety = 2
-        pool = PooledDB(dbapi, 1, 1, 0, 0, False, None, None, True, None, 0)
-        db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 0)
-        db._con.close()
-        db.close()
-        db = pool.connection()
-        self.assertFalse(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 0)
-        pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 0)
-        db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 0)
-        db._con.close()
-        db = pool.connection()
-        self.assertFalse(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 0)
-        pool = PooledDB(dbapi, 1, 1, 0, 0, False, None, None, True, None, 1)
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_shareable_connection(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi, 0, 1, 2)
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db1 = pool.connection()
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+    db2 = pool.connection()
+    assert db1._con != db2._con
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 2
+    db3 = pool.connection()
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 2
+        assert db3._con == db1._con
+        assert db1._shared_con.shared == 2
+        assert db2._shared_con.shared == 1
+    else:
+        assert db3._con != db1._con
+        assert db3._con != db2._con
+    db4 = pool.connection()
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 2
+        assert db4._con == db2._con
+        assert db1._shared_con.shared == 2
+        assert db2._shared_con.shared == 2
+    else:
+        assert db4._con != db1._con
+        assert db4._con != db2._con
+        assert db4._con != db3._con
+    db5 = pool.connection(False)
+    assert db5._con != db1._con
+    assert db5._con != db2._con
+    assert db5._con != db3._con
+    assert db5._con != db4._con
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 2
+        assert db1._shared_con.shared == 2
+        assert db2._shared_con.shared == 2
+    db5.close()
+    assert len(pool._idle_cache) == 1
+    db5 = pool.connection()
+    if shareable:
+        assert len(pool._idle_cache) == 1
+        assert len(pool._shared_cache) == 2
+        assert db5._shared_con.shared == 3
+    else:
+        assert len(pool._idle_cache) == 0
+    pool = PooledDB(dbapi, 0, 0, 1)
+    assert len(pool._idle_cache) == 0
+    db1 = pool.connection(False)
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db2 = pool.connection()
+    if shareable:
+        assert len(pool._shared_cache) == 1
+    db3 = pool.connection()
+    if shareable:
+        assert len(pool._shared_cache) == 1
+        assert db2._con == db3._con
+    else:
+        assert db2._con != db3._con
+    del db3
+    if shareable:
+        assert len(pool._idle_cache) == 0
+        assert len(pool._shared_cache) == 1
+    else:
+        assert len(pool._idle_cache) == 1
+    del db2
+    if shareable:
+        assert len(pool._idle_cache) == 1
+        assert len(pool._shared_cache) == 0
+    else:
+        assert len(pool._idle_cache) == 2
+    del db1
+    if shareable:
+        assert len(pool._idle_cache) == 2
+        assert len(pool._shared_cache) == 0
+    else:
+        assert len(pool._idle_cache) == 3
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_min_max_cached(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi, 3)
+    assert len(pool._idle_cache) == 3
+    cache = [pool.connection() for i in range(3)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    cache = [pool.connection() for i in range(6)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 6
+    pool = PooledDB(dbapi, 0, 3)
+    assert len(pool._idle_cache) == 0
+    cache = [pool.connection() for i in range(3)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    cache = [pool.connection() for i in range(6)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    pool = PooledDB(dbapi, 3, 3)
+    assert len(pool._idle_cache) == 3
+    cache = [pool.connection() for i in range(3)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    cache = [pool.connection() for i in range(6)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    pool = PooledDB(dbapi, 3, 2)
+    assert len(pool._idle_cache) == 3
+    cache = [pool.connection() for i in range(4)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    pool = PooledDB(dbapi, 2, 5)
+    assert len(pool._idle_cache) == 2
+    cache = [pool.connection() for i in range(10)]
+    assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 5
+    pool = PooledDB(dbapi, 1, 2, 3)
+    assert len(pool._idle_cache) == 1
+    cache = [pool.connection(False) for i in range(4)]
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    del cache
+    assert len(pool._idle_cache) == 2
+    cache = [pool.connection() for i in range(10)]
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 3
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 2
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    pool = PooledDB(dbapi, 1, 3, 2)
+    assert len(pool._idle_cache) == 1
+    cache = [pool.connection(False) for i in range(4)]
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    cache = [pool.connection() for i in range(10)]
+    if shareable:
+        assert len(pool._idle_cache) == 1
+        assert len(pool._shared_cache) == 2
+    else:
+        assert len(pool._idle_cache) == 0
+    assert cache
+    del cache
+    assert len(pool._idle_cache) == 3
+    if shareable:
+        assert len(pool._shared_cache) == 0
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_max_shared(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi)
+    assert len(pool._idle_cache) == 0
+    cache = [pool.connection() for i in range(10)]
+    assert len(cache) == 10
+    assert len(pool._idle_cache) == 0
+    pool = PooledDB(dbapi, 1, 1, 0)
+    assert len(pool._idle_cache) == 1
+    cache = [pool.connection() for i in range(10)]
+    assert len(cache) == 10
+    assert len(pool._idle_cache) == 0
+    pool = PooledDB(dbapi, 0, 0, 1)
+    cache = [pool.connection() for i in range(10)]
+    assert len(cache) == 10
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+    pool = PooledDB(dbapi, 1, 1, 1)
+    assert len(pool._idle_cache) == 1
+    cache = [pool.connection() for i in range(10)]
+    assert len(cache) == 10
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+    pool = PooledDB(dbapi, 0, 0, 7)
+    cache = [pool.connection(False) for i in range(3)]
+    assert len(cache) == 3
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    cache = [pool.connection() for i in range(10)]
+    assert len(cache) == 10
+    assert len(pool._idle_cache) == 3
+    if shareable:
+        assert len(pool._shared_cache) == 7
+
+
+def test_sort_shared(dbapi):  # noqa: F811
+    pool = PooledDB(dbapi, 0, 4, 4)
+    cache = []
+    for i in range(6):
         db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 1)
-        db._con.close()
+        db.cursor().execute('select test')
+        cache.append(db)
+    for i, db in enumerate(cache):
+        assert db._shared_con.shared == 1 if 2 <= i < 4 else 2
+    cache[2].begin()
+    cache[3].begin()
+    db = pool.connection()
+    assert db._con is cache[0]._con
+    db.close()
+    cache[3].rollback()
+    db = pool.connection()
+    assert db._con is cache[3]._con
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_equally_shared(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi, 5, 5, 5)
+    assert len(pool._idle_cache) == 5
+    for i in range(15):
+        db = pool.connection(False)
+        db.cursor().execute('select test')
         db.close()
+    assert len(pool._idle_cache) == 5
+    for i in range(5):
+        con = pool._idle_cache[i]
+        assert con._usage == 3
+        assert con._con.num_queries == 3
+    cache = []
+    for i in range(35):
         db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 2)
-        pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 1)
-        db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 3)
-        db._con.close()
-        db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 4)
-        pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 2)
-        db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 4)
-        db._con.close()
-        db = pool.connection()
-        self.assertFalse(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 4)
-        db.cursor()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 5)
-        pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 4)
-        db = pool.connection()
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 5)
-        db._con.close()
+        db.cursor().execute('select test')
+        cache.append(db)
+        del db
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 5
+        for i in range(5):
+            con = pool._shared_cache[i]
+            assert con.shared == 7
+            con = con.con
+            assert con._usage == 10
+            assert con._con.num_queries == 10
+    del cache
+    assert len(pool._idle_cache) == 5
+    if shareable:
+        assert len(pool._shared_cache) == 0
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_many_shared(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi, 0, 0, 5)
+    cache = []
+    for i in range(35):
         db = pool.connection()
-        self.assertFalse(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 5)
-        cursor = db.cursor()
-        db._con.close()
-        self.assertFalse(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 5)
-        cursor.execute('select test')
-        self.assertTrue(db._con._con.valid)
-        self.assertEqual(Connection.num_pings, 6)
-        Connection.has_ping = False
-        Connection.num_pings = 0
-
-    def test_failed_transaction(self):
-        dbapi.threadsafety = 2
-        pool = PooledDB(dbapi, 0, 1, 1)
+        db.cursor().execute('select test1')
+        db.cursor().execute('select test2')
+        db.cursor().callproc('test3')
+        cache.append(db)
+        del db
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 5
+        for i in range(5):
+            con = pool._shared_cache[i]
+            assert con.shared == 7
+            con = con.con
+            assert con._usage == 21
+            assert con._con.num_queries == 14
+        cache[3] = cache[8] = cache[33] = None
+        cache[12] = cache[17] = cache[34] = None
+        assert len(pool._shared_cache) == 5
+        assert pool._shared_cache[0].shared == 7
+        assert pool._shared_cache[1].shared == 7
+        assert pool._shared_cache[2].shared == 5
+        assert pool._shared_cache[3].shared == 4
+        assert pool._shared_cache[4].shared == 6
+        for db in cache:
+            if db:
+                db.cursor().callproc('test4')
+        for i in range(6):
+            db = pool.connection()
+            db.cursor().callproc('test4')
+            cache.append(db)
+            del db
+        for i in range(5):
+            con = pool._shared_cache[i]
+            assert con.shared == 7
+            con = con.con
+            assert con._usage == 28
+            assert con._con.num_queries == 14
+    del cache
+    if shareable:
+        assert len(pool._idle_cache) == 5
+        assert len(pool._shared_cache) == 0
+    else:
+        assert len(pool._idle_cache) == 35
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_rollback(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    pool = PooledDB(dbapi, 0, 1)
+    assert len(pool._idle_cache) == 0
+    db = pool.connection(False)
+    assert len(pool._idle_cache) == 0
+    assert db._con._con.open_cursors == 0
+    cursor = db.cursor()
+    assert db._con._con.open_cursors == 1
+    cursor.execute('set doit1')
+    db.commit()
+    cursor.execute('set dont1')
+    cursor.close()
+    assert db._con._con.open_cursors == 0
+    del db
+    assert len(pool._idle_cache) == 1
+    db = pool.connection(False)
+    assert len(pool._idle_cache) == 0
+    assert db._con._con.open_cursors == 0
+    cursor = db.cursor()
+    assert db._con._con.open_cursors == 1
+    cursor.execute('set doit2')
+    cursor.close()
+    assert db._con._con.open_cursors == 0
+    db.commit()
+    session = db._con._con.session
+    db.close()
+    assert session == [
+        'doit1', 'commit', 'dont1', 'rollback',
+        'doit2', 'commit', 'rollback']
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_maxconnections(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi, 1, 2, 2, 3)
+    assert hasattr(pool, '_maxconnections')
+    assert pool._maxconnections == 3
+    assert hasattr(pool, '_connections')
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    cache = []
+    for i in range(3):
+        cache.append(pool.connection(False))
+    assert pool._connections == 3
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    cache = []
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 2
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    for i in range(3):
+        cache.append(pool.connection())
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert pool._connections == 2
+        assert len(pool._shared_cache) == 2
+        cache.append(pool.connection(False))
+        assert pool._connections == 3
+        assert len(pool._shared_cache) == 2
+    else:
+        assert pool._connections == 3
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    if shareable:
+        cache.append(pool.connection(True))
+        assert pool._connections == 3
+    else:
+        with pytest.raises(TooManyConnections):
+            pool.connection()
+    del cache
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 2
+    pool = PooledDB(dbapi, 0, 1, 1, 1)
+    assert pool._maxconnections == 1
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 0
+    db = pool.connection(False)
+    assert pool._connections == 1
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    assert db
+    del db
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    cache = [pool.connection()]
+    assert pool._connections == 1
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 1
+        cache.append(pool.connection())
+        assert pool._connections == 1
+        assert len(pool._shared_cache) == 1
+        assert pool._shared_cache[0].shared == 2
+    else:
+        with pytest.raises(TooManyConnections):
+            pool.connection()
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    if shareable:
+        cache.append(pool.connection(True))
+        assert pool._connections == 1
+        assert len(pool._shared_cache) == 1
+        assert pool._shared_cache[0].shared == 3
+    else:
+        with pytest.raises(TooManyConnections):
+            pool.connection(True)
+    del cache
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db = pool.connection(False)
+    assert pool._connections == 1
+    assert len(pool._idle_cache) == 0
+    assert db
+    del db
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    pool = PooledDB(dbapi, 1, 2, 2, 1)
+    assert pool._maxconnections == 2
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    cache = [pool.connection(False)]
+    assert pool._connections == 1
+    assert len(pool._idle_cache) == 0
+    cache.append(pool.connection(False))
+    assert pool._connections == 2
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    pool = PooledDB(dbapi, 4, 3, 2, 1, False)
+    assert pool._maxconnections == 4
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 4
+    cache = []
+    for i in range(4):
+        cache.append(pool.connection(False))
+    assert pool._connections == 4
+    assert len(pool._idle_cache) == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    pool = PooledDB(dbapi, 1, 2, 3, 4, False)
+    assert pool._maxconnections == 4
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    for i in range(4):
+        cache.append(pool.connection())
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert pool._connections == 3
+        assert len(pool._shared_cache) == 3
+        cache.append(pool.connection())
+        assert pool._connections == 3
+        cache.append(pool.connection(False))
+        assert pool._connections == 4
+    else:
+        assert pool._connections == 4
+        with pytest.raises(TooManyConnections):
+            pool.connection()
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    pool = PooledDB(dbapi, 0, 0, 3, 3, False)
+    assert pool._maxconnections == 3
+    assert pool._connections == 0
+    cache = []
+    for i in range(3):
+        cache.append(pool.connection(False))
+    assert pool._connections == 3
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    with pytest.raises(TooManyConnections):
+        pool.connection(True)
+    cache = []
+    assert pool._connections == 0
+    for i in range(3):
+        cache.append(pool.connection())
+    assert pool._connections == 3
+    if shareable:
+        for i in range(3):
+            cache.append(pool.connection())
+        assert pool._connections == 3
+    else:
+        with pytest.raises(TooManyConnections):
+            pool.connection()
+    with pytest.raises(TooManyConnections):
+        pool.connection(False)
+    pool = PooledDB(dbapi, 0, 0, 3)
+    assert pool._maxconnections == 0
+    assert pool._connections == 0
+    cache = []
+    for i in range(10):
+        cache.append(pool.connection(False))
+        cache.append(pool.connection())
+    if shareable:
+        assert pool._connections == 13
+        assert len(pool._shared_cache) == 3
+    else:
+        assert pool._connections == 20
+    pool = PooledDB(dbapi, 1, 1, 1, 1, True)
+    assert pool._maxconnections == 1
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    db = pool.connection(False)
+    assert pool._connections == 1
+    assert len(pool._idle_cache) == 0
+
+    def connection():
         db = pool.connection()
         cursor = db.cursor()
-        db._con._con.close()
-        cursor.execute('select test')
-        db.begin()
-        db._con._con.close()
-        self.assertRaises(dbapi.InternalError, cursor.execute, 'select test')
-        cursor.execute('select test')
-        db.begin()
-        db.cancel()
-        db._con._con.close()
-        cursor.execute('select test')
-        pool = PooledDB(dbapi, 1, 1, 0)
-        db = pool.connection()
+        cursor.execute('set thread')
+        cursor.close()
+        db.close()
+
+    thread = Thread(target=connection)
+    thread.start()
+    thread.join(0.1)
+    assert thread.is_alive()
+    assert pool._connections == 1
+    assert len(pool._idle_cache) == 0
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    session = db._con._con.session
+    assert session == ['rollback']
+    del db
+    thread.join(0.1)
+    assert not thread.is_alive()
+    assert pool._connections == 0
+    assert len(pool._idle_cache) == 1
+    if shareable:
+        assert len(pool._shared_cache) == 0
+    db = pool.connection(False)
+    assert pool._connections == 1
+    assert len(pool._idle_cache) == 0
+    assert session == ['rollback', 'rollback', 'thread', 'rollback']
+    assert db
+    del db
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+@pytest.mark.parametrize("maxusage", [0, 3, 7])
+def test_maxusage(dbapi, threadsafety, maxusage):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    pool = PooledDB(dbapi, 0, 0, 0, 1, False, maxusage)
+    assert pool._maxusage == maxusage
+    assert len(pool._idle_cache) == 0
+    db = pool.connection(False)
+    assert db._con._maxusage == maxusage
+    assert len(pool._idle_cache) == 0
+    assert db._con._con.open_cursors == 0
+    assert db._usage == 0
+    assert db._con._con.num_uses == 0
+    assert db._con._con.num_queries == 0
+    for i in range(20):
         cursor = db.cursor()
-        db._con._con.close()
-        cursor.execute('select test')
-        db.begin()
-        db._con._con.close()
-        self.assertRaises(dbapi.InternalError, cursor.execute, 'select test')
+        assert db._con._con.open_cursors == 1
+        cursor.execute(f'select test{i}')
+        r = cursor.fetchone()
+        assert r == f'test{i}'
+        cursor.close()
+        assert db._con._con.open_cursors == 0
+        if maxusage:
+            j = i % maxusage + 1
+        else:
+            j = i + 1
+        assert db._usage == j
+        assert db._con._con.num_uses == j
+        assert db._con._con.num_queries == j
+    db.cursor().callproc('test')
+    assert db._con._con.open_cursors == 0
+    assert db._usage == j + 1
+    assert db._con._con.num_uses == j + 1
+    assert db._con._con.num_queries == j
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_setsession(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    setsession = ('set time zone', 'set datestyle')
+    pool = PooledDB(dbapi, 0, 0, 0, 1, False, None, setsession)
+    assert pool._setsession == setsession
+    db = pool.connection(False)
+    assert db._setsession_sql == setsession
+    assert db._con._con.session == ['time zone', 'datestyle']
+    db.cursor().execute('select test')
+    db.cursor().execute('set test1')
+    assert db._usage == 2
+    assert db._con._con.num_uses == 4
+    assert db._con._con.num_queries == 1
+    assert db._con._con.session == ['time zone', 'datestyle', 'test1']
+    db.close()
+    db = pool.connection(False)
+    assert db._setsession_sql == setsession
+    assert db._con._con.session == \
+        ['time zone', 'datestyle', 'test1', 'rollback']
+    db._con._con.close()
+    db.cursor().execute('select test')
+    db.cursor().execute('set test2')
+    assert db._con._con.session == ['time zone', 'datestyle', 'test2']
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_one_thread_two_connections(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    shareable = threadsafety > 1
+    pool = PooledDB(dbapi, 2)
+    db1 = pool.connection()
+    for i in range(5):
+        db1.cursor().execute('select test')
+    db2 = pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    for i in range(7):
+        db2.cursor().execute('select test')
+    assert db1._con._con.num_queries == 5
+    assert db2._con._con.num_queries == 7
+    del db1
+    db1 = pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    for i in range(3):
+        db1.cursor().execute('select test')
+    assert db1._con._con.num_queries == 8
+    db2.cursor().execute('select test')
+    assert db2._con._con.num_queries == 8
+    pool = PooledDB(dbapi, 0, 0, 2)
+    db1 = pool.connection()
+    for i in range(5):
+        db1.cursor().execute('select test')
+    db2 = pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    for i in range(7):
+        db2.cursor().execute('select test')
+    assert db1._con._con.num_queries == 5
+    assert db2._con._con.num_queries == 7
+    del db1
+    db1 = pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    for i in range(3):
+        db1.cursor().execute('select test')
+    assert db1._con._con.num_queries == 8
+    db2.cursor().execute('select test')
+    assert db2._con._con.num_queries == 8
+    pool = PooledDB(dbapi, 0, 0, 1)
+    db1 = pool.connection()
+    db2 = pool.connection()
+    assert db1 != db2
+    if shareable:
+        assert db1._con == db2._con
+    else:
+        assert db1._con != db2._con
+    del db1
+    db1 = pool.connection(False)
+    assert db1 != db2
+    assert db1._con != db2._con
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2])
+def test_three_threads_two_connections(dbapi, threadsafety):  # noqa: F811
+    dbapi.threadsafety = threadsafety
+    pool = PooledDB(dbapi, 2, 2, 0, 2, True)
+    queue = Queue(3)
+
+    def connection():
+        try:
+            queue.put(pool.connection(), 1, 1)
+        except Exception:
+            queue.put(pool.connection(), 1)
+
+    for i in range(3):
+        Thread(target=connection).start()
+    try:
+        db1 = queue.get(1, 1)
+        db2 = queue.get(1, 1)
+    except TypeError:
+        db1 = queue.get(1)
+        db2 = queue.get(1)
+    assert db1 != db2
+    db1_con = db1._con
+    db2_con = db2._con
+    assert db1_con != db2_con
+    try:
+        with pytest.raises(Empty):
+            queue.get(1, 0.1)
+    except TypeError:
+        with pytest.raises(Empty):
+            queue.get(0)
+    del db1
+    try:
+        db1 = queue.get(1, 1)
+    except TypeError:
+        db1 = queue.get(1)
+    assert db1 != db2
+    assert db1._con != db2._con
+    assert db1._con == db1_con
+    pool = PooledDB(dbapi, 2, 2, 1, 2, True)
+    db1 = pool.connection(False)
+    db2 = pool.connection(False)
+    assert db1 != db2
+    db1_con = db1._con
+    db2_con = db2._con
+    assert db1_con != db2_con
+    Thread(target=connection).start()
+    try:
+        with pytest.raises(Empty):
+            queue.get(1, 0.1)
+    except TypeError:
+        with pytest.raises(Empty):
+            queue.get(0)
+    del db1
+    try:
+        db1 = queue.get(1, 1)
+    except TypeError:
+        db1 = queue.get(1)
+    assert db1 != db2
+    assert db1._con != db2._con
+    assert db1._con == db1_con
+
+
+def test_ping_check(dbapi):  # noqa: F811
+    con_cls = dbapi.Connection
+    con_cls.has_ping = True
+    con_cls.num_pings = 0
+    pool = PooledDB(dbapi, 1, 1, 0, 0, False, None, None, True, None, 0)
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 0
+    db._con.close()
+    db.close()
+    db = pool.connection()
+    assert not db._con._con.valid
+    assert con_cls.num_pings == 0
+    pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 0)
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 0
+    db._con.close()
+    db = pool.connection()
+    assert not db._con._con.valid
+    assert con_cls.num_pings == 0
+    pool = PooledDB(dbapi, 1, 1, 0, 0, False, None, None, True, None, 1)
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 1
+    db._con.close()
+    db.close()
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 2
+    pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 1)
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 3
+    db._con.close()
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 4
+    pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 2)
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 4
+    db._con.close()
+    db = pool.connection()
+    assert not db._con._con.valid
+    assert con_cls.num_pings == 4
+    db.cursor()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 5
+    pool = PooledDB(dbapi, 1, 1, 1, 0, False, None, None, True, None, 4)
+    db = pool.connection()
+    assert db._con._con.valid
+    assert con_cls.num_pings == 5
+    db._con.close()
+    db = pool.connection()
+    assert not db._con._con.valid
+    assert con_cls.num_pings == 5
+    cursor = db.cursor()
+    db._con.close()
+    assert not db._con._con.valid
+    assert con_cls.num_pings == 5
+    cursor.execute('select test')
+    assert db._con._con.valid
+    assert con_cls.num_pings == 6
+    con_cls.has_ping = False
+    con_cls.num_pings = 0
+
+
+def test_failed_transaction(dbapi):  # noqa: F811
+    pool = PooledDB(dbapi, 0, 1, 1)
+    db = pool.connection()
+    cursor = db.cursor()
+    db._con._con.close()
+    cursor.execute('select test')
+    db.begin()
+    db._con._con.close()
+    with pytest.raises(dbapi.InternalError):
         cursor.execute('select test')
-        db.begin()
-        db.cancel()
-        db._con._con.close()
+    cursor.execute('select test')
+    db.begin()
+    db.cancel()
+    db._con._con.close()
+    cursor.execute('select test')
+    pool = PooledDB(dbapi, 1, 1, 0)
+    db = pool.connection()
+    cursor = db.cursor()
+    db._con._con.close()
+    cursor.execute('select test')
+    db.begin()
+    db._con._con.close()
+    with pytest.raises(dbapi.InternalError):
         cursor.execute('select test')
+    cursor.execute('select test')
+    db.begin()
+    db.cancel()
+    db._con._con.close()
+    cursor.execute('select test')
 
-    def test_shared_in_transaction(self):
-        dbapi.threadsafety = 2
-        pool = PooledDB(dbapi, 0, 1, 1)
-        db = pool.connection()
-        db.begin()
-        pool.connection(False)
-        self.assertRaises(TooManyConnections, pool.connection)
-        pool = PooledDB(dbapi, 0, 2, 2)
-        db1 = pool.connection()
-        db2 = pool.connection()
-        self.assertIsNot(db2._con, db1._con)
-        db2.close()
-        db2 = pool.connection()
-        self.assertIsNot(db2._con, db1._con)
-        db = pool.connection()
-        self.assertIs(db._con, db1._con)
-        db.close()
-        db1.begin()
-        db = pool.connection()
-        self.assertIs(db._con, db2._con)
-        db.close()
-        db2.begin()
-        pool.connection(False)
-        self.assertRaises(TooManyConnections, pool.connection)
-        db1.rollback()
-        db = pool.connection()
-        self.assertIs(db._con, db1._con)
 
-    def test_reset_transaction(self):
-        pool = PooledDB(dbapi, 1, 1, 0)
-        db = pool.connection()
-        db.begin()
-        con = db._con
-        self.assertTrue(con._transaction)
-        self.assertEqual(con._con.session, ['rollback'])
-        db.close()
-        self.assertIs(pool.connection()._con, con)
-        self.assertFalse(con._transaction)
-        self.assertEqual(con._con.session, ['rollback'] * 3)
-        pool = PooledDB(dbapi, 1, 1, 0, reset=False)
-        db = pool.connection()
-        db.begin()
-        con = db._con
-        self.assertTrue(con._transaction)
-        self.assertEqual(con._con.session, [])
-        db.close()
-        self.assertIs(pool.connection()._con, con)
-        self.assertFalse(con._transaction)
-        self.assertEqual(con._con.session, ['rollback'])
-
-    def test_context_manager(self):
-        pool = PooledDB(dbapi, 1, 1, 1)
-        con = pool._idle_cache[0]._con
-        with pool.connection() as db:
-            self.assertTrue(hasattr(db, '_shared_con'))
-            self.assertFalse(pool._idle_cache)
-            self.assertTrue(con.valid)
-            with db.cursor() as cursor:
-                self.assertEqual(con.open_cursors, 1)
-                cursor.execute('select test')
-                r = cursor.fetchone()
-            self.assertEqual(con.open_cursors, 0)
-            self.assertEqual(r, 'test')
-            self.assertEqual(con.num_queries, 1)
-        self.assertTrue(pool._idle_cache)
-        with pool.dedicated_connection() as db:
-            self.assertFalse(hasattr(db, '_shared_con'))
-            self.assertFalse(pool._idle_cache)
-            with db.cursor() as cursor:
-                self.assertEqual(con.open_cursors, 1)
-                cursor.execute('select test')
-                r = cursor.fetchone()
-            self.assertEqual(con.open_cursors, 0)
-            self.assertEqual(r, 'test')
-            self.assertEqual(con.num_queries, 2)
-        self.assertTrue(pool._idle_cache)
-
-
-class TestSharedDBConnection(unittest.TestCase):
-
-    def test_create_connection(self):
-        db_con = dbapi.connect()
-        con = SharedDBConnection(db_con)
-        self.assertEqual(con.con, db_con)
-        self.assertEqual(con.shared, 1)
-
-    def test_share_and_unshare(self):
-        con = SharedDBConnection(dbapi.connect())
-        self.assertEqual(con.shared, 1)
-        con.share()
-        self.assertEqual(con.shared, 2)
-        con.share()
-        self.assertEqual(con.shared, 3)
-        con.unshare()
-        self.assertEqual(con.shared, 2)
-        con.unshare()
-        self.assertEqual(con.shared, 1)
-
-    def test_comparison(self):
-        con1 = SharedDBConnection(dbapi.connect())
-        con1.con._transaction = False
-        con2 = SharedDBConnection(dbapi.connect())
-        con2.con._transaction = False
-        self.assertTrue(con1 == con2)
-        self.assertTrue(con1 <= con2)
-        self.assertTrue(con1 >= con2)
-        self.assertFalse(con1 != con2)
-        self.assertFalse(con1 < con2)
-        self.assertFalse(con1 > con2)
-        con2.share()
-        self.assertFalse(con1 == con2)
-        self.assertTrue(con1 <= con2)
-        self.assertFalse(con1 >= con2)
-        self.assertTrue(con1 != con2)
-        self.assertTrue(con1 < con2)
-        self.assertFalse(con1 > con2)
-        con1.con._transaction = True
-        self.assertFalse(con1 == con2)
-        self.assertFalse(con1 <= con2)
-        self.assertTrue(con1 >= con2)
-        self.assertTrue(con1 != con2)
-        self.assertFalse(con1 < con2)
-        self.assertTrue(con1 > con2)
-
-
-if __name__ == '__main__':
-    unittest.main()
+def test_shared_in_transaction(dbapi):  # noqa: F811
+    pool = PooledDB(dbapi, 0, 1, 1)
+    db = pool.connection()
+    db.begin()
+    pool.connection(False)
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    pool = PooledDB(dbapi, 0, 2, 2)
+    db1 = pool.connection()
+    db2 = pool.connection()
+    assert db2._con is not db1._con
+    db2.close()
+    db2 = pool.connection()
+    assert db2._con is not db1._con
+    db = pool.connection()
+    assert db._con is db1._con
+    db.close()
+    db1.begin()
+    db = pool.connection()
+    assert db._con is db2._con
+    db.close()
+    db2.begin()
+    pool.connection(False)
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    db1.rollback()
+    db = pool.connection()
+    assert db._con is db1._con
+
+
+def test_reset_transaction(dbapi):  # noqa: F811
+    pool = PooledDB(dbapi, 1, 1, 0)
+    db = pool.connection()
+    db.begin()
+    con = db._con
+    assert con._transaction
+    assert con._con.session == ['rollback']
+    db.close()
+    assert pool.connection()._con is con
+    assert not con._transaction
+    assert con._con.session == ['rollback'] * 3
+    pool = PooledDB(dbapi, 1, 1, 0, reset=False)
+    db = pool.connection()
+    db.begin()
+    con = db._con
+    assert con._transaction
+    assert con._con.session == []
+    db.close()
+    assert pool.connection()._con is con
+    assert not con._transaction
+    assert con._con.session == ['rollback']
+
+
+def test_context_manager(dbapi):  # noqa: F811
+    pool = PooledDB(dbapi, 1, 1, 1)
+    con = pool._idle_cache[0]._con
+    with pool.connection() as db:
+        assert hasattr(db, '_shared_con')
+        assert not pool._idle_cache
+        assert con.valid
+        with db.cursor() as cursor:
+            assert con.open_cursors == 1
+            cursor.execute('select test')
+            r = cursor.fetchone()
+        assert con.open_cursors == 0
+        assert r == 'test'
+        assert con.num_queries == 1
+    assert pool._idle_cache
+    with pool.dedicated_connection() as db:
+        assert not hasattr(db, '_shared_con')
+        assert not pool._idle_cache
+        with db.cursor() as cursor:
+            assert con.open_cursors == 1
+            cursor.execute('select test')
+            r = cursor.fetchone()
+        assert con.open_cursors == 0
+        assert r == 'test'
+        assert con.num_queries == 2
+    assert pool._idle_cache
+
+
+def test_shared_db_connection_create(dbapi):  # noqa: F811
+    db_con = dbapi.connect()
+    con = SharedDBConnection(db_con)
+    assert con.con == db_con
+    assert con.shared == 1
+
+
+def test_shared_db_connection_share_and_unshare(dbapi):  # noqa: F811
+    con = SharedDBConnection(dbapi.connect())
+    assert con.shared == 1
+    con.share()
+    assert con.shared == 2
+    con.share()
+    assert con.shared == 3
+    con.unshare()
+    assert con.shared == 2
+    con.unshare()
+    assert con.shared == 1
+
+
+def test_shared_db_connection_compare(dbapi):  # noqa: F811
+    con1 = SharedDBConnection(dbapi.connect())
+    con1.con._transaction = False
+    con2 = SharedDBConnection(dbapi.connect())
+    con2.con._transaction = False
+    assert con1 == con2
+    assert con1 <= con2
+    assert con1 >= con2
+    assert not con1 != con2
+    assert not con1 < con2
+    assert not con1 > con2
+    con2.share()
+    assert not con1 == con2
+    assert con1 <= con2
+    assert not con1 >= con2
+    assert con1 != con2
+    assert con1 < con2
+    assert not con1 > con2
+    con1.con._transaction = True
+    assert not con1 == con2
+    assert not con1 <= con2
+    assert con1 >= con2
+    assert con1 != con2
+    assert not con1 < con2
+    assert con1 > con2
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index ec088a5..b09469f 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -10,310 +10,317 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
+from queue import Queue, Empty
+from threading import Thread
 
-from . import mock_pg  # noqa
+import pg  # noqa: F401
+import pytest
 
 from dbutils.pooled_pg import PooledPg, InvalidConnection, TooManyConnections
+from dbutils.steady_pg import SteadyPgConnection
 
 
-class TestPooledPg(unittest.TestCase):
+def test_version():
+    from dbutils import __version__, pooled_pg
+    assert pooled_pg.__version__ == __version__
+    assert PooledPg.version == __version__
 
-    def test_version(self):
-        from dbutils import __version__, pooled_pg
-        self.assertEqual(pooled_pg.__version__, __version__)
-        self.assertEqual(PooledPg.version, __version__)
 
-    def test_create_connection(self):
-        pool = PooledPg(
-            1, 1, 0, False, None, None, False,
-            'PooledPgTestDB', user='PooledPgTestUser')
-        self.assertTrue(hasattr(pool, '_cache'))
-        self.assertEqual(pool._cache.qsize(), 1)
-        self.assertTrue(hasattr(pool, '_maxusage'))
-        self.assertIsNone(pool._maxusage)
-        self.assertTrue(hasattr(pool, '_setsession'))
-        self.assertIsNone(pool._setsession)
-        self.assertTrue(hasattr(pool, '_reset'))
-        self.assertFalse(pool._reset)
-        db_con = pool._cache.get(0)
-        pool._cache.put(db_con, 0)
-        from dbutils.steady_pg import SteadyPgConnection
-        self.assertTrue(isinstance(db_con, SteadyPgConnection))
-        db = pool.connection()
-        self.assertEqual(pool._cache.qsize(), 0)
-        self.assertTrue(hasattr(db, '_con'))
-        self.assertEqual(db._con, db_con)
-        self.assertTrue(hasattr(db, 'query'))
-        self.assertTrue(hasattr(db, 'num_queries'))
-        self.assertEqual(db.num_queries, 0)
-        self.assertTrue(hasattr(db, '_maxusage'))
-        self.assertEqual(db._maxusage, 0)
-        self.assertTrue(hasattr(db, '_setsession_sql'))
-        self.assertIsNone(db._setsession_sql)
-        self.assertTrue(hasattr(db, 'dbname'))
-        self.assertEqual(db.dbname, 'PooledPgTestDB')
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertEqual(db.user, 'PooledPgTestUser')
-        db.query('select test')
-        self.assertEqual(db.num_queries, 1)
-        pool = PooledPg(1)
-        db = pool.connection()
-        self.assertTrue(hasattr(db, 'dbname'))
-        self.assertIsNone(db.dbname)
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertIsNone(db.user)
-        self.assertTrue(hasattr(db, 'num_queries'))
-        self.assertEqual(db.num_queries, 0)
-        pool = PooledPg(0, 0, 0, False, 3, ('set datestyle',),)
-        self.assertEqual(pool._maxusage, 3)
-        self.assertEqual(pool._setsession, ('set datestyle',))
-        db = pool.connection()
-        self.assertEqual(db._maxusage, 3)
-        self.assertEqual(db._setsession_sql, ('set datestyle',))
+def test_create_connection():
+    pool = PooledPg(
+        1, 1, 0, False, None, None, False,
+        'PooledPgTestDB', user='PooledPgTestUser')
+    assert hasattr(pool, '_cache')
+    assert pool._cache.qsize() == 1
+    assert hasattr(pool, '_maxusage')
+    assert pool._maxusage is None
+    assert hasattr(pool, '_setsession')
+    assert pool._setsession is None
+    assert hasattr(pool, '_reset')
+    assert not pool._reset
+    db_con = pool._cache.get(0)
+    pool._cache.put(db_con, 0)
+    assert isinstance(db_con, SteadyPgConnection)
+    db = pool.connection()
+    assert pool._cache.qsize() == 0
+    assert hasattr(db, '_con')
+    assert db._con == db_con
+    assert hasattr(db, 'query')
+    assert hasattr(db, 'num_queries')
+    assert db.num_queries == 0
+    assert hasattr(db, '_maxusage')
+    assert db._maxusage == 0
+    assert hasattr(db, '_setsession_sql')
+    assert db._setsession_sql is None
+    assert hasattr(db, 'dbname')
+    assert db.dbname == 'PooledPgTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'PooledPgTestUser'
+    db.query('select test')
+    assert db.num_queries == 1
+    pool = PooledPg(1)
+    db = pool.connection()
+    assert hasattr(db, 'dbname')
+    assert db.dbname is None
+    assert hasattr(db, 'user')
+    assert db.user is None
+    assert hasattr(db, 'num_queries')
+    assert db.num_queries == 0
+    pool = PooledPg(0, 0, 0, False, 3, ('set datestyle',),)
+    assert pool._maxusage == 3
+    assert pool._setsession == ('set datestyle',)
+    db = pool.connection()
+    assert db._maxusage == 3
+    assert db._setsession_sql == ('set datestyle',)
 
-    def test_close_connection(self):
-        pool = PooledPg(
-            0, 1, 0, False, None, None, False,
-            'PooledPgTestDB', user='PooledPgTestUser')
-        db = pool.connection()
-        self.assertTrue(hasattr(db, '_con'))
-        db_con = db._con
-        from dbutils.steady_pg import SteadyPgConnection
-        self.assertTrue(isinstance(db_con, SteadyPgConnection))
-        self.assertTrue(hasattr(pool, '_cache'))
-        self.assertEqual(pool._cache.qsize(), 0)
-        self.assertEqual(db.num_queries, 0)
-        db.query('select test')
-        self.assertEqual(db.num_queries, 1)
-        db.close()
-        self.assertRaises(InvalidConnection, getattr, db, 'num_queries')
-        db = pool.connection()
-        self.assertTrue(hasattr(db, 'dbname'))
-        self.assertEqual(db.dbname, 'PooledPgTestDB')
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertEqual(db.user, 'PooledPgTestUser')
-        self.assertEqual(db.num_queries, 1)
-        db.query('select test')
-        self.assertEqual(db.num_queries, 2)
-        db = pool.connection()
-        self.assertEqual(pool._cache.qsize(), 1)
-        self.assertEqual(pool._cache.get(0), db_con)
 
-    def test_min_max_cached(self):
-        pool = PooledPg(3)
-        self.assertTrue(hasattr(pool, '_cache'))
-        self.assertEqual(pool._cache.qsize(), 3)
-        cache = [pool.connection() for i in range(3)]
-        self.assertEqual(pool._cache.qsize(), 0)
-        for i in range(3):
-            cache.pop().close()
-        self.assertEqual(pool._cache.qsize(), 3)
-        for i in range(6):
-            cache.append(pool.connection())
-        self.assertEqual(pool._cache.qsize(), 0)
-        for i in range(6):
-            cache.pop().close()
-        self.assertEqual(pool._cache.qsize(), 6)
-        pool = PooledPg(3, 4)
-        self.assertTrue(hasattr(pool, '_cache'))
-        self.assertEqual(pool._cache.qsize(), 3)
-        cache = [pool.connection() for i in range(3)]
-        self.assertEqual(pool._cache.qsize(), 0)
-        for i in range(3):
-            cache.pop().close()
-        self.assertEqual(pool._cache.qsize(), 3)
-        for i in range(6):
-            cache.append(pool.connection())
-        self.assertEqual(pool._cache.qsize(), 0)
-        for i in range(6):
-            cache.pop().close()
-        self.assertEqual(pool._cache.qsize(), 4)
-        pool = PooledPg(3, 2)
-        self.assertTrue(hasattr(pool, '_cache'))
-        self.assertEqual(pool._cache.qsize(), 3)
-        cache = [pool.connection() for i in range(4)]
-        self.assertEqual(pool._cache.qsize(), 0)
-        for i in range(4):
-            cache.pop().close()
-        self.assertEqual(pool._cache.qsize(), 3)
-        pool = PooledPg(2, 5)
-        self.assertTrue(hasattr(pool, '_cache'))
-        self.assertEqual(pool._cache.qsize(), 2)
-        cache = [pool.connection() for i in range(10)]
-        self.assertEqual(pool._cache.qsize(), 0)
-        for i in range(10):
-            cache.pop().close()
-        self.assertEqual(pool._cache.qsize(), 5)
+def test_close_connection():
+    pool = PooledPg(
+        0, 1, 0, False, None, None, False,
+        'PooledPgTestDB', user='PooledPgTestUser')
+    db = pool.connection()
+    assert hasattr(db, '_con')
+    db_con = db._con
+    assert isinstance(db_con, SteadyPgConnection)
+    assert hasattr(pool, '_cache')
+    assert pool._cache.qsize() == 0
+    assert db.num_queries == 0
+    db.query('select test')
+    assert db.num_queries == 1
+    db.close()
+    with pytest.raises(InvalidConnection):
+        assert db.num_queries
+    db = pool.connection()
+    assert hasattr(db, 'dbname')
+    assert db.dbname == 'PooledPgTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'PooledPgTestUser'
+    assert db.num_queries == 1
+    db.query('select test')
+    assert db.num_queries == 2
+    db = pool.connection()
+    assert pool._cache.qsize() == 1
+    assert pool._cache.get(0) == db_con
 
-    def test_max_connections(self):
-        from dbutils.pooled_pg import TooManyConnections
-        pool = PooledPg(1, 2, 3)
-        self.assertEqual(pool._cache.qsize(), 1)
-        cache = [pool.connection() for i in range(3)]
-        self.assertEqual(pool._cache.qsize(), 0)
-        self.assertRaises(TooManyConnections, pool.connection)
-        pool = PooledPg(0, 1, 1, False)
-        self.assertEqual(pool._blocking, 0)
-        self.assertEqual(pool._cache.qsize(), 0)
-        db = pool.connection()
-        self.assertEqual(pool._cache.qsize(), 0)
-        self.assertRaises(TooManyConnections, pool.connection)
-        del db
-        del cache
-        pool = PooledPg(1, 2, 1)
-        self.assertEqual(pool._cache.qsize(), 1)
-        cache = [pool.connection()]
-        self.assertEqual(pool._cache.qsize(), 0)
+
+def test_min_max_cached():
+    pool = PooledPg(3)
+    assert hasattr(pool, '_cache')
+    assert pool._cache.qsize() == 3
+    cache = [pool.connection() for i in range(3)]
+    assert pool._cache.qsize() == 0
+    for i in range(3):
+        cache.pop().close()
+    assert pool._cache.qsize() == 3
+    for i in range(6):
+        cache.append(pool.connection())
+    assert pool._cache.qsize() == 0
+    for i in range(6):
+        cache.pop().close()
+    assert pool._cache.qsize() == 6
+    pool = PooledPg(3, 4)
+    assert hasattr(pool, '_cache')
+    assert pool._cache.qsize() == 3
+    cache = [pool.connection() for i in range(3)]
+    assert pool._cache.qsize() == 0
+    for i in range(3):
+        cache.pop().close()
+    assert pool._cache.qsize() == 3
+    for i in range(6):
         cache.append(pool.connection())
-        self.assertEqual(pool._cache.qsize(), 0)
-        self.assertRaises(TooManyConnections, pool.connection)
-        pool = PooledPg(3, 2, 1, False)
-        self.assertEqual(pool._cache.qsize(), 3)
-        cache = [pool.connection() for i in range(3)]
-        self.assertEqual(len(cache), 3)
-        self.assertEqual(pool._cache.qsize(), 0)
-        self.assertRaises(TooManyConnections, pool.connection)
-        pool = PooledPg(1, 1, 1, True)
-        self.assertEqual(pool._blocking, 1)
-        self.assertEqual(pool._cache.qsize(), 1)
-        db = pool.connection()
-        self.assertEqual(pool._cache.qsize(), 0)
+    assert pool._cache.qsize() == 0
+    for i in range(6):
+        cache.pop().close()
+    assert pool._cache.qsize() == 4
+    pool = PooledPg(3, 2)
+    assert hasattr(pool, '_cache')
+    assert pool._cache.qsize() == 3
+    cache = [pool.connection() for i in range(4)]
+    assert pool._cache.qsize() == 0
+    for i in range(4):
+        cache.pop().close()
+    assert pool._cache.qsize() == 3
+    pool = PooledPg(2, 5)
+    assert hasattr(pool, '_cache')
+    assert pool._cache.qsize() == 2
+    cache = [pool.connection() for i in range(10)]
+    assert pool._cache.qsize() == 0
+    for i in range(10):
+        cache.pop().close()
+    assert pool._cache.qsize() == 5
 
-        def connection():
-            pool.connection().query('set thread')
 
-        from threading import Thread
-        thread = Thread(target=connection)
-        thread.start()
-        thread.join(0.1)
-        self.assertTrue(thread.is_alive())
-        self.assertEqual(pool._cache.qsize(), 0)
-        session = db._con.session
-        self.assertEqual(session, [])
-        del db
-        thread.join(0.1)
-        self.assertFalse(thread.is_alive())
-        self.assertEqual(pool._cache.qsize(), 1)
-        db = pool.connection()
-        self.assertEqual(pool._cache.qsize(), 0)
-        self.assertEqual(session, ['thread'])
-        del db
+def test_max_connections():
+    pool = PooledPg(1, 2, 3)
+    assert pool._cache.qsize() == 1
+    cache = [pool.connection() for i in range(3)]
+    assert pool._cache.qsize() == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    pool = PooledPg(0, 1, 1, False)
+    assert pool._blocking == 0
+    assert pool._cache.qsize() == 0
+    db = pool.connection()
+    assert pool._cache.qsize() == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    del db
+    del cache
+    pool = PooledPg(1, 2, 1)
+    assert pool._cache.qsize() == 1
+    cache = [pool.connection()]
+    assert pool._cache.qsize() == 0
+    cache.append(pool.connection())
+    assert pool._cache.qsize() == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    pool = PooledPg(3, 2, 1, False)
+    assert pool._cache.qsize() == 3
+    cache = [pool.connection() for i in range(3)]
+    assert len(cache) == 3
+    assert pool._cache.qsize() == 0
+    with pytest.raises(TooManyConnections):
+        pool.connection()
+    pool = PooledPg(1, 1, 1, True)
+    assert pool._blocking == 1
+    assert pool._cache.qsize() == 1
+    db = pool.connection()
+    assert pool._cache.qsize() == 0
 
-    def test_one_thread_two_connections(self):
-        pool = PooledPg(2)
-        db1 = pool.connection()
-        for i in range(5):
-            db1.query('select test')
-        db2 = pool.connection()
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1._con, db2._con)
-        for i in range(7):
-            db2.query('select test')
-        self.assertEqual(db1.num_queries, 5)
-        self.assertEqual(db2.num_queries, 7)
-        del db1
-        db1 = pool.connection()
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1._con, db2._con)
-        self.assertTrue(hasattr(db1, 'query'))
-        for i in range(3):
-            db1.query('select test')
-        self.assertEqual(db1.num_queries, 8)
+    def connection():
+        pool.connection().query('set thread')
+
+    thread = Thread(target=connection)
+    thread.start()
+    thread.join(0.1)
+    assert thread.is_alive()
+    assert pool._cache.qsize() == 0
+    session = db._con.session
+    assert session == []
+    del db
+    thread.join(0.1)
+    assert not thread.is_alive()
+    assert pool._cache.qsize() == 1
+    db = pool.connection()
+    assert pool._cache.qsize() == 0
+    assert session == ['thread']
+    del db
+
+
+def test_one_thread_two_connections():
+    pool = PooledPg(2)
+    db1 = pool.connection()
+    for i in range(5):
+        db1.query('select test')
+    db2 = pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    for i in range(7):
         db2.query('select test')
-        self.assertEqual(db2.num_queries, 8)
+    assert db1.num_queries == 5
+    assert db2.num_queries == 7
+    del db1
+    db1 = pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    assert hasattr(db1, 'query')
+    for i in range(3):
+        db1.query('select test')
+    assert db1.num_queries == 8
+    db2.query('select test')
+    assert db2.num_queries == 8
 
-    def test_three_threads_two_connections(self):
-        pool = PooledPg(2, 2, 2, True)
-        from queue import Queue, Empty
-        queue = Queue(3)
 
-        def connection():
-            try:
-                queue.put(pool.connection(), 1, 1)
-            except TypeError:
-                queue.put(pool.connection(), 1)
+def test_three_threads_two_connections():
+    pool = PooledPg(2, 2, 2, True)
+    queue = Queue(3)
 
-        from threading import Thread
-        for i in range(3):
-            Thread(target=connection).start()
-        try:
-            db1 = queue.get(1, 1)
-            db2 = queue.get(1, 1)
-        except TypeError:
-            db1 = queue.get(1)
-            db2 = queue.get(1)
-        db1_con = db1._con
-        db2_con = db2._con
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1_con, db2_con)
-        try:
-            self.assertRaises(Empty, queue.get, 1, 0.1)
-        except TypeError:
-            self.assertRaises(Empty, queue.get, 0)
-        del db1
+    def connection():
         try:
-            db1 = queue.get(1, 1)
+            queue.put(pool.connection(), 1, 1)
         except TypeError:
-            db1 = queue.get(1)
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1._con, db2._con)
-        self.assertEqual(db1._con, db1_con)
+            queue.put(pool.connection(), 1)
+
+    for i in range(3):
+        Thread(target=connection).start()
+    try:
+        db1 = queue.get(1, 1)
+        db2 = queue.get(1, 1)
+    except TypeError:
+        db1 = queue.get(1)
+        db2 = queue.get(1)
+    db1_con = db1._con
+    db2_con = db2._con
+    assert db1 != db2
+    assert db1_con != db2_con
+    try:
+        with pytest.raises(Empty):
+            queue.get(1, 0.1)
+    except TypeError:
+        with pytest.raises(Empty):
+            queue.get(0)
+    del db1
+    try:
+        db1 = queue.get(1, 1)
+    except TypeError:
+        db1 = queue.get(1)
+    assert db1 != db2
+    assert db1._con != db2._con
+    assert db1._con == db1_con
 
-    def test_reset_transaction(self):
-        pool = PooledPg(1)
-        db = pool.connection()
-        db.begin()
-        con = db._con
-        self.assertTrue(con._transaction)
-        db.query('select test')
-        self.assertEqual(con.num_queries, 1)
-        db.close()
-        self.assertIs(pool.connection()._con, con)
-        self.assertFalse(con._transaction)
-        self.assertEqual(con.session, ['begin', 'rollback'])
-        self.assertEqual(con.num_queries, 1)
-        pool = PooledPg(1, reset=1)
-        db = pool.connection()
-        db.begin()
-        con = db._con
-        self.assertTrue(con._transaction)
-        self.assertEqual(con.session, ['rollback', 'begin'])
-        db.query('select test')
-        self.assertEqual(con.num_queries, 1)
-        db.close()
-        self.assertIs(pool.connection()._con, con)
-        self.assertFalse(con._transaction)
-        self.assertEqual(
-            con.session, ['rollback', 'begin', 'rollback', 'rollback'])
-        self.assertEqual(con.num_queries, 1)
-        pool = PooledPg(1, reset=2)
-        db = pool.connection()
-        db.begin()
-        con = db._con
-        self.assertTrue(con._transaction)
-        self.assertEqual(con.session, ['begin'])
-        db.query('select test')
-        self.assertEqual(con.num_queries, 1)
-        db.close()
-        self.assertIs(pool.connection()._con, con)
-        self.assertFalse(con._transaction)
-        self.assertEqual(con.session, [])
-        self.assertEqual(con.num_queries, 0)
 
-    def test_context_manager(self):
-        pool = PooledPg(1, 1, 1)
-        with pool.connection() as db:
-            db_con = db._con._con
-            db.query('select test')
-            self.assertEqual(db_con.num_queries, 1)
-            self.assertRaises(TooManyConnections, pool.connection)
-        with pool.connection() as db:
-            db_con = db._con._con
-            db.query('select test')
-            self.assertEqual(db_con.num_queries, 2)
-            self.assertRaises(TooManyConnections, pool.connection)
+def test_reset_transaction():
+    pool = PooledPg(1)
+    db = pool.connection()
+    db.begin()
+    con = db._con
+    assert con._transaction
+    db.query('select test')
+    assert con.num_queries == 1
+    db.close()
+    assert pool.connection()._con is con
+    assert not con._transaction
+    assert con.session == ['begin', 'rollback']
+    assert con.num_queries == 1
+    pool = PooledPg(1, reset=1)
+    db = pool.connection()
+    db.begin()
+    con = db._con
+    assert con._transaction
+    assert con.session == ['rollback', 'begin']
+    db.query('select test')
+    assert con.num_queries == 1
+    db.close()
+    assert pool.connection()._con is con
+    assert not con._transaction
+    assert con.session == ['rollback', 'begin', 'rollback', 'rollback']
+    assert con.num_queries == 1
+    pool = PooledPg(1, reset=2)
+    db = pool.connection()
+    db.begin()
+    con = db._con
+    assert con._transaction
+    assert con.session == ['begin']
+    db.query('select test')
+    assert con.num_queries == 1
+    db.close()
+    assert pool.connection()._con is con
+    assert not con._transaction
+    assert con.session == []
+    assert con.num_queries == 0
 
 
-if __name__ == '__main__':
-    unittest.main()
+def test_context_manager():
+    pool = PooledPg(1, 1, 1)
+    with pool.connection() as db:
+        db_con = db._con._con
+        db.query('select test')
+        assert db_con.num_queries == 1
+        with pytest.raises(TooManyConnections):
+            pool.connection()
+    with pool.connection() as db:
+        db_con = db._con._con
+        db.query('select test')
+        assert db_con.num_queries == 2
+        with pytest.raises(TooManyConnections):
+            pool.connection()
diff --git a/tests/test_simple_pooled_db.py b/tests/test_simple_pooled_db.py
index 4cf82ae..3cfd99e 100644
--- a/tests/test_simple_pooled_db.py
+++ b/tests/test_simple_pooled_db.py
@@ -11,137 +11,141 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
+from queue import Queue, Empty
+
+import pytest
 
 from . import mock_db as dbapi
 
 from dbutils import simple_pooled_db
 
 
-class TestSimplePooledDB(unittest.TestCase):
-
-    def my_db_pool(self, mythreadsafety, maxConnections):
-        threadsafety = dbapi.threadsafety
-        dbapi.threadsafety = mythreadsafety
-        try:
-            return simple_pooled_db.PooledDB(
-                dbapi, maxConnections,
-                'SimplePooledDBTestDB', 'SimplePooledDBTestUser')
-        finally:
-            dbapi.threadsafety = threadsafety
-
-    def test_version(self):
-        from dbutils import __version__
-        self.assertEqual(simple_pooled_db.__version__, __version__)
-        self.assertEqual(simple_pooled_db.PooledDB.version, __version__)
-
-    def test_no_threadsafety(self):
-        for threadsafety in (None, -1, 0, 4):
-            self.assertRaises(
-                simple_pooled_db.NotSupportedError,
-                self.my_db_pool, threadsafety, 1)
-
-    def test_create_connection(self):
-        for threadsafety in (1, 2, 3):
-            dbpool = self.my_db_pool(threadsafety, 1)
-            db = dbpool.connection()
-            self.assertTrue(hasattr(db, 'cursor'))
-            self.assertTrue(hasattr(db, 'open_cursors'))
-            self.assertEqual(db.open_cursors, 0)
-            self.assertTrue(hasattr(db, 'database'))
-            self.assertEqual(db.database, 'SimplePooledDBTestDB')
-            self.assertTrue(hasattr(db, 'user'))
-            self.assertEqual(db.user, 'SimplePooledDBTestUser')
-            cursor = db.cursor()
-            self.assertIsNotNone(cursor)
-            self.assertEqual(db.open_cursors, 1)
-            del cursor
-
-    def test_close_connection(self):
-        for threadsafety in (1, 2, 3):
-            db_pool = self.my_db_pool(threadsafety, 1)
-            db = db_pool.connection()
-            self.assertEqual(db.open_cursors, 0)
-            cursor1 = db.cursor()
-            self.assertIsNotNone(cursor1)
-            self.assertEqual(db.open_cursors, 1)
-            db.close()
-            self.assertFalse(hasattr(db, 'open_cursors'))
-            db = db_pool.connection()
-            self.assertTrue(hasattr(db, 'database'))
-            self.assertEqual(db.database, 'SimplePooledDBTestDB')
-            self.assertTrue(hasattr(db, 'user'))
-            self.assertEqual(db.user, 'SimplePooledDBTestUser')
-            self.assertEqual(db.open_cursors, 1)
-            cursor2 = db.cursor()
-            self.assertIsNotNone(cursor2)
-            self.assertEqual(db.open_cursors, 2)
-            del cursor2
-            del cursor1
-
-    def test_two_connections(self):
-        for threadsafety in (1, 2, 3):
-            db_pool = self.my_db_pool(threadsafety, 2)
-            db1 = db_pool.connection()
-            cursors1 = [db1.cursor() for i in range(5)]
-            db2 = db_pool.connection()
-            self.assertNotEqual(db1, db2)
-            cursors2 = [db2.cursor() for i in range(7)]
-            self.assertEqual(db1.open_cursors, 5)
-            self.assertEqual(db2.open_cursors, 7)
-            db1.close()
-            db1 = db_pool.connection()
-            self.assertNotEqual(db1, db2)
-            self.assertTrue(hasattr(db1, 'cursor'))
-            for i in range(3):
-                cursors1.append(db1.cursor())
-            self.assertEqual(db1.open_cursors, 8)
-            cursors2.append(db2.cursor())
-            self.assertEqual(db2.open_cursors, 8)
-            del cursors2
-            del cursors1
-
-    def test_threadsafety_1(self):
-        db_pool = self.my_db_pool(1, 2)
-        from queue import Queue, Empty
-        queue = Queue(3)
-
-        def connection():
-            queue.put(db_pool.connection())
-
-        from threading import Thread
-        threads = [Thread(target=connection).start() for i in range(3)]
-        self.assertEqual(len(threads), 3)
-        try:
-            db1 = queue.get(1, 1)
-            db2 = queue.get(1, 1)
-        except TypeError:
-            db1 = queue.get(1)
-            db2 = queue.get(1)
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1._con, db2._con)
-        try:
-            self.assertRaises(Empty, queue.get, 1, 0.1)
-        except TypeError:
-            self.assertRaises(Empty, queue.get, 0)
-        db2.close()
-        try:
-            db3 = queue.get(1, 1)
-        except TypeError:
-            db3 = queue.get(1)
-        self.assertNotEqual(db1, db3)
-        self.assertNotEqual(db1._con, db3._con)
-
-    def test_threadsafety_2(self):
-        for threadsafety in (2, 3):
-            dbpool = self.my_db_pool(threadsafety, 2)
-            db1 = dbpool.connection()
-            db2 = dbpool.connection()
-            cursors = [dbpool.connection().cursor() for i in range(100)]
-            self.assertEqual(db1.open_cursors, 50)
-            self.assertEqual(db2.open_cursors, 50)
-            del cursors
-
-
-if __name__ == '__main__':
-    unittest.main()
+def my_db_pool(mythreadsafety, max_connections):
+    threadsafety = dbapi.threadsafety
+    dbapi.threadsafety = mythreadsafety
+    try:
+        return simple_pooled_db.PooledDB(
+            dbapi, max_connections,
+            'SimplePooledDBTestDB', 'SimplePooledDBTestUser')
+    finally:
+        dbapi.threadsafety = threadsafety
+
+
+def test_version():
+    from dbutils import __version__
+    assert simple_pooled_db.__version__ == __version__
+    assert simple_pooled_db.PooledDB.version == __version__
+
+
+@pytest.mark.parametrize("threadsafety", [None, -1, 0, 4])
+def test_no_threadsafety(threadsafety):
+    with pytest.raises(simple_pooled_db.NotSupportedError):
+        my_db_pool(threadsafety, 1)
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2, 3])
+def test_create_connection(threadsafety):
+    dbpool = my_db_pool(threadsafety, 1)
+    db = dbpool.connection()
+    assert hasattr(db, 'cursor')
+    assert hasattr(db, 'open_cursors')
+    assert db.open_cursors == 0
+    assert hasattr(db, 'database')
+    assert db.database == 'SimplePooledDBTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'SimplePooledDBTestUser'
+    cursor = db.cursor()
+    assert cursor is not None
+    assert db.open_cursors == 1
+    del cursor
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2, 3])
+def test_close_connection(threadsafety):
+    db_pool = my_db_pool(threadsafety, 1)
+    db = db_pool.connection()
+    assert db.open_cursors == 0
+    cursor1 = db.cursor()
+    assert cursor1 is not None
+    assert db.open_cursors == 1
+    db.close()
+    assert not hasattr(db, 'open_cursors')
+    db = db_pool.connection()
+    assert hasattr(db, 'database')
+    assert db.database == 'SimplePooledDBTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'SimplePooledDBTestUser'
+    assert db.open_cursors == 1
+    cursor2 = db.cursor()
+    assert cursor2 is not None
+    assert db.open_cursors == 2
+    del cursor2
+    del cursor1
+
+
+@pytest.mark.parametrize("threadsafety", [1, 2, 3])
+def test_two_connections(threadsafety):
+    db_pool = my_db_pool(threadsafety, 2)
+    db1 = db_pool.connection()
+    cursors1 = [db1.cursor() for i in range(5)]
+    db2 = db_pool.connection()
+    assert db1 != db2
+    cursors2 = [db2.cursor() for i in range(7)]
+    assert db1.open_cursors == 5
+    assert db2.open_cursors == 7
+    db1.close()
+    db1 = db_pool.connection()
+    assert db1 != db2
+    assert hasattr(db1, 'cursor')
+    for i in range(3):
+        cursors1.append(db1.cursor())
+    assert db1.open_cursors == 8
+    cursors2.append(db2.cursor())
+    assert db2.open_cursors == 8
+    del cursors2
+    del cursors1
+
+
+def test_threadsafety_1():
+    db_pool = my_db_pool(1, 2)
+    queue = Queue(3)
+
+    def connection():
+        queue.put(db_pool.connection())
+
+    from threading import Thread
+    threads = [Thread(target=connection).start() for i in range(3)]
+    assert len(threads) == 3
+    try:
+        db1 = queue.get(1, 1)
+        db2 = queue.get(1, 1)
+    except TypeError:
+        db1 = queue.get(1)
+        db2 = queue.get(1)
+    assert db1 != db2
+    assert db1._con != db2._con
+    try:
+        with pytest.raises(Empty):
+            queue.get(1, 0.1)
+    except TypeError:
+        with pytest.raises(Empty):
+            queue.get(0)
+    db2.close()
+    try:
+        db3 = queue.get(1, 1)
+    except TypeError:
+        db3 = queue.get(1)
+    assert db1 != db3
+    assert db1._con != db3._con
+
+
+@pytest.mark.parametrize("threadsafety", [2, 3])
+def test_threadsafety_2(threadsafety):
+    dbpool = my_db_pool(threadsafety, 2)
+    db1 = dbpool.connection()
+    db2 = dbpool.connection()
+    cursors = [dbpool.connection().cursor() for _i in range(100)]
+    assert db1.open_cursors == 50
+    assert db2.open_cursors == 50
+    assert cursors
+    del cursors
diff --git a/tests/test_simple_pooled_pg.py b/tests/test_simple_pooled_pg.py
index d3026cc..2652c4c 100644
--- a/tests/test_simple_pooled_pg.py
+++ b/tests/test_simple_pooled_pg.py
@@ -10,108 +10,109 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
+from queue import Queue, Empty
+from threading import Thread
 
-from . import mock_pg  # noqa
+import pg  # noqa: F401
+import pytest
 
 from dbutils import simple_pooled_pg
 
 
-class TestSimplePooledPg(unittest.TestCase):
-
-    def my_db_pool(self, maxConnections):
-        return simple_pooled_pg.PooledPg(
-            maxConnections, 'SimplePooledPgTestDB', 'SimplePooledPgTestUser')
-
-    def test_version(self):
-        from dbutils import __version__
-        self.assertEqual(simple_pooled_pg.__version__, __version__)
-        self.assertEqual(simple_pooled_pg.PooledPg.version, __version__)
-
-    def test_create_connection(self):
-        db_pool = self.my_db_pool(1)
-        db = db_pool.connection()
-        self.assertTrue(hasattr(db, 'query'))
-        self.assertTrue(hasattr(db, 'num_queries'))
-        self.assertEqual(db.num_queries, 0)
-        self.assertTrue(hasattr(db, 'dbname'))
-        self.assertEqual(db.dbname, 'SimplePooledPgTestDB')
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertEqual(db.user, 'SimplePooledPgTestUser')
-        db.query('select 1')
-        self.assertEqual(db.num_queries, 1)
-
-    def test_close_connection(self):
-        db_pool = self.my_db_pool(1)
-        db = db_pool.connection()
-        self.assertEqual(db.num_queries, 0)
-        db.query('select 1')
-        self.assertEqual(db.num_queries, 1)
-        db.close()
-        self.assertFalse(hasattr(db, 'num_queries'))
-        db = db_pool.connection()
-        self.assertTrue(hasattr(db, 'dbname'))
-        self.assertEqual(db.dbname, 'SimplePooledPgTestDB')
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertEqual(db.user, 'SimplePooledPgTestUser')
-        self.assertEqual(db.num_queries, 1)
-        db.query('select 1')
-        self.assertEqual(db.num_queries, 2)
-
-    def test_two_connections(self):
-        db_pool = self.my_db_pool(2)
-        db1 = db_pool.connection()
-        for i in range(5):
-            db1.query('select 1')
-        db2 = db_pool.connection()
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1._con, db2._con)
-        for i in range(7):
-            db2.query('select 1')
-        self.assertEqual(db1.num_queries, 5)
-        self.assertEqual(db2.num_queries, 7)
-        db1.close()
-        db1 = db_pool.connection()
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1._con, db2._con)
-        self.assertTrue(hasattr(db1, 'query'))
-        for i in range(3):
-            db1.query('select 1')
-        self.assertEqual(db1.num_queries, 8)
+def my_db_pool(max_connections):
+    return simple_pooled_pg.PooledPg(
+        max_connections, 'SimplePooledPgTestDB', 'SimplePooledPgTestUser')
+
+
+def test_version():
+    from dbutils import __version__
+    assert simple_pooled_pg.__version__ == __version__
+    assert simple_pooled_pg.PooledPg.version == __version__
+
+
+def test_create_connection():
+    db_pool = my_db_pool(1)
+    db = db_pool.connection()
+    assert hasattr(db, 'query')
+    assert hasattr(db, 'num_queries')
+    assert db.num_queries == 0
+    assert hasattr(db, 'dbname')
+    assert db.dbname == 'SimplePooledPgTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'SimplePooledPgTestUser'
+    db.query('select 1')
+    assert db.num_queries == 1
+
+
+def test_close_connection():
+    db_pool = my_db_pool(1)
+    db = db_pool.connection()
+    assert db.num_queries == 0
+    db.query('select 1')
+    assert db.num_queries == 1
+    db.close()
+    assert not hasattr(db, 'num_queries')
+    db = db_pool.connection()
+    assert hasattr(db, 'dbname')
+    assert db.dbname == 'SimplePooledPgTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'SimplePooledPgTestUser'
+    assert db.num_queries == 1
+    db.query('select 1')
+    assert db.num_queries == 2
+
+
+def test_two_connections():
+    db_pool = my_db_pool(2)
+    db1 = db_pool.connection()
+    for i in range(5):
+        db1.query('select 1')
+    db2 = db_pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    for i in range(7):
         db2.query('select 1')
-        self.assertEqual(db2.num_queries, 8)
-
-    def test_threads(self):
-        db_pool = self.my_db_pool(2)
-        from queue import Queue, Empty
-        queue = Queue(3)
-
-        def connection():
-            queue.put(db_pool.connection())
-
-        from threading import Thread
-        threads = [Thread(target=connection).start() for i in range(3)]
-        self.assertEqual(len(threads), 3)
-        try:
-            db1 = queue.get(1, 1)
-            db2 = queue.get(1, 1)
-        except TypeError:
-            db1 = queue.get(1)
-            db2 = queue.get(1)
-        self.assertNotEqual(db1, db2)
-        self.assertNotEqual(db1._con, db2._con)
-        try:
-            self.assertRaises(Empty, queue.get, 1, 0.1)
-        except TypeError:
-            self.assertRaises(Empty, queue.get, 0)
-        db2.close()
-        try:
-            db3 = queue.get(1, 1)
-        except TypeError:
-            db3 = queue.get(1)
-        self.assertNotEqual(db1, db3)
-        self.assertNotEqual(db1._con, db3._con)
-
-
-if __name__ == '__main__':
-    unittest.main()
+    assert db1.num_queries == 5
+    assert db2.num_queries == 7
+    db1.close()
+    db1 = db_pool.connection()
+    assert db1 != db2
+    assert db1._con != db2._con
+    assert hasattr(db1, 'query')
+    for i in range(3):
+        db1.query('select 1')
+    assert db1.num_queries == 8
+    db2.query('select 1')
+    assert db2.num_queries == 8
+
+
+def test_threads():
+    db_pool = my_db_pool(2)
+    queue = Queue(3)
+
+    def connection():
+        queue.put(db_pool.connection())
+
+    threads = [Thread(target=connection).start() for _i in range(3)]
+    assert len(threads) == 3
+    try:
+        db1 = queue.get(1, 1)
+        db2 = queue.get(1, 1)
+    except TypeError:
+        db1 = queue.get(1)
+        db2 = queue.get(1)
+    assert db1 != db2
+    assert db1._con != db2._con
+    try:
+        with pytest.raises(Empty):
+            queue.get(1, 0.1)
+    except TypeError:
+        with pytest.raises(Empty):
+            queue.get(0)
+    db2.close()
+    try:
+        db3 = queue.get(1, 1)
+    except TypeError:
+        db3 = queue.get(1)
+    assert db1 != db3
+    assert db1._con != db3._con
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index 3c21cfd..c2d1f95 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -9,7 +9,7 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
+import pytest
 
 from . import mock_db as dbapi
 
@@ -17,675 +17,711 @@
     connect as SteadyDBconnect, SteadyDBConnection, SteadyDBCursor)
 
 
-class TestSteadyDB(unittest.TestCase):
-
-    def test_version(self):
-        from dbutils import __version__, steady_db
-        self.assertEqual(steady_db.__version__, __version__)
-        self.assertEqual(steady_db.SteadyDBConnection.version, __version__)
-
-    def test_mocked_connection(self):
-        db = dbapi.connect(
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        db.__class__.has_ping = False
-        db.__class__.num_pings = 0
-        self.assertTrue(hasattr(db, 'database'))
-        self.assertEqual(db.database, 'SteadyDBTestDB')
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertEqual(db.user, 'SteadyDBTestUser')
-        self.assertTrue(hasattr(db, 'cursor'))
-        self.assertTrue(hasattr(db, 'close'))
-        self.assertTrue(hasattr(db, 'open_cursors'))
-        self.assertTrue(hasattr(db, 'num_uses'))
-        self.assertTrue(hasattr(db, 'num_queries'))
-        self.assertTrue(hasattr(db, 'session'))
-        self.assertTrue(hasattr(db, 'valid'))
-        self.assertTrue(db.valid)
-        self.assertEqual(db.open_cursors, 0)
-        for i in range(3):
-            cursor = db.cursor()
-            self.assertEqual(db.open_cursors, 1)
-            cursor.close()
-            self.assertEqual(db.open_cursors, 0)
-        cursor = []
-        for i in range(3):
-            cursor.append(db.cursor())
-            self.assertEqual(db.open_cursors, i + 1)
-        del cursor
-        self.assertEqual(db.open_cursors, 0)
+def test_version():
+    from dbutils import __version__, steady_db
+    assert steady_db.__version__ == __version__
+    assert steady_db.SteadyDBConnection.version == __version__
+
+
+def test_mocked_connection():
+    db = dbapi.connect(
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    db.__class__.has_ping = False
+    db.__class__.num_pings = 0
+    assert hasattr(db, 'database')
+    assert db.database == 'SteadyDBTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'SteadyDBTestUser'
+    assert hasattr(db, 'cursor')
+    assert hasattr(db, 'close')
+    assert hasattr(db, 'open_cursors')
+    assert hasattr(db, 'num_uses')
+    assert hasattr(db, 'num_queries')
+    assert hasattr(db, 'session')
+    assert hasattr(db, 'valid')
+    assert db.valid
+    assert db.open_cursors == 0
+    for i in range(3):
         cursor = db.cursor()
-        self.assertTrue(hasattr(cursor, 'execute'))
-        self.assertTrue(hasattr(cursor, 'fetchone'))
-        self.assertTrue(hasattr(cursor, 'callproc'))
-        self.assertTrue(hasattr(cursor, 'close'))
-        self.assertTrue(hasattr(cursor, 'valid'))
-        self.assertTrue(cursor.valid)
-        self.assertEqual(db.open_cursors, 1)
-        for i in range(3):
-            self.assertEqual(db.num_uses, i)
-            self.assertEqual(db.num_queries, i)
-            cursor.execute(f'select test{i}')
-            self.assertEqual(cursor.fetchone(), f'test{i}')
-        self.assertTrue(cursor.valid)
-        self.assertEqual(db.open_cursors, 1)
-        for i in range(4):
-            cursor.callproc('test')
+        assert db.open_cursors == 1
         cursor.close()
-        self.assertFalse(cursor.valid)
-        self.assertEqual(db.open_cursors, 0)
-        self.assertEqual(db.num_uses, 7)
-        self.assertEqual(db.num_queries, 3)
-        self.assertRaises(dbapi.InternalError, cursor.close)
-        self.assertRaises(dbapi.InternalError, cursor.execute, 'select test')
-        self.assertTrue(db.valid)
-        self.assertFalse(db.__class__.has_ping)
-        self.assertEqual(db.__class__.num_pings, 0)
-        self.assertRaises(AttributeError, db.ping)
-        self.assertEqual(db.__class__.num_pings, 1)
-        db.__class__.has_ping = True
-        self.assertIsNone(db.ping())
-        self.assertEqual(db.__class__.num_pings, 2)
-        db.close()
-        self.assertFalse(db.valid)
-        self.assertEqual(db.num_uses, 0)
-        self.assertEqual(db.num_queries, 0)
-        self.assertRaises(dbapi.InternalError, db.close)
-        self.assertRaises(dbapi.InternalError, db.cursor)
-        self.assertRaises(dbapi.OperationalError, db.ping)
-        self.assertEqual(db.__class__.num_pings, 3)
-        db.__class__.has_ping = False
-        db.__class__.num_pings = 0
-
-    def test_broken_connection(self):
-        self.assertRaises(TypeError, SteadyDBConnection, None)
-        self.assertRaises(TypeError, SteadyDBCursor, None)
-        db = SteadyDBconnect(dbapi, database='ok')
-        for i in range(3):
-            db.close()
-        del db
-        self.assertRaises(
-            dbapi.OperationalError, SteadyDBconnect, dbapi, database='error')
-        db = SteadyDBconnect(dbapi, database='ok')
-        cursor = db.cursor()
-        for i in range(3):
-            cursor.close()
-        cursor = db.cursor('ok')
-        for i in range(3):
-            cursor.close()
-        self.assertRaises(dbapi.OperationalError, db.cursor, 'error')
-
-    def test_close(self):
-        for closeable in (False, True):
-            db = SteadyDBconnect(dbapi, closeable=closeable)
-            self.assertTrue(db._con.valid)
-            db.close()
-            self.assertTrue(closeable ^ db._con.valid)
-            db.close()
-            self.assertTrue(closeable ^ db._con.valid)
-            db._close()
-            self.assertFalse(db._con.valid)
-            db._close()
-            self.assertFalse(db._con.valid)
-
-    def test_connection(self):
-        db = SteadyDBconnect(
-            dbapi, 0, None, None, None, True,
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        self.assertTrue(isinstance(db, SteadyDBConnection))
-        self.assertTrue(hasattr(db, '_con'))
-        self.assertTrue(hasattr(db, '_usage'))
-        self.assertEqual(db._usage, 0)
-        self.assertTrue(hasattr(db._con, 'valid'))
-        self.assertTrue(db._con.valid)
-        self.assertTrue(hasattr(db._con, 'cursor'))
-        self.assertTrue(hasattr(db._con, 'close'))
-        self.assertTrue(hasattr(db._con, 'open_cursors'))
-        self.assertTrue(hasattr(db._con, 'num_uses'))
-        self.assertTrue(hasattr(db._con, 'num_queries'))
-        self.assertTrue(hasattr(db._con, 'session'))
-        self.assertTrue(hasattr(db._con, 'database'))
-        self.assertEqual(db._con.database, 'SteadyDBTestDB')
-        self.assertTrue(hasattr(db._con, 'user'))
-        self.assertEqual(db._con.user, 'SteadyDBTestUser')
-        self.assertTrue(hasattr(db, 'cursor'))
-        self.assertTrue(hasattr(db, 'close'))
-        self.assertEqual(db._con.open_cursors, 0)
-        for i in range(3):
-            cursor = db.cursor()
-            self.assertEqual(db._con.open_cursors, 1)
-            cursor.close()
-            self.assertEqual(db._con.open_cursors, 0)
-        cursor = []
-        for i in range(3):
-            cursor.append(db.cursor())
-            self.assertEqual(db._con.open_cursors, i + 1)
-        del cursor
-        self.assertEqual(db._con.open_cursors, 0)
-        cursor = db.cursor()
-        self.assertTrue(hasattr(cursor, 'execute'))
-        self.assertTrue(hasattr(cursor, 'fetchone'))
-        self.assertTrue(hasattr(cursor, 'callproc'))
-        self.assertTrue(hasattr(cursor, 'close'))
-        self.assertTrue(hasattr(cursor, 'valid'))
-        self.assertTrue(cursor.valid)
-        self.assertEqual(db._con.open_cursors, 1)
-        for i in range(3):
-            self.assertEqual(db._usage, i)
-            self.assertEqual(db._con.num_uses, i)
-            self.assertEqual(db._con.num_queries, i)
-            cursor.execute(f'select test{i}')
-            self.assertEqual(cursor.fetchone(), f'test{i}')
-        self.assertTrue(cursor.valid)
-        self.assertEqual(db._con.open_cursors, 1)
-        for i in range(4):
-            cursor.callproc('test')
-        cursor.close()
-        self.assertFalse(cursor.valid)
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 7)
-        self.assertEqual(db._con.num_uses, 7)
-        self.assertEqual(db._con.num_queries, 3)
+        assert db.open_cursors == 0
+    cursor = []
+    for i in range(3):
+        cursor.append(db.cursor())
+        assert db.open_cursors == i + 1
+    del cursor
+    assert db.open_cursors == 0
+    cursor = db.cursor()
+    assert hasattr(cursor, 'execute')
+    assert hasattr(cursor, 'fetchone')
+    assert hasattr(cursor, 'callproc')
+    assert hasattr(cursor, 'close')
+    assert hasattr(cursor, 'valid')
+    assert cursor.valid
+    assert db.open_cursors == 1
+    for i in range(3):
+        assert db.num_uses == i
+        assert db.num_queries == i
+        cursor.execute(f'select test{i}')
+        assert cursor.fetchone() == f'test{i}'
+    assert cursor.valid
+    assert db.open_cursors == 1
+    for i in range(4):
+        cursor.callproc('test')
+    cursor.close()
+    assert not cursor.valid
+    assert db.open_cursors == 0
+    assert db.num_uses == 7
+    assert db.num_queries == 3
+    with pytest.raises(dbapi.InternalError):
         cursor.close()
-        cursor.execute('select test8')
-        self.assertTrue(cursor.valid)
-        self.assertEqual(db._con.open_cursors, 1)
-        self.assertEqual(cursor.fetchone(), 'test8')
-        self.assertEqual(db._usage, 8)
-        self.assertEqual(db._con.num_uses, 8)
-        self.assertEqual(db._con.num_queries, 4)
-        self.assertTrue(db._con.valid)
-        db.close()
-        self.assertFalse(db._con.valid)
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 8)
-        self.assertEqual(db._con.num_uses, 0)
-        self.assertEqual(db._con.num_queries, 0)
-        self.assertRaises(dbapi.InternalError, db._con.close)
+    with pytest.raises(dbapi.InternalError):
+        cursor.execute('select test')
+    assert db.valid
+    assert not db.__class__.has_ping
+    assert db.__class__.num_pings == 0
+    with pytest.raises(AttributeError):
+        db.ping()
+    assert db.__class__.num_pings == 1
+    db.__class__.has_ping = True
+    assert db.ping() is None
+    assert db.__class__.num_pings == 2
+    db.close()
+    assert not db.valid
+    assert db.num_uses == 0
+    assert db.num_queries == 0
+    with pytest.raises(dbapi.InternalError):
         db.close()
-        self.assertRaises(dbapi.InternalError, db._con.cursor)
-        cursor = db.cursor()
-        self.assertTrue(db._con.valid)
-        cursor.execute('select test11')
-        self.assertEqual(cursor.fetchone(), 'test11')
-        cursor.execute('select test12')
-        self.assertEqual(cursor.fetchone(), 'test12')
-        cursor.callproc('test')
-        self.assertEqual(db._usage, 3)
-        self.assertEqual(db._con.num_uses, 3)
-        self.assertEqual(db._con.num_queries, 2)
-        cursor2 = db.cursor()
-        self.assertEqual(db._con.open_cursors, 2)
-        cursor2.execute('select test13')
-        self.assertEqual(cursor2.fetchone(), 'test13')
-        self.assertEqual(db._con.num_queries, 3)
+    with pytest.raises(dbapi.InternalError):
+        db.cursor()
+    with pytest.raises(dbapi.OperationalError):
+        db.ping()
+    assert db.__class__.num_pings == 3
+    db.__class__.has_ping = False
+    db.__class__.num_pings = 0
+
+
+def test_broken_connection():
+    with pytest.raises(TypeError):
+        SteadyDBConnection(None)
+    with pytest.raises(TypeError):
+        SteadyDBCursor(None)
+    db = SteadyDBconnect(dbapi, database='ok')
+    for i in range(3):
         db.close()
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._con.num_queries, 0)
+    del db
+    with pytest.raises(dbapi.OperationalError):#
+        SteadyDBconnect(dbapi, database='error')
+    db = SteadyDBconnect(dbapi, database='ok')
+    cursor = db.cursor()
+    for i in range(3):
+        cursor.close()
+    cursor = db.cursor('ok')
+    for i in range(3):
+        cursor.close()
+    with pytest.raises(dbapi.OperationalError):
+        db.cursor('error')
+
+
+@pytest.mark.parametrize("closeable", [False, True])
+def test_close(closeable):
+    db = SteadyDBconnect(dbapi, closeable=closeable)
+    assert db._con.valid
+    db.close()
+    assert closeable ^ db._con.valid
+    db.close()
+    assert closeable ^ db._con.valid
+    db._close()
+    assert not db._con.valid
+    db._close()
+    assert not db._con.valid
+
+
+def test_connection():
+    db = SteadyDBconnect(
+        dbapi, 0, None, None, None, True,
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    assert isinstance(db, SteadyDBConnection)
+    assert hasattr(db, '_con')
+    assert hasattr(db, '_usage')
+    assert db._usage == 0
+    assert hasattr(db._con, 'valid')
+    assert db._con.valid
+    assert hasattr(db._con, 'cursor')
+    assert hasattr(db._con, 'close')
+    assert hasattr(db._con, 'open_cursors')
+    assert hasattr(db._con, 'num_uses')
+    assert hasattr(db._con, 'num_queries')
+    assert hasattr(db._con, 'session')
+    assert hasattr(db._con, 'database')
+    assert db._con.database == 'SteadyDBTestDB'
+    assert hasattr(db._con, 'user')
+    assert db._con.user == 'SteadyDBTestUser'
+    assert hasattr(db, 'cursor')
+    assert hasattr(db, 'close')
+    assert db._con.open_cursors == 0
+    for i in range(3):
         cursor = db.cursor()
-        self.assertTrue(cursor.valid)
-        cursor.callproc('test')
-        cursor._cursor.valid = False
-        self.assertFalse(cursor.valid)
-        self.assertRaises(dbapi.InternalError, cursor._cursor.callproc, 'test')
+        assert db._con.open_cursors == 1
+        cursor.close()
+        assert db._con.open_cursors == 0
+    cursor = []
+    for i in range(3):
+        cursor.append(db.cursor())
+        assert db._con.open_cursors == i + 1
+    del cursor
+    assert db._con.open_cursors == 0
+    cursor = db.cursor()
+    assert hasattr(cursor, 'execute')
+    assert hasattr(cursor, 'fetchone')
+    assert hasattr(cursor, 'callproc')
+    assert hasattr(cursor, 'close')
+    assert hasattr(cursor, 'valid')
+    assert cursor.valid
+    assert db._con.open_cursors == 1
+    for i in range(3):
+        assert db._usage == i
+        assert db._con.num_uses == i
+        assert db._con.num_queries == i
+        cursor.execute(f'select test{i}')
+        assert cursor.fetchone() == f'test{i}'
+    assert cursor.valid
+    assert db._con.open_cursors == 1
+    for i in range(4):
         cursor.callproc('test')
-        self.assertTrue(cursor.valid)
+    cursor.close()
+    assert not cursor.valid
+    assert db._con.open_cursors == 0
+    assert db._usage == 7
+    assert db._con.num_uses == 7
+    assert db._con.num_queries == 3
+    cursor.close()
+    cursor.execute('select test8')
+    assert cursor.valid
+    assert db._con.open_cursors == 1
+    assert cursor.fetchone() == 'test8'
+    assert db._usage == 8
+    assert db._con.num_uses == 8
+    assert db._con.num_queries == 4
+    assert db._con.valid
+    db.close()
+    assert not db._con.valid
+    assert db._con.open_cursors == 0
+    assert db._usage == 8
+    assert db._con.num_uses == 0
+    assert db._con.num_queries == 0
+    with pytest.raises(dbapi.InternalError):
+        db._con.close()
+    db.close()
+    with pytest.raises(dbapi.InternalError):
+        db._con.cursor()
+    cursor = db.cursor()
+    assert db._con.valid
+    cursor.execute('select test11')
+    assert cursor.fetchone() == 'test11'
+    cursor.execute('select test12')
+    assert cursor.fetchone() == 'test12'
+    cursor.callproc('test')
+    assert db._usage == 3
+    assert db._con.num_uses == 3
+    assert db._con.num_queries == 2
+    cursor2 = db.cursor()
+    assert db._con.open_cursors == 2
+    cursor2.execute('select test13')
+    assert cursor2.fetchone() == 'test13'
+    assert db._con.num_queries == 3
+    db.close()
+    assert db._con.open_cursors == 0
+    assert db._con.num_queries == 0
+    cursor = db.cursor()
+    assert cursor.valid
+    cursor.callproc('test')
+    cursor._cursor.valid = False
+    assert not cursor.valid
+    with pytest.raises(dbapi.InternalError):
         cursor._cursor.callproc('test')
-        self.assertEqual(db._usage, 2)
-        self.assertEqual(db._con.num_uses, 3)
-        db._con.valid = cursor._cursor.valid = False
-        cursor.callproc('test')
-        self.assertTrue(cursor.valid)
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db._con.num_uses, 1)
-        cursor.execute('set this')
-        db.commit()
-        cursor.execute('set that')
-        db.rollback()
-        self.assertEqual(
-            db._con.session, ['this', 'commit', 'that', 'rollback'])
-
-    def test_connection_context_handler(self):
-        db = SteadyDBconnect(
-            dbapi, 0, None, None, None, True,
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        self.assertEqual(db._con.session, [])
+    cursor.callproc('test')
+    assert cursor.valid
+    cursor._cursor.callproc('test')
+    assert db._usage == 2
+    assert db._con.num_uses == 3
+    db._con.valid = cursor._cursor.valid = False
+    cursor.callproc('test')
+    assert cursor.valid
+    assert db._usage == 1
+    assert db._con.num_uses == 1
+    cursor.execute('set this')
+    db.commit()
+    cursor.execute('set that')
+    db.rollback()
+    assert db._con.session == ['this', 'commit', 'that', 'rollback']
+
+
+def test_connection_context_handler():
+    db = SteadyDBconnect(
+        dbapi, 0, None, None, None, True,
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    assert db._con.session == []
+    with db as con:
+        con.cursor().execute('select test')
+    assert db._con.session == ['commit']
+    try:
         with db as con:
-            con.cursor().execute('select test')
-        self.assertEqual(db._con.session, ['commit'])
-        try:
-            with db as con:
-                con.cursor().execute('error')
-        except dbapi.ProgrammingError:
-            error = True
-        else:
-            error = False
-        self.assertTrue(error)
-        self.assertEqual(db._con.session, ['commit', 'rollback'])
-
-    def test_cursor_context_handler(self):
-        db = SteadyDBconnect(
-            dbapi, 0, None, None, None, True,
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        self.assertEqual(db._con.open_cursors, 0)
-        with db.cursor() as cursor:
-            self.assertEqual(db._con.open_cursors, 1)
-            cursor.execute('select test')
-            self.assertEqual(cursor.fetchone(), 'test')
-        self.assertEqual(db._con.open_cursors, 0)
-
-    def test_cursor_as_iterator_provided(self):
-        db = SteadyDBconnect(
-            dbapi, 0, None, None, None, True,
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        self.assertEqual(db._con.open_cursors, 0)
-        cursor = db.cursor()
-        self.assertEqual(db._con.open_cursors, 1)
-        cursor.execute('select test')
-        _cursor = cursor._cursor
-        try:
-            assert not hasattr(_cursor, 'iter')
-            _cursor.__iter__ = lambda: ['test-iter']
-            assert list(iter(cursor)) == ['test']
-        finally:
-            del _cursor.__iter__
-        cursor.close()
-        self.assertEqual(db._con.open_cursors, 0)
-
-    def test_cursor_as_iterator_created(self):
-        db = SteadyDBconnect(
-            dbapi, 0, None, None, None, True,
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        self.assertEqual(db._con.open_cursors, 0)
-        cursor = db.cursor()
-        self.assertEqual(db._con.open_cursors, 1)
+            con.cursor().execute('error')
+    except dbapi.ProgrammingError:
+        error = True
+    else:
+        error = False
+    assert error
+    assert db._con.session == ['commit', 'rollback']
+
+
+def test_cursor_context_handler():
+    db = SteadyDBconnect(
+        dbapi, 0, None, None, None, True,
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    assert db._con.open_cursors == 0
+    with db.cursor() as cursor:
+        assert db._con.open_cursors == 1
         cursor.execute('select test')
+        assert cursor.fetchone() == 'test'
+    assert db._con.open_cursors == 0
+
+
+def test_cursor_as_iterator_provided():
+    db = SteadyDBconnect(
+        dbapi, 0, None, None, None, True,
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    assert db._con.open_cursors == 0
+    cursor = db.cursor()
+    assert db._con.open_cursors == 1
+    cursor.execute('select test')
+    _cursor = cursor._cursor
+    try:
+        assert not hasattr(_cursor, 'iter')
+        _cursor.__iter__ = lambda: ['test-iter']
         assert list(iter(cursor)) == ['test']
-        cursor.close()
-        self.assertEqual(db._con.open_cursors, 0)
-
-    def test_connection_creator_function(self):
-        db1 = SteadyDBconnect(
-            dbapi, 0, None, None, None, True,
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        db2 = SteadyDBconnect(
-            dbapi.connect, 0, None, None, None, True,
-            'SteadyDBTestDB', user='SteadyDBTestUser')
-        self.assertEqual(db1.dbapi(), db2.dbapi())
-        self.assertEqual(db1.threadsafety(), db2.threadsafety())
-        self.assertEqual(db1._creator, db2._creator)
-        self.assertEqual(db1._args, db2._args)
-        self.assertEqual(db1._kwargs, db2._kwargs)
-        db2.close()
-        db1.close()
-
-    def test_connection_maxusage(self):
-        db = SteadyDBconnect(dbapi, 10)
-        cursor = db.cursor()
-        for i in range(100):
-            cursor.execute(f'select test{i}')
-            r = cursor.fetchone()
-            self.assertEqual(r, f'test{i}')
-            self.assertTrue(db._con.valid)
-            j = i % 10 + 1
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db._con.num_uses, j)
-            self.assertEqual(db._con.num_queries, j)
-        self.assertEqual(db._con.open_cursors, 1)
-        db.begin()
-        for i in range(100):
-            cursor.callproc('test')
-            self.assertTrue(db._con.valid)
-            if i == 49:
-                db.commit()
-            j = i % 10 + 1 if i > 49 else i + 11
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db._con.num_uses, j)
-            j = 0 if i > 49 else 10
-            self.assertEqual(db._con.num_queries, j)
-        for i in range(10):
-            if i == 7:
-                db._con.valid = cursor._cursor.valid = False
-            cursor.execute(f'select test{i}')
-            r = cursor.fetchone()
-            self.assertEqual(r, f'test{i}')
-            j = i % 7 + 1
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db._con.num_uses, j)
-            self.assertEqual(db._con.num_queries, j)
-        for i in range(10):
-            if i == 5:
-                db._con.valid = cursor._cursor.valid = False
-            cursor.callproc('test')
-            j = (i + (3 if i < 5 else -5)) % 10 + 1
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db._con.num_uses, j)
-            j = 3 if i < 5 else 0
-            self.assertEqual(db._con.num_queries, j)
-        db.close()
-        cursor.execute('select test1')
-        self.assertEqual(cursor.fetchone(), 'test1')
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db._con.num_uses, 1)
-        self.assertEqual(db._con.num_queries, 1)
-
-    def test_connection_setsession(self):
-        db = SteadyDBconnect(dbapi, 3, ('set time zone', 'set datestyle'))
-        self.assertTrue(hasattr(db, '_usage'))
-        self.assertEqual(db._usage, 0)
-        self.assertTrue(hasattr(db._con, 'open_cursors'))
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertTrue(hasattr(db._con, 'num_uses'))
-        self.assertEqual(db._con.num_uses, 2)
-        self.assertTrue(hasattr(db._con, 'num_queries'))
-        self.assertEqual(db._con.num_queries, 0)
-        self.assertTrue(hasattr(db._con, 'session'))
-        self.assertEqual(tuple(db._con.session), ('time zone', 'datestyle'))
-        for i in range(11):
-            db.cursor().execute('select test')
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 2)
-        self.assertEqual(db._con.num_uses, 4)
-        self.assertEqual(db._con.num_queries, 2)
-        self.assertEqual(db._con.session, ['time zone', 'datestyle'])
-        db.cursor().execute('set test')
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 3)
-        self.assertEqual(db._con.num_uses, 5)
-        self.assertEqual(db._con.num_queries, 2)
-        self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test'])
-        db.cursor().execute('select test')
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db._con.num_uses, 3)
-        self.assertEqual(db._con.num_queries, 1)
-        self.assertEqual(db._con.session, ['time zone', 'datestyle'])
-        db.cursor().execute('set test')
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 2)
-        self.assertEqual(db._con.num_uses, 4)
-        self.assertEqual(db._con.num_queries, 1)
-        self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test'])
-        db.cursor().execute('select test')
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 3)
-        self.assertEqual(db._con.num_uses, 5)
-        self.assertEqual(db._con.num_queries, 2)
-        self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test'])
-        db.close()
-        db.cursor().execute('set test')
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db._con.num_uses, 3)
-        self.assertEqual(db._con.num_queries, 0)
-        self.assertEqual(db._con.session, ['time zone', 'datestyle', 'test'])
-        db.close()
-        db.cursor().execute('select test')
-        self.assertEqual(db._con.open_cursors, 0)
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db._con.num_uses, 3)
-        self.assertEqual(db._con.num_queries, 1)
-        self.assertEqual(db._con.session, ['time zone', 'datestyle'])
-
-    def test_connection_failures(self):
-        db = SteadyDBconnect(dbapi)
-        db.close()
-        db.cursor()
-        db = SteadyDBconnect(dbapi, failures=dbapi.InternalError)
-        db.close()
-        db.cursor()
-        db = SteadyDBconnect(dbapi, failures=dbapi.OperationalError)
-        db.close()
-        self.assertRaises(dbapi.InternalError, db.cursor)
-        db = SteadyDBconnect(dbapi, failures=(
-            dbapi.OperationalError, dbapi.InterfaceError))
-        db.close()
-        self.assertRaises(dbapi.InternalError, db.cursor)
-        db = SteadyDBconnect(dbapi, failures=(
-            dbapi.OperationalError, dbapi.InterfaceError, dbapi.InternalError))
-        db.close()
-        db.cursor()
-
-    def test_connection_failure_error(self):
-        db = SteadyDBconnect(dbapi)
-        cursor = db.cursor()
-        db.close()
-        cursor.execute('select test')
-        cursor = db.cursor()
-        db.close()
-        self.assertRaises(dbapi.ProgrammingError, cursor.execute, 'error')
-
-    def test_connection_set_sizes(self):
-        db = SteadyDBconnect(dbapi)
-        cursor = db.cursor()
-        cursor.execute('get sizes')
-        result = cursor.fetchone()
-        self.assertEqual(result, ([], {}))
-        cursor.setinputsizes([7, 42, 6])
-        cursor.setoutputsize(9)
-        cursor.setoutputsize(15, 3)
-        cursor.setoutputsize(42, 7)
-        cursor.execute('get sizes')
-        result = cursor.fetchone()
-        self.assertEqual(result, ([7, 42, 6], {None: 9, 3: 15, 7: 42}))
-        cursor.execute('get sizes')
-        result = cursor.fetchone()
-        self.assertEqual(result, ([], {}))
-        cursor.setinputsizes([6, 42, 7])
-        cursor.setoutputsize(7)
-        cursor.setoutputsize(15, 3)
-        cursor.setoutputsize(42, 9)
-        db.close()
-        cursor.execute('get sizes')
-        result = cursor.fetchone()
-        self.assertEqual(result, ([6, 42, 7], {None: 7, 3: 15, 9: 42}))
-
-    def test_connection_ping_check(self):
-        Connection = dbapi.Connection
-        Connection.has_ping = False
-        Connection.num_pings = 0
-        db = SteadyDBconnect(dbapi)
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 0)
-        db.close()
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 0)
-        self.assertIsNone(db._ping_check())
-        self.assertEqual(Connection.num_pings, 1)
-        db = SteadyDBconnect(dbapi, ping=7)
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 2)
-        db.close()
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 2)
-        self.assertIsNone(db._ping_check())
-        self.assertEqual(Connection.num_pings, 2)
-        Connection.has_ping = True
-        db = SteadyDBconnect(dbapi)
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 2)
-        db.close()
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 2)
-        self.assertTrue(db._ping_check())
-        self.assertEqual(Connection.num_pings, 3)
-        db = SteadyDBconnect(dbapi, ping=1)
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 3)
-        db.close()
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 3)
-        self.assertTrue(db._ping_check())
-        self.assertEqual(Connection.num_pings, 4)
-        db.close()
-        self.assertTrue(db._ping_check())
-        self.assertEqual(Connection.num_pings, 5)
-        db = SteadyDBconnect(dbapi, ping=7)
-        db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 7)
-        db.close()
+    finally:
+        del _cursor.__iter__
+    cursor.close()
+    assert db._con.open_cursors == 0
+
+
+def test_cursor_as_iterator_created():
+    db = SteadyDBconnect(
+        dbapi, 0, None, None, None, True,
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    assert db._con.open_cursors == 0
+    cursor = db.cursor()
+    assert db._con.open_cursors == 1
+    cursor.execute('select test')
+    assert list(iter(cursor)) == ['test']
+    cursor.close()
+    assert db._con.open_cursors == 0
+
+
+def test_connection_creator_function():
+    db1 = SteadyDBconnect(
+        dbapi, 0, None, None, None, True,
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    db2 = SteadyDBconnect(
+        dbapi.connect, 0, None, None, None, True,
+        'SteadyDBTestDB', user='SteadyDBTestUser')
+    assert db1.dbapi() == db2.dbapi()
+    assert db1.threadsafety() == db2.threadsafety()
+    assert db1._creator == db2._creator
+    assert db1._args == db2._args
+    assert db1._kwargs == db2._kwargs
+    db2.close()
+    db1.close()
+
+
+def test_connection_maxusage():
+    db = SteadyDBconnect(dbapi, 10)
+    cursor = db.cursor()
+    for i in range(100):
+        cursor.execute(f'select test{i}')
+        r = cursor.fetchone()
+        assert r == f'test{i}'
+        assert db._con.valid
+        j = i % 10 + 1
+        assert db._usage == j
+        assert db._con.num_uses == j
+        assert db._con.num_queries == j
+    assert db._con.open_cursors == 1
+    db.begin()
+    for i in range(100):
+        cursor.callproc('test')
+        assert db._con.valid
+        if i == 49:
+            db.commit()
+        j = i % 10 + 1 if i > 49 else i + 11
+        assert db._usage == j
+        assert db._con.num_uses == j
+        j = 0 if i > 49 else 10
+        assert db._con.num_queries == j
+    for i in range(10):
+        if i == 7:
+            db._con.valid = cursor._cursor.valid = False
+        cursor.execute(f'select test{i}')
+        r = cursor.fetchone()
+        assert r == f'test{i}'
+        j = i % 7 + 1
+        assert db._usage == j
+        assert db._con.num_uses == j
+        assert db._con.num_queries == j
+    for i in range(10):
+        if i == 5:
+            db._con.valid = cursor._cursor.valid = False
+        cursor.callproc('test')
+        j = (i + (3 if i < 5 else -5)) % 10 + 1
+        assert db._usage == j
+        assert db._con.num_uses == j
+        j = 3 if i < 5 else 0
+        assert db._con.num_queries == j
+    db.close()
+    cursor.execute('select test1')
+    assert cursor.fetchone() == 'test1'
+    assert db._usage == 1
+    assert db._con.num_uses == 1
+    assert db._con.num_queries == 1
+
+
+def test_connection_setsession():
+    db = SteadyDBconnect(dbapi, 3, ('set time zone', 'set datestyle'))
+    assert hasattr(db, '_usage')
+    assert db._usage == 0
+    assert hasattr(db._con, 'open_cursors')
+    assert db._con.open_cursors == 0
+    assert hasattr(db._con, 'num_uses')
+    assert db._con.num_uses == 2
+    assert hasattr(db._con, 'num_queries')
+    assert db._con.num_queries == 0
+    assert hasattr(db._con, 'session')
+    assert tuple(db._con.session) == ('time zone', 'datestyle')
+    for i in range(11):
         db.cursor().execute('select test')
-        self.assertEqual(Connection.num_pings, 9)
-        db = SteadyDBconnect(dbapi, ping=3)
-        self.assertEqual(Connection.num_pings, 9)
-        db.cursor()
-        self.assertEqual(Connection.num_pings, 10)
-        db.close()
-        cursor = db.cursor()
-        self.assertEqual(Connection.num_pings, 11)
-        cursor.execute('select test')
-        self.assertEqual(Connection.num_pings, 11)
-        db = SteadyDBconnect(dbapi, ping=5)
-        self.assertEqual(Connection.num_pings, 11)
+    assert db._con.open_cursors == 0
+    assert db._usage == 2
+    assert db._con.num_uses == 4
+    assert db._con.num_queries == 2
+    assert db._con.session == ['time zone', 'datestyle']
+    db.cursor().execute('set test')
+    assert db._con.open_cursors == 0
+    assert db._usage == 3
+    assert db._con.num_uses == 5
+    assert db._con.num_queries == 2
+    assert db._con.session == ['time zone', 'datestyle', 'test']
+    db.cursor().execute('select test')
+    assert db._con.open_cursors == 0
+    assert db._usage == 1
+    assert db._con.num_uses == 3
+    assert db._con.num_queries == 1
+    assert db._con.session == ['time zone', 'datestyle']
+    db.cursor().execute('set test')
+    assert db._con.open_cursors == 0
+    assert db._usage == 2
+    assert db._con.num_uses == 4
+    assert db._con.num_queries == 1
+    assert db._con.session == ['time zone', 'datestyle', 'test']
+    db.cursor().execute('select test')
+    assert db._con.open_cursors == 0
+    assert db._usage == 3
+    assert db._con.num_uses == 5
+    assert db._con.num_queries == 2
+    assert db._con.session == ['time zone', 'datestyle', 'test']
+    db.close()
+    db.cursor().execute('set test')
+    assert db._con.open_cursors == 0
+    assert db._usage == 1
+    assert db._con.num_uses == 3
+    assert db._con.num_queries == 0
+    assert db._con.session == ['time zone', 'datestyle', 'test']
+    db.close()
+    db.cursor().execute('select test')
+    assert db._con.open_cursors == 0
+    assert db._usage == 1
+    assert db._con.num_uses == 3
+    assert db._con.num_queries == 1
+    assert db._con.session == ['time zone', 'datestyle']
+
+
+def test_connection_failures():
+    db = SteadyDBconnect(dbapi)
+    db.close()
+    db.cursor()
+    db = SteadyDBconnect(dbapi, failures=dbapi.InternalError)
+    db.close()
+    db.cursor()
+    db = SteadyDBconnect(dbapi, failures=dbapi.OperationalError)
+    db.close()
+    with pytest.raises(dbapi.InternalError):
         db.cursor()
-        self.assertEqual(Connection.num_pings, 11)
-        db.close()
-        cursor = db.cursor()
-        self.assertEqual(Connection.num_pings, 11)
-        cursor.execute('select test')
-        self.assertEqual(Connection.num_pings, 12)
-        db.close()
-        cursor = db.cursor()
-        self.assertEqual(Connection.num_pings, 12)
-        cursor.execute('select test')
-        self.assertEqual(Connection.num_pings, 13)
-        db = SteadyDBconnect(dbapi, ping=7)
-        self.assertEqual(Connection.num_pings, 13)
+    db = SteadyDBconnect(dbapi, failures=(
+        dbapi.OperationalError, dbapi.InterfaceError))
+    db.close()
+    with pytest.raises(dbapi.InternalError):
         db.cursor()
-        self.assertEqual(Connection.num_pings, 14)
-        db.close()
-        cursor = db.cursor()
-        self.assertEqual(Connection.num_pings, 15)
-        cursor.execute('select test')
-        self.assertEqual(Connection.num_pings, 16)
-        db.close()
-        cursor = db.cursor()
-        self.assertEqual(Connection.num_pings, 17)
-        cursor.execute('select test')
-        self.assertEqual(Connection.num_pings, 18)
-        db.close()
-        cursor.execute('select test')
-        self.assertEqual(Connection.num_pings, 20)
-        Connection.has_ping = False
-        Connection.num_pings = 0
-
-    def test_begin_transaction(self):
-        db = SteadyDBconnect(dbapi, database='ok')
-        cursor = db.cursor()
-        cursor.close()
+    db = SteadyDBconnect(dbapi, failures=(
+        dbapi.OperationalError, dbapi.InterfaceError, dbapi.InternalError))
+    db.close()
+    db.cursor()
+
+
+def test_connection_failure_error():
+    db = SteadyDBconnect(dbapi)
+    cursor = db.cursor()
+    db.close()
+    cursor.execute('select test')
+    cursor = db.cursor()
+    db.close()
+    with pytest.raises(dbapi.ProgrammingError):
+        cursor.execute('error')
+
+
+def test_connection_set_sizes():
+    db = SteadyDBconnect(dbapi)
+    cursor = db.cursor()
+    cursor.execute('get sizes')
+    result = cursor.fetchone()
+    assert result == ([], {})
+    cursor.setinputsizes([7, 42, 6])
+    cursor.setoutputsize(9)
+    cursor.setoutputsize(15, 3)
+    cursor.setoutputsize(42, 7)
+    cursor.execute('get sizes')
+    result = cursor.fetchone()
+    assert result == ([7, 42, 6], {None: 9, 3: 15, 7: 42})
+    cursor.execute('get sizes')
+    result = cursor.fetchone()
+    assert result == ([], {})
+    cursor.setinputsizes([6, 42, 7])
+    cursor.setoutputsize(7)
+    cursor.setoutputsize(15, 3)
+    cursor.setoutputsize(42, 9)
+    db.close()
+    cursor.execute('get sizes')
+    result = cursor.fetchone()
+    assert result == ([6, 42, 7], {None: 7, 3: 15, 9: 42})
+
+
+def test_connection_ping_check():
+    con_cls = dbapi.Connection
+    con_cls.has_ping = False
+    con_cls.num_pings = 0
+    db = SteadyDBconnect(dbapi)
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 0
+    db.close()
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 0
+    assert db._ping_check() is None
+    assert con_cls.num_pings == 1
+    db = SteadyDBconnect(dbapi, ping=7)
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 2
+    db.close()
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 2
+    assert db._ping_check() is None
+    assert con_cls.num_pings == 2
+    con_cls.has_ping = True
+    db = SteadyDBconnect(dbapi)
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 2
+    db.close()
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 2
+    assert db._ping_check()
+    assert con_cls.num_pings == 3
+    db = SteadyDBconnect(dbapi, ping=1)
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 3
+    db.close()
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 3
+    assert db._ping_check()
+    assert con_cls.num_pings == 4
+    db.close()
+    assert db._ping_check()
+    assert con_cls.num_pings == 5
+    db = SteadyDBconnect(dbapi, ping=7)
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 7
+    db.close()
+    db.cursor().execute('select test')
+    assert con_cls.num_pings == 9
+    db = SteadyDBconnect(dbapi, ping=3)
+    assert con_cls.num_pings == 9
+    db.cursor()
+    assert con_cls.num_pings == 10
+    db.close()
+    cursor = db.cursor()
+    assert con_cls.num_pings == 11
+    cursor.execute('select test')
+    assert con_cls.num_pings == 11
+    db = SteadyDBconnect(dbapi, ping=5)
+    assert con_cls.num_pings == 11
+    db.cursor()
+    assert con_cls.num_pings == 11
+    db.close()
+    cursor = db.cursor()
+    assert con_cls.num_pings == 11
+    cursor.execute('select test')
+    assert con_cls.num_pings == 12
+    db.close()
+    cursor = db.cursor()
+    assert con_cls.num_pings == 12
+    cursor.execute('select test')
+    assert con_cls.num_pings == 13
+    db = SteadyDBconnect(dbapi, ping=7)
+    assert con_cls.num_pings == 13
+    db.cursor()
+    assert con_cls.num_pings == 14
+    db.close()
+    cursor = db.cursor()
+    assert con_cls.num_pings == 15
+    cursor.execute('select test')
+    assert con_cls.num_pings == 16
+    db.close()
+    cursor = db.cursor()
+    assert con_cls.num_pings == 17
+    cursor.execute('select test')
+    assert con_cls.num_pings == 18
+    db.close()
+    cursor.execute('select test')
+    assert con_cls.num_pings == 20
+    con_cls.has_ping = False
+    con_cls.num_pings = 0
+
+
+def test_begin_transaction():
+    db = SteadyDBconnect(dbapi, database='ok')
+    cursor = db.cursor()
+    cursor.close()
+    cursor.execute('select test12')
+    assert cursor.fetchone() == 'test12'
+    db.begin()
+    cursor = db.cursor()
+    cursor.close()
+    with pytest.raises(dbapi.InternalError):
         cursor.execute('select test12')
-        self.assertEqual(cursor.fetchone(), 'test12')
-        db.begin()
-        cursor = db.cursor()
-        cursor.close()
-        self.assertRaises(dbapi.InternalError, cursor.execute, 'select test12')
-        cursor.execute('select test12')
-        self.assertEqual(cursor.fetchone(), 'test12')
-        db.close()
-        db.begin()
-        self.assertRaises(dbapi.InternalError, cursor.execute, 'select test12')
-        cursor.execute('select test12')
-        self.assertEqual(cursor.fetchone(), 'test12')
-        db.begin()
-        self.assertRaises(dbapi.ProgrammingError, cursor.execute, 'error')
-        cursor.close()
+    cursor.execute('select test12')
+    assert cursor.fetchone() == 'test12'
+    db.close()
+    db.begin()
+    with pytest.raises(dbapi.InternalError):
         cursor.execute('select test12')
-        self.assertEqual(cursor.fetchone(), 'test12')
-
-    def test_with_begin_extension(self):
-        db = SteadyDBconnect(dbapi, database='ok')
-        db._con._begin_called_with = None
-
-        def begin(a, b=None, c=7):
-            db._con._begin_called_with = (a, b, c)
-
-        db._con.begin = begin
-        db.begin(42, 6)
-        cursor = db.cursor()
-        cursor.execute('select test13')
-        self.assertEqual(cursor.fetchone(), 'test13')
-        self.assertEqual(db._con._begin_called_with, (42, 6, 7))
-
-    def test_cancel_transaction(self):
-        db = SteadyDBconnect(dbapi, database='ok')
-        cursor = db.cursor()
-        db.begin()
-        cursor.execute('select test14')
-        self.assertEqual(cursor.fetchone(), 'test14')
-        db.cancel()
-        cursor.execute('select test14')
-        self.assertEqual(cursor.fetchone(), 'test14')
-
-    def test_with_cancel_extension(self):
-        db = SteadyDBconnect(dbapi, database='ok')
-        db._con._cancel_called = None
-
-        def cancel():
-            db._con._cancel_called = 'yes'
-
-        db._con.cancel = cancel
-        db.begin()
-        cursor = db.cursor()
-        cursor.execute('select test15')
-        self.assertEqual(cursor.fetchone(), 'test15')
-        db.cancel()
-        self.assertEqual(db._con._cancel_called, 'yes')
-
-    def test_reset_transaction(self):
-        db = SteadyDBconnect(dbapi, database='ok')
-        db.begin()
-        self.assertFalse(db._con.session)
-        db.close()
-        self.assertFalse(db._con.session)
-        db = SteadyDBconnect(dbapi, database='ok', closeable=False)
-        db.begin()
-        self.assertFalse(db._con.session)
-        db.close()
-        self.assertEqual(db._con.session, ['rollback'])
-
-    def test_commit_error(self):
-        db = SteadyDBconnect(dbapi, database='ok')
-        db.begin()
-        self.assertFalse(db._con.session)
-        self.assertTrue(db._con.valid)
+    cursor.execute('select test12')
+    assert cursor.fetchone() == 'test12'
+    db.begin()
+    with pytest.raises(dbapi.ProgrammingError):
+        cursor.execute('error')
+    cursor.close()
+    cursor.execute('select test12')
+    assert cursor.fetchone() == 'test12'
+
+
+def test_with_begin_extension():
+    db = SteadyDBconnect(dbapi, database='ok')
+    db._con._begin_called_with = None
+
+    def begin(a, b=None, c=7):
+        db._con._begin_called_with = (a, b, c)
+
+    db._con.begin = begin
+    db.begin(42, 6)
+    cursor = db.cursor()
+    cursor.execute('select test13')
+    assert cursor.fetchone() == 'test13'
+    assert db._con._begin_called_with == (42, 6, 7)
+
+
+def test_cancel_transaction():
+    db = SteadyDBconnect(dbapi, database='ok')
+    cursor = db.cursor()
+    db.begin()
+    cursor.execute('select test14')
+    assert cursor.fetchone() == 'test14'
+    db.cancel()
+    cursor.execute('select test14')
+    assert cursor.fetchone() == 'test14'
+
+
+def test_with_cancel_extension():
+    db = SteadyDBconnect(dbapi, database='ok')
+    db._con._cancel_called = None
+
+    def cancel():
+        db._con._cancel_called = 'yes'
+
+    db._con.cancel = cancel
+    db.begin()
+    cursor = db.cursor()
+    cursor.execute('select test15')
+    assert cursor.fetchone() == 'test15'
+    db.cancel()
+    assert db._con._cancel_called == 'yes'
+
+
+def test_reset_transaction():
+    db = SteadyDBconnect(dbapi, database='ok')
+    db.begin()
+    assert not db._con.session
+    db.close()
+    assert not db._con.session
+    db = SteadyDBconnect(dbapi, database='ok', closeable=False)
+    db.begin()
+    assert not db._con.session
+    db.close()
+    assert db._con.session == ['rollback']
+
+
+def test_commit_error():
+    db = SteadyDBconnect(dbapi, database='ok')
+    db.begin()
+    assert not db._con.session
+    assert db._con.valid
+    db.commit()
+    assert db._con.session == ['commit']
+    assert db._con.valid
+    db.begin()
+    db._con.valid = False
+    con = db._con
+    with pytest.raises(dbapi.InternalError):
         db.commit()
-        self.assertEqual(db._con.session, ['commit'])
-        self.assertTrue(db._con.valid)
-        db.begin()
-        db._con.valid = False
-        con = db._con
-        self.assertRaises(dbapi.InternalError, db.commit)
-        self.assertFalse(db._con.session)
-        self.assertTrue(db._con.valid)
-        self.assertIsNot(con, db._con)
-        db.begin()
-        self.assertFalse(db._con.session)
-        self.assertTrue(db._con.valid)
-        db.commit()
-        self.assertEqual(db._con.session, ['commit'])
-        self.assertTrue(db._con.valid)
-
-    def test_rollback_error(self):
-        db = SteadyDBconnect(dbapi, database='ok')
-        db.begin()
-        self.assertFalse(db._con.session)
-        self.assertTrue(db._con.valid)
-        db.rollback()
-        self.assertEqual(db._con.session, ['rollback'])
-        self.assertTrue(db._con.valid)
-        db.begin()
-        db._con.valid = False
-        con = db._con
-        self.assertRaises(dbapi.InternalError, db.rollback)
-        self.assertFalse(db._con.session)
-        self.assertTrue(db._con.valid)
-        self.assertIsNot(con, db._con)
-        db.begin()
-        self.assertFalse(db._con.session)
-        self.assertTrue(db._con.valid)
+    assert not db._con.session
+    assert db._con.valid
+    assert con is not db._con
+    db.begin()
+    assert not db._con.session
+    assert db._con.valid
+    db.commit()
+    assert db._con.session == ['commit']
+    assert db._con.valid
+
+
+def test_rollback_error():
+    db = SteadyDBconnect(dbapi, database='ok')
+    db.begin()
+    assert not db._con.session
+    assert db._con.valid
+    db.rollback()
+    assert db._con.session == ['rollback']
+    assert db._con.valid
+    db.begin()
+    db._con.valid = False
+    con = db._con
+    with pytest.raises(dbapi.InternalError):
         db.rollback()
-        self.assertEqual(db._con.session, ['rollback'])
-        self.assertTrue(db._con.valid)
-
+    assert not db._con.session
+    assert db._con.valid
+    assert con is not db._con
+    db.begin()
+    assert not db._con.session
+    assert db._con.valid
+    db.rollback()
+    assert db._con.session == ['rollback']
+    assert db._con.valid
 
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/test_steady_pg.py b/tests/test_steady_pg.py
index 2b8a1ad..094c7bc 100644
--- a/tests/test_steady_pg.py
+++ b/tests/test_steady_pg.py
@@ -11,312 +11,318 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-import unittest
 import sys
 
-from . import mock_pg as pg
+import pg
+import pytest
 
 from dbutils.steady_pg import SteadyPgConnection
 
 
-class TestSteadyPg(unittest.TestCase):
+def test_version():
+    from dbutils import __version__, steady_pg
+    assert steady_pg.__version__ == __version__
+    assert steady_pg.SteadyPgConnection.version == __version__
 
-    def test_version(self):
-        from dbutils import __version__, steady_pg
-        self.assertEqual(steady_pg.__version__, __version__)
-        self.assertEqual(steady_pg.SteadyPgConnection.version, __version__)
+def test_mocked_connection():
+    db_cls = pg.DB
+    db = db_cls(
+        'SteadyPgTestDB', user='SteadyPgTestUser')
+    assert hasattr(db, 'db')
+    assert hasattr(db.db, 'status')
+    assert db.db.status
+    assert hasattr(db.db, 'query')
+    assert hasattr(db.db, 'close')
+    assert not hasattr(db.db, 'reopen')
+    assert hasattr(db, 'reset')
+    assert hasattr(db.db, 'num_queries')
+    assert hasattr(db.db, 'session')
+    assert not hasattr(db.db, 'get_tables')
+    assert hasattr(db.db, 'db')
+    assert db.db.db == 'SteadyPgTestDB'
+    assert hasattr(db.db, 'user')
+    assert db.db.user == 'SteadyPgTestUser'
+    assert hasattr(db, 'query')
+    assert hasattr(db, 'close')
+    assert hasattr(db, 'reopen')
+    assert hasattr(db, 'reset')
+    assert hasattr(db, 'num_queries')
+    assert hasattr(db, 'session')
+    assert hasattr(db, 'get_tables')
+    assert hasattr(db, 'dbname')
+    assert db.dbname == 'SteadyPgTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'SteadyPgTestUser'
+    for i in range(3):
+        assert db.num_queries == i
+        assert db.query(f'select test{i}') == f'test{i}'
+    assert db.db.status
+    db.reopen()
+    assert db.db.status
+    assert db.num_queries == 0
+    assert db.query('select test4') == 'test4'
+    assert db.get_tables() == 'test'
+    db.close()
+    try:
+        status = db.db.status
+    except AttributeError:
+        status = False
+    assert not status
+    with pytest.raises(pg.InternalError):
+        db.close()
+    with pytest.raises(pg.InternalError):
+        db.query('select test')
+    with pytest.raises(pg.InternalError):
+        db.get_tables()
 
-    def test_mocked_connection(self):
-        PgConnection = pg.DB
-        db = PgConnection(
-            'SteadyPgTestDB', user='SteadyPgTestUser')
-        self.assertTrue(hasattr(db, 'db'))
-        self.assertTrue(hasattr(db.db, 'status'))
-        self.assertTrue(db.db.status)
-        self.assertTrue(hasattr(db.db, 'query'))
-        self.assertTrue(hasattr(db.db, 'close'))
-        self.assertFalse(hasattr(db.db, 'reopen'))
-        self.assertTrue(hasattr(db, 'reset'))
-        self.assertTrue(hasattr(db.db, 'num_queries'))
-        self.assertTrue(hasattr(db.db, 'session'))
-        self.assertFalse(hasattr(db.db, 'get_tables'))
-        self.assertTrue(hasattr(db.db, 'db'))
-        self.assertEqual(db.db.db, 'SteadyPgTestDB')
-        self.assertTrue(hasattr(db.db, 'user'))
-        self.assertEqual(db.db.user, 'SteadyPgTestUser')
-        self.assertTrue(hasattr(db, 'query'))
-        self.assertTrue(hasattr(db, 'close'))
-        self.assertTrue(hasattr(db, 'reopen'))
-        self.assertTrue(hasattr(db, 'reset'))
-        self.assertTrue(hasattr(db, 'num_queries'))
-        self.assertTrue(hasattr(db, 'session'))
-        self.assertTrue(hasattr(db, 'get_tables'))
-        self.assertTrue(hasattr(db, 'dbname'))
-        self.assertEqual(db.dbname, 'SteadyPgTestDB')
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertEqual(db.user, 'SteadyPgTestUser')
-        for i in range(3):
-            self.assertEqual(db.num_queries, i)
-            self.assertEqual(
-                db.query(f'select test{i}'), f'test{i}')
-        self.assertTrue(db.db.status)
-        db.reopen()
-        self.assertTrue(db.db.status)
-        self.assertEqual(db.num_queries, 0)
-        self.assertEqual(db.query('select test4'), 'test4')
-        self.assertEqual(db.get_tables(), 'test')
+
+def test_broken_connection():
+    with pytest.raises(TypeError):
+        SteadyPgConnection('wrong')
+    db = SteadyPgConnection(dbname='ok')
+    internal_error_cls = sys.modules[db._con.__module__].InternalError
+    for i in range(3):
         db.close()
-        try:
-            status = db.db.status
-        except AttributeError:
-            status = False
-        self.assertFalse(status)
-        self.assertRaises(pg.InternalError, db.close)
-        self.assertRaises(pg.InternalError, db.query, 'select test')
-        self.assertRaises(pg.InternalError, db.get_tables)
+    del db
+    with pytest.raises(internal_error_cls):
+        SteadyPgConnection(dbname='error')
 
-    def test_broken_connection(self):
-        self.assertRaises(TypeError, SteadyPgConnection, 'wrong')
-        db = SteadyPgConnection(dbname='ok')
-        InternalError = sys.modules[db._con.__module__].InternalError
-        for i in range(3):
-            db.close()
-        del db
-        self.assertRaises(InternalError, SteadyPgConnection, dbname='error')
 
-    def test_close(self):
-        for closeable in (False, True):
-            db = SteadyPgConnection(closeable=closeable)
-            self.assertTrue(db._con.db and db._con.valid)
-            db.close()
-            self.assertTrue(
-                closeable ^ (db._con.db is not None and db._con.valid))
-            db.close()
-            self.assertTrue(
-                closeable ^ (db._con.db is not None and db._con.valid))
-            db._close()
-            self.assertFalse(db._con.db and db._con.valid)
-            db._close()
-            self.assertFalse(db._con.db and db._con.valid)
+@pytest.mark.parametrize("closeable", [False, True])
+def test_close(closeable):
+    db = SteadyPgConnection(closeable=closeable)
+    assert db._con.db
+    assert db._con.valid is True
+    db.close()
+    assert closeable ^ (db._con.db is not None and db._con.valid)
+    db.close()
+    assert closeable ^ (db._con.db is not None and db._con.valid)
+    db._close()
+    assert not db._con.db
+    db._close()
+    assert not db._con.db
 
-    def test_connection(self):
-        db = SteadyPgConnection(
-            0, None, 1, 'SteadyPgTestDB', user='SteadyPgTestUser')
-        self.assertTrue(hasattr(db, 'db'))
-        self.assertTrue(hasattr(db, '_con'))
-        self.assertEqual(db.db, db._con.db)
-        self.assertTrue(hasattr(db, '_usage'))
-        self.assertEqual(db._usage, 0)
-        self.assertTrue(hasattr(db.db, 'status'))
-        self.assertTrue(db.db.status)
-        self.assertTrue(hasattr(db.db, 'query'))
-        self.assertTrue(hasattr(db.db, 'close'))
-        self.assertFalse(hasattr(db.db, 'reopen'))
-        self.assertTrue(hasattr(db.db, 'reset'))
-        self.assertTrue(hasattr(db.db, 'num_queries'))
-        self.assertTrue(hasattr(db.db, 'session'))
-        self.assertTrue(hasattr(db.db, 'db'))
-        self.assertEqual(db.db.db, 'SteadyPgTestDB')
-        self.assertTrue(hasattr(db.db, 'user'))
-        self.assertEqual(db.db.user, 'SteadyPgTestUser')
-        self.assertFalse(hasattr(db.db, 'get_tables'))
-        self.assertTrue(hasattr(db, 'query'))
-        self.assertTrue(hasattr(db, 'close'))
-        self.assertTrue(hasattr(db, 'reopen'))
-        self.assertTrue(hasattr(db, 'reset'))
-        self.assertTrue(hasattr(db, 'num_queries'))
-        self.assertTrue(hasattr(db, 'session'))
-        self.assertTrue(hasattr(db, 'dbname'))
-        self.assertEqual(db.dbname, 'SteadyPgTestDB')
-        self.assertTrue(hasattr(db, 'user'))
-        self.assertEqual(db.user, 'SteadyPgTestUser')
-        self.assertTrue(hasattr(db, 'get_tables'))
-        for i in range(3):
-            self.assertEqual(db._usage, i)
-            self.assertEqual(db.num_queries, i)
-            self.assertEqual(
-                db.query(f'select test{i}'), f'test{i}')
-        self.assertTrue(db.db.status)
-        self.assertEqual(db.get_tables(), 'test')
-        self.assertTrue(db.db.status)
-        self.assertEqual(db._usage, 4)
-        self.assertEqual(db.num_queries, 3)
-        db.reopen()
-        self.assertTrue(db.db.status)
-        self.assertEqual(db._usage, 0)
-        self.assertEqual(db.num_queries, 0)
-        self.assertEqual(db.query('select test'), 'test')
-        self.assertTrue(db.db.status)
-        self.assertTrue(hasattr(db._con, 'status'))
-        self.assertTrue(db._con.status)
-        self.assertTrue(hasattr(db._con, 'close'))
-        self.assertTrue(hasattr(db._con, 'query'))
-        db.close()
-        try:
-            status = db.db.status
-        except AttributeError:
-            status = False
-        self.assertFalse(status)
-        self.assertTrue(hasattr(db._con, 'close'))
-        self.assertTrue(hasattr(db._con, 'query'))
-        InternalError = sys.modules[db._con.__module__].InternalError
-        self.assertRaises(InternalError, db._con.close)
-        self.assertRaises(InternalError, db._con.query, 'select test')
-        self.assertEqual(db.query('select test'), 'test')
-        self.assertTrue(db.db.status)
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db.num_queries, 1)
-        db.db.status = False
-        self.assertFalse(db.db.status)
-        self.assertEqual(db.query('select test'), 'test')
-        self.assertTrue(db.db.status)
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db.num_queries, 1)
-        db.db.status = False
-        self.assertFalse(db.db.status)
-        self.assertEqual(db.get_tables(), 'test')
-        self.assertTrue(db.db.status)
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db.num_queries, 0)
 
-    def test_connection_context_handler(self):
-        db = SteadyPgConnection(
-            0, None, 1, 'SteadyPgTestDB', user='SteadyPgTestUser')
-        self.assertEqual(db.session, [])
-        with db:
-            db.query('select test')
-        self.assertEqual(db.session, ['begin', 'commit'])
-        try:
-            with db:
-                db.query('error')
-        except pg.ProgrammingError:
-            error = True
-        else:
-            error = False
-        self.assertTrue(error)
-        self.assertEqual(
-            db._con.session, ['begin', 'commit', 'begin', 'rollback'])
+def test_connection():
+    db = SteadyPgConnection(
+        0, None, 1, 'SteadyPgTestDB', user='SteadyPgTestUser')
+    assert hasattr(db, 'db')
+    assert hasattr(db, '_con')
+    assert db.db == db._con.db
+    assert hasattr(db, '_usage')
+    assert db._usage == 0
+    assert hasattr(db.db, 'status')
+    assert db.db.status
+    assert hasattr(db.db, 'query')
+    assert hasattr(db.db, 'close')
+    assert not hasattr(db.db, 'reopen')
+    assert hasattr(db.db, 'reset')
+    assert hasattr(db.db, 'num_queries')
+    assert hasattr(db.db, 'session')
+    assert hasattr(db.db, 'db')
+    assert db.db.db == 'SteadyPgTestDB'
+    assert hasattr(db.db, 'user')
+    assert db.db.user == 'SteadyPgTestUser'
+    assert not hasattr(db.db, 'get_tables')
+    assert hasattr(db, 'query')
+    assert hasattr(db, 'close')
+    assert hasattr(db, 'reopen')
+    assert hasattr(db, 'reset')
+    assert hasattr(db, 'num_queries')
+    assert hasattr(db, 'session')
+    assert hasattr(db, 'dbname')
+    assert db.dbname == 'SteadyPgTestDB'
+    assert hasattr(db, 'user')
+    assert db.user == 'SteadyPgTestUser'
+    assert hasattr(db, 'get_tables')
+    for i in range(3):
+        assert db._usage == i
+        assert db.num_queries == i
+        assert db.query(f'select test{i}') == f'test{i}'
+    assert db.db.status
+    assert db.get_tables() == 'test'
+    assert db.db.status
+    assert db._usage == 4
+    assert db.num_queries == 3
+    db.reopen()
+    assert db.db.status
+    assert db._usage == 0
+    assert db.num_queries == 0
+    assert db.query('select test') == 'test'
+    assert db.db.status
+    assert hasattr(db._con, 'status')
+    assert db._con.status
+    assert hasattr(db._con, 'close')
+    assert hasattr(db._con, 'query')
+    db.close()
+    try:
+        status = db.db.status
+    except AttributeError:
+        status = False
+    assert not status
+    assert hasattr(db._con, 'close')
+    assert hasattr(db._con, 'query')
+    internal_error_cls = sys.modules[db._con.__module__].InternalError
+    with pytest.raises(internal_error_cls):
+        db._con.close()
+    with pytest.raises(internal_error_cls):
+        db._con.query('select test')
+    assert db.query('select test') == 'test'
+    assert db.db.status
+    assert db._usage == 1
+    assert db.num_queries == 1
+    db.db.status = False
+    assert not db.db.status
+    assert db.query('select test') == 'test'
+    assert db.db.status
+    assert db._usage == 1
+    assert db.num_queries == 1
+    db.db.status = False
+    assert not db.db.status
+    assert db.get_tables() == 'test'
+    assert db.db.status
+    assert db._usage == 1
+    assert db.num_queries == 0
 
-    def test_connection_maxusage(self):
-        db = SteadyPgConnection(10)
-        for i in range(100):
-            r = db.query(f'select test{i}')
-            self.assertEqual(r, f'test{i}')
-            self.assertTrue(db.db.status)
-            j = i % 10 + 1
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db.num_queries, j)
-        db.begin()
-        for i in range(100):
-            r = db.get_tables()
-            self.assertEqual(r, 'test')
-            self.assertTrue(db.db.status)
-            if i == 49:
-                db.commit()
-            j = i % 10 + 1 if i > 49 else i + 11
-            self.assertEqual(db._usage, j)
-            j = 0 if i > 49 else 10
-            self.assertEqual(db.num_queries, j)
-        for i in range(10):
-            if i == 7:
-                db.db.status = False
-            r = db.query(f'select test{i}')
-            self.assertEqual(r, f'test{i}')
-            j = i % 7 + 1
-            self.assertEqual(db._usage, j)
-            self.assertEqual(db.num_queries, j)
-        for i in range(10):
-            if i == 5:
-                db.db.status = False
-            r = db.get_tables()
-            self.assertEqual(r, 'test')
-            j = (i + (3 if i < 5 else -5)) % 10 + 1
-            self.assertEqual(db._usage, j)
-            j = 3 if i < 5 else 0
-            self.assertEqual(db.num_queries, j)
-        db.close()
-        self.assertEqual(db.query('select test1'), 'test1')
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db.num_queries, 1)
-        db.reopen()
-        self.assertEqual(db._usage, 0)
-        self.assertEqual(db.num_queries, 0)
-        self.assertEqual(db.query('select test2'), 'test2')
-        self.assertEqual(db._usage, 1)
-        self.assertEqual(db.num_queries, 1)
 
-    def test_connection_setsession(self):
-        db = SteadyPgConnection(3, ('set time zone', 'set datestyle'))
-        self.assertTrue(hasattr(db, 'num_queries'))
-        self.assertEqual(db.num_queries, 0)
-        self.assertTrue(hasattr(db, 'session'))
-        self.assertEqual(tuple(db.session), ('time zone', 'datestyle'))
-        for i in range(11):
-            db.query('select test')
-        self.assertEqual(db.num_queries, 2)
-        self.assertEqual(db.session, ['time zone', 'datestyle'])
-        db.query('set test')
-        self.assertEqual(db.num_queries, 2)
-        self.assertEqual(db.session, ['time zone', 'datestyle', 'test'])
+def test_connection_context_handler():
+    db = SteadyPgConnection(
+        0, None, 1, 'SteadyPgTestDB', user='SteadyPgTestUser')
+    assert db.session == []
+    with db:
         db.query('select test')
-        self.assertEqual(db.num_queries, 1)
-        self.assertEqual(db.session, ['time zone', 'datestyle'])
-        db.close()
-        db.query('set test')
-        self.assertEqual(db.num_queries, 0)
-        self.assertEqual(db.session, ['time zone', 'datestyle', 'test'])
+    assert db.session == ['begin', 'commit']
+    try:
+        with db:
+            db.query('error')
+    except pg.ProgrammingError:
+        error = True
+    else:
+        error = False
+    assert error
+    assert db._con.session == ['begin', 'commit', 'begin', 'rollback']
 
-    def test_begin(self):
-        for closeable in (False, True):
-            db = SteadyPgConnection(closeable=closeable)
-            db.begin()
-            self.assertEqual(db.session, ['begin'])
-            db.query('select test')
-            self.assertEqual(db.num_queries, 1)
-            db.close()
-            db.query('select test')
-            self.assertEqual(db.num_queries, 1)
-            db.begin()
-            self.assertEqual(db.session, ['begin'])
-            db.db.close()
-            self.assertRaises(pg.InternalError, db.query, 'select test')
-            self.assertEqual(db.num_queries, 0)
-            db.query('select test')
-            self.assertEqual(db.num_queries, 1)
-            self.assertEqual(db.begin('select sql:begin'), 'sql:begin')
-            self.assertEqual(db.num_queries, 2)
 
-    def test_end(self):
-        for closeable in (False, True):
-            db = SteadyPgConnection(closeable=closeable)
-            db.begin()
-            db.query('select test')
-            db.end()
-            self.assertEqual(db.session, ['begin', 'end'])
-            db.db.close()
-            db.query('select test')
-            self.assertEqual(db.num_queries, 1)
-            self.assertEqual(db.begin('select sql:end'), 'sql:end')
-            self.assertEqual(db.num_queries, 2)
-            db.begin()
-            db.query('select test')
+def test_connection_maxusage():
+    db = SteadyPgConnection(10)
+    for i in range(100):
+        r = db.query(f'select test{i}')
+        assert r == f'test{i}'
+        assert db.db.status
+        j = i % 10 + 1
+        assert db._usage == j
+        assert db.num_queries == j
+    db.begin()
+    for i in range(100):
+        r = db.get_tables()
+        assert r == 'test'
+        assert db.db.status
+        if i == 49:
             db.commit()
-            self.assertEqual(db.session, ['begin', 'commit'])
-            db.db.close()
-            db.query('select test')
-            self.assertEqual(db.num_queries, 1)
-            self.assertEqual(db.begin('select sql:commit'), 'sql:commit')
-            self.assertEqual(db.num_queries, 2)
-            db.begin()
-            db.query('select test')
-            db.rollback()
-            self.assertEqual(db.session, ['begin', 'rollback'])
-            db.db.close()
-            db.query('select test')
-            self.assertEqual(db.num_queries, 1)
-            self.assertEqual(db.begin('select sql:rollback'), 'sql:rollback')
-            self.assertEqual(db.num_queries, 2)
+        j = i % 10 + 1 if i > 49 else i + 11
+        assert db._usage == j
+        j = 0 if i > 49 else 10
+        assert db.num_queries == j
+    for i in range(10):
+        if i == 7:
+            db.db.status = False
+        r = db.query(f'select test{i}')
+        assert r == f'test{i}'
+        j = i % 7 + 1
+        assert db._usage == j
+        assert db.num_queries == j
+    for i in range(10):
+        if i == 5:
+            db.db.status = False
+        r = db.get_tables()
+        assert r == 'test'
+        j = (i + (3 if i < 5 else -5)) % 10 + 1
+        assert db._usage == j
+        j = 3 if i < 5 else 0
+        assert db.num_queries == j
+    db.close()
+    assert db.query('select test1') == 'test1'
+    assert db._usage == 1
+    assert db.num_queries == 1
+    db.reopen()
+    assert db._usage == 0
+    assert db.num_queries == 0
+    assert db.query('select test2') == 'test2'
+    assert db._usage == 1
+    assert db.num_queries == 1
+
+
+def test_connection_setsession():
+    db = SteadyPgConnection(3, ('set time zone', 'set datestyle'))
+    assert hasattr(db, 'num_queries')
+    assert db.num_queries == 0
+    assert hasattr(db, 'session')
+    assert tuple(db.session) == ('time zone', 'datestyle')
+    for i in range(11):
+        db.query('select test')
+    assert db.num_queries == 2
+    assert db.session == ['time zone', 'datestyle']
+    db.query('set test')
+    assert db.num_queries == 2
+    assert db.session == ['time zone', 'datestyle', 'test']
+    db.query('select test')
+    assert db.num_queries == 1
+    assert db.session == ['time zone', 'datestyle']
+    db.close()
+    db.query('set test')
+    assert db.num_queries == 0
+    assert db.session == ['time zone', 'datestyle', 'test']
+
+
+@pytest.mark.parametrize("closeable", [False, True])
+def test_begin(closeable):
+    db = SteadyPgConnection(closeable=closeable)
+    db.begin()
+    assert db.session == ['begin']
+    db.query('select test')
+    assert db.num_queries == 1
+    db.close()
+    db.query('select test')
+    assert db.num_queries == 1
+    db.begin()
+    assert db.session == ['begin']
+    db.db.close()
+    with pytest.raises(pg.InternalError):
+        db.query('select test')
+    assert db.num_queries == 0
+    db.query('select test')
+    assert db.num_queries == 1
+    assert db.begin('select sql:begin') == 'sql:begin'
+    assert db.num_queries == 2
 
 
-if __name__ == '__main__':
-    unittest.main()
+@pytest.mark.parametrize("closeable", [False, True])
+def test_end(closeable):
+    db = SteadyPgConnection(closeable=closeable)
+    db.begin()
+    db.query('select test')
+    db.end()
+    assert db.session == ['begin', 'end']
+    db.db.close()
+    db.query('select test')
+    assert db.num_queries == 1
+    assert db.begin('select sql:end') == 'sql:end'
+    assert db.num_queries == 2
+    db.begin()
+    db.query('select test')
+    db.commit()
+    assert db.session == ['begin', 'commit']
+    db.db.close()
+    db.query('select test')
+    assert db.num_queries == 1
+    assert db.begin('select sql:commit') == 'sql:commit'
+    assert db.num_queries == 2
+    db.begin()
+    db.query('select test')
+    db.rollback()
+    assert db.session == ['begin', 'rollback']
+    db.db.close()
+    db.query('select test')
+    assert db.num_queries == 1
+    assert db.begin('select sql:rollback') == 'sql:rollback'
+    assert db.num_queries == 2
diff --git a/tests/test_threading_local.py b/tests/test_threading_local.py
index b44c613..2caf138 100644
--- a/tests/test_threading_local.py
+++ b/tests/test_threading_local.py
@@ -1,86 +1,83 @@
 """Test the ThreadingLocal module."""
 
-import unittest
 from threading import Thread
 
 from dbutils.persistent_db import local
 
 
-class TestThreadingLocal(unittest.TestCase):
-
-    def test_getattr(self):
-        my_data = local()
-        my_data.number = 42
-        self.assertEqual(my_data.number, 42)
-
-    def test_dict(self):
-        my_data = local()
-        my_data.number = 42
-        self.assertEqual(my_data.__dict__, {'number': 42})
-        my_data.__dict__.setdefault('widgets', [])
-        self.assertEqual(my_data.widgets, [])
-
-    def test_threadlocal(self):
-        def f():
-            items = sorted(my_data.__dict__.items())
-            log.append(items)
-            my_data.number = 11
-            log.append(my_data.number)
-        my_data = local()
-        my_data.number = 42
-        log = []
-        thread = Thread(target=f)
-        thread.start()
-        thread.join()
-        self.assertEqual(log, [[], 11])
-        self.assertEqual(my_data.number, 42)
-
-    def test_subclass(self):
-
-        class MyLocal(local):
-            number = 2
-            initialized = 0
-
-            def __init__(self, **kw):
-                if self.initialized:
-                    raise SystemError
-                self.initialized = 1
-                self.__dict__.update(kw)
-
-            def squared(self):
-                return self.number ** 2
-
-        my_data = MyLocal(color='red')
-        self.assertEqual(my_data.number, 2)
-        self.assertEqual(my_data.color, 'red')
-        del my_data.color
-        self.assertEqual(my_data.squared(), 4)
-
-        def f():
-            items = sorted(my_data.__dict__.items())
-            log.append(items)
-            my_data.number = 7
-            log.append(my_data.number)
-
-        log = []
-        thread = Thread(target=f)
-        thread.start()
-        thread.join()
-        self.assertEqual(log, [[('color', 'red'), ('initialized', 1)], 7])
-        self.assertEqual(my_data.number, 2)
-        self.assertFalse(hasattr(my_data, 'color'))
-
-        class MyLocal(local):
-            __slots__ = 'number'
-
-        my_data = MyLocal()
-        my_data.number = 42
-        my_data.color = 'red'
-        thread = Thread(target=f)
-        thread.start()
-        thread.join()
-        self.assertEqual(my_data.number, 7)
-
-
-if __name__ == '__main__':
-    unittest.main()
+def test_getattr():
+    my_data = local()
+    my_data.number = 42
+    assert my_data.number == 42
+
+
+def test_dict():
+    my_data = local()
+    my_data.number = 42
+    assert my_data.__dict__ == {'number': 42}
+    my_data.__dict__.setdefault('widgets', [])
+    assert my_data.widgets == []
+
+
+def test_threadlocal():
+    def f():
+        items = sorted(my_data.__dict__.items())
+        log.append(items)
+        my_data.number = 11
+        log.append(my_data.number)
+    my_data = local()
+    my_data.number = 42
+    log = []
+    thread = Thread(target=f)
+    thread.start()
+    thread.join()
+    assert log == [[], 11]
+    assert my_data.number == 42
+
+
+def test_subclass():
+
+    class MyLocal(local):
+        number = 2
+        initialized = 0
+
+        def __init__(self, **kw):
+            if self.initialized:
+                raise SystemError
+            self.initialized = 1
+            self.__dict__.update(kw)
+
+        def squared(self):
+            return self.number ** 2
+
+    my_data = MyLocal(color='red')
+    assert my_data.number == 2
+    assert my_data.color == 'red'
+    del my_data.color
+    assert my_data.squared() == 4
+
+    def f():
+        items = sorted(my_data.__dict__.items())
+        log.append(items)
+        my_data.number = 7
+        log.append(my_data.number)
+
+    log = []
+    thread = Thread(target=f)
+    thread.start()
+    thread.join()
+    assert log == [[('color', 'red'), ('initialized', 1)], 7]
+    assert my_data.number == 2
+    assert not hasattr(my_data, 'color')
+
+    class MyLocal(local):
+        __slots__ = 'number'
+
+    my_data = MyLocal()
+    my_data.number = 42
+    my_data.color = 'red'
+    thread = Thread(target=f)
+    thread.start()
+    thread.join()
+    assert my_data.number == 7
+
diff --git a/tox.ini b/tox.ini
index 1b03272..812d428 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@ envlist = py3{7,8,9,10,11}, ruff, manifest, docs, spell
 [testenv]
 setenv =
     PYTHONPATH = {toxinidir}
-deps = pytest
+deps = pytest>=7
 commands =
     pytest {posargs}
 

From 7e2d00a91fca169675cca56bc89b0431c78da2ea Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 21:32:46 +0200
Subject: [PATCH 54/84] Assume modern Python when using Queue class

The timeout parameter actually exists since Python 2.3...
---
 tests/test_persistent_db.py    | 69 ++++++++--------------------------
 tests/test_persistent_pg.py    | 67 ++++++++-------------------------
 tests/test_pooled_db.py        | 39 +++++--------------
 tests/test_pooled_pg.py        | 43 +++++++++------------
 tests/test_simple_pooled_db.py | 38 +++++++------------
 tests/test_simple_pooled_pg.py | 22 +++--------
 tests/test_steady_db.py        |  3 +-
 tests/test_steady_pg.py        |  1 +
 tests/test_threading_local.py  |  1 -
 9 files changed, 78 insertions(+), 205 deletions(-)

diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index 25897fd..d8fb504 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -76,10 +76,7 @@ def run_queries(i):
         db = None
         while True:
             try:
-                try:
-                    q = query_queue[i].get(1, 1)
-                except TypeError:
-                    q = query_queue[i].get(1)
+                q = query_queue[i].get(timeout=1)
             except Empty:
                 q = None
             if not q:
@@ -99,10 +96,7 @@ def run_queries(i):
                     r = cursor.fetchone()
                     cursor.close()
             r = f'{i}({db._usage}): {r}'
-            try:
-                result_queue[i].put(r, 1, 1)
-            except TypeError:
-                result_queue[i].put(r, 1)
+            result_queue[i].put(r, timeout=1)
         if db:
             db.close()
 
@@ -112,68 +106,35 @@ def run_queries(i):
         threads.append(thread)
         thread.start()
     for i in range(num_threads):
-        try:
-            query_queue[i].put('ping', 1, 1)
-        except TypeError:
-            query_queue[i].put('ping', 1)
+        query_queue[i].put('ping', timeout=1)
     for i in range(num_threads):
-        try:
-            r = result_queue[i].get(1, 1)
-        except TypeError:
-            r = result_queue[i].get(1)
+        r = result_queue[i].get(timeout=1)
         assert r == f'{i}(0): ok - thread alive'
         assert threads[i].is_alive()
     for i in range(num_threads):
         for j in range(i + 1):
-            try:
-                query_queue[i].put(f'select test{j}', 1, 1)
-                r = result_queue[i].get(1, 1)
-            except TypeError:
-                query_queue[i].put(f'select test{j}', 1)
-                r = result_queue[i].get(1)
+            query_queue[i].put(f'select test{j}', timeout=1)
+            r = result_queue[i].get(timeout=1)
             assert r == f'{i}({j + 1}): test{j}'
-    try:
-        query_queue[1].put('select test4', 1, 1)
-    except TypeError:
-        query_queue[1].put('select test4', 1)
-    try:
-        r = result_queue[1].get(1, 1)
-    except TypeError:
-        r = result_queue[1].get(1)
+    query_queue[1].put('select test4', timeout=1)
+    r = result_queue[1].get(timeout=1)
     assert r == '1(3): test4'
-    try:
-        query_queue[1].put('close', 1, 1)
-        r = result_queue[1].get(1, 1)
-    except TypeError:
-        query_queue[1].put('close', 1)
-        r = result_queue[1].get(1)
+    query_queue[1].put('close', timeout=1)
+    r = result_queue[1].get(timeout=1)
     assert r == '1(3): ok - connection closed'
     for j in range(2):
-        try:
-            query_queue[1].put(f'select test{j}', 1, 1)
-            r = result_queue[1].get(1, 1)
-        except TypeError:
-            query_queue[1].put(f'select test{j}', 1)
-            r = result_queue[1].get(1)
+        query_queue[1].put(f'select test{j}', timeout=1)
+        r = result_queue[1].get(timeout=1)
         assert r == f'1({j + 1}): test{j}'
     for i in range(num_threads):
         assert threads[i].is_alive()
-        try:
-            query_queue[i].put('ping', 1, 1)
-        except TypeError:
-            query_queue[i].put('ping', 1)
+        query_queue[i].put('ping', timeout=1)
     for i in range(num_threads):
-        try:
-            r = result_queue[i].get(1, 1)
-        except TypeError:
-            r = result_queue[i].get(1)
+        r = result_queue[i].get(timeout=1)
         assert r == f'{i}({i + 1}): ok - thread alive'
         assert threads[i].is_alive()
     for i in range(num_threads):
-        try:
-            query_queue[i].put(None, 1, 1)
-        except TypeError:
-            query_queue[i].put(None, 1)
+        query_queue[i].put(None, timeout=1)
 
 
 def test_maxusage(dbapi):  # noqa: F811
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index bd6c21a..c8d66d6 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -54,10 +54,7 @@ def run_queries(i):
         db = None
         while True:
             try:
-                try:
-                    q = query_queue[i].get(1, 1)
-                except TypeError:
-                    q = query_queue[i].get(1)
+                q = query_queue[i].get(timeout=1)
             except Empty:
                 q = None
             if not q:
@@ -74,10 +71,7 @@ def run_queries(i):
                 else:
                     r = db.query(q)
             r = f'{i}({db._usage}): {r}'
-            try:
-                result_queue[i].put(r, 1, 1)
-            except TypeError:
-                result_queue[i].put(r, 1)
+            result_queue[i].put(r, timeout=1)
         if db:
             db.close()
 
@@ -87,66 +81,35 @@ def run_queries(i):
         threads.append(thread)
         thread.start()
     for i in range(num_threads):
-        try:
-            query_queue[i].put('ping', 1, 1)
-        except TypeError:
-            query_queue[i].put('ping', 1)
+        query_queue[i].put('ping', timeout=1)
     for i in range(num_threads):
-        try:
-            r = result_queue[i].get(1, 1)
-        except TypeError:
-            r = result_queue[i].get(1)
+        r = result_queue[i].get(timeout=1)
         assert r == f'{i}(0): ok - thread alive'
         assert threads[i].is_alive()
     for i in range(num_threads):
         for j in range(i + 1):
-            try:
-                query_queue[i].put(f'select test{j}', 1, 1)
-                r = result_queue[i].get(1, 1)
-            except TypeError:
-                query_queue[i].put(f'select test{j}', 1)
-                r = result_queue[i].get(1)
+            query_queue[i].put(f'select test{j}', timeout=1)
+            r = result_queue[i].get(timeout=1)
             assert r == f'{i}({j + 1}): test{j}'
-    try:
-        query_queue[1].put('select test4', 1, 1)
-        r = result_queue[1].get(1, 1)
-    except TypeError:
-        query_queue[1].put('select test4', 1)
-        r = result_queue[1].get(1)
+    query_queue[1].put('select test4', timeout=1)
+    r = result_queue[1].get(timeout=1)
     assert r == '1(3): test4'
-    try:
-        query_queue[1].put('close', 1, 1)
-        r = result_queue[1].get(1, 1)
-    except TypeError:
-        query_queue[1].put('close', 1)
-        r = result_queue[1].get(1)
+    query_queue[1].put('close', timeout=1)
+    r = result_queue[1].get(timeout=1)
     assert r == '1(3): ok - connection closed'
     for j in range(2):
-        try:
-            query_queue[1].put(f'select test{j}', 1, 1)
-            r = result_queue[1].get(1, 1)
-        except TypeError:
-            query_queue[1].put(f'select test{j}', 1)
-            r = result_queue[1].get(1)
+        query_queue[1].put(f'select test{j}', timeout=1)
+        r = result_queue[1].get(timeout=1)
         assert r == f'1({j + 1}): test{j}'
     for i in range(num_threads):
         assert threads[i].is_alive()
-        try:
-            query_queue[i].put('ping', 1, 1)
-        except TypeError:
-            query_queue[i].put('ping', 1)
+        query_queue[i].put('ping', timeout=1)
     for i in range(num_threads):
-        try:
-            r = result_queue[i].get(1, 1)
-        except TypeError:
-            r = result_queue[i].get(1)
+        r = result_queue[i].get(timeout=1)
         assert r == f'{i}({i + 1}): ok - thread alive'
         assert threads[i].is_alive()
     for i in range(num_threads):
-        try:
-            query_queue[i].put(None, 1, 1)
-        except TypeError:
-            query_queue[i].put(None, 1)
+        query_queue[i].put(None, timeout=1)
 
 
 def test_maxusage():
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index ebbe88d..8bbd866 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -1022,34 +1022,20 @@ def test_three_threads_two_connections(dbapi, threadsafety):  # noqa: F811
     queue = Queue(3)
 
     def connection():
-        try:
-            queue.put(pool.connection(), 1, 1)
-        except Exception:
-            queue.put(pool.connection(), 1)
+        queue.put(pool.connection(), timeout=1)
 
     for i in range(3):
         Thread(target=connection).start()
-    try:
-        db1 = queue.get(1, 1)
-        db2 = queue.get(1, 1)
-    except TypeError:
-        db1 = queue.get(1)
-        db2 = queue.get(1)
+    db1 = queue.get(timeout=1)
+    db2 = queue.get(timeout=1)
     assert db1 != db2
     db1_con = db1._con
     db2_con = db2._con
     assert db1_con != db2_con
-    try:
-        with pytest.raises(Empty):
-            queue.get(1, 0.1)
-    except TypeError:
-        with pytest.raises(Empty):
-            queue.get(0)
+    with pytest.raises(Empty):
+        queue.get(timeout=0.1)
     del db1
-    try:
-        db1 = queue.get(1, 1)
-    except TypeError:
-        db1 = queue.get(1)
+    db1 = queue.get(timeout=1)
     assert db1 != db2
     assert db1._con != db2._con
     assert db1._con == db1_con
@@ -1061,17 +1047,10 @@ def connection():
     db2_con = db2._con
     assert db1_con != db2_con
     Thread(target=connection).start()
-    try:
-        with pytest.raises(Empty):
-            queue.get(1, 0.1)
-    except TypeError:
-        with pytest.raises(Empty):
-            queue.get(0)
+    with pytest.raises(Empty):
+        queue.get(timeout=0.1)
     del db1
-    try:
-        db1 = queue.get(1, 1)
-    except TypeError:
-        db1 = queue.get(1)
+    db1 = queue.get(timeout=1)
     assert db1 != db2
     assert db1._con != db2._con
     assert db1._con == db1_con
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index b09469f..93c266c 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -101,13 +101,15 @@ def test_close_connection():
     db = pool.connection()
     assert pool._cache.qsize() == 1
     assert pool._cache.get(0) == db_con
+    assert db
+    del db
 
 
 def test_min_max_cached():
     pool = PooledPg(3)
     assert hasattr(pool, '_cache')
     assert pool._cache.qsize() == 3
-    cache = [pool.connection() for i in range(3)]
+    cache = [pool.connection() for _i in range(3)]
     assert pool._cache.qsize() == 0
     for i in range(3):
         cache.pop().close()
@@ -121,7 +123,7 @@ def test_min_max_cached():
     pool = PooledPg(3, 4)
     assert hasattr(pool, '_cache')
     assert pool._cache.qsize() == 3
-    cache = [pool.connection() for i in range(3)]
+    cache = [pool.connection() for _i in range(3)]
     assert pool._cache.qsize() == 0
     for i in range(3):
         cache.pop().close()
@@ -135,7 +137,7 @@ def test_min_max_cached():
     pool = PooledPg(3, 2)
     assert hasattr(pool, '_cache')
     assert pool._cache.qsize() == 3
-    cache = [pool.connection() for i in range(4)]
+    cache = [pool.connection() for _i in range(4)]
     assert pool._cache.qsize() == 0
     for i in range(4):
         cache.pop().close()
@@ -143,7 +145,7 @@ def test_min_max_cached():
     pool = PooledPg(2, 5)
     assert hasattr(pool, '_cache')
     assert pool._cache.qsize() == 2
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert pool._cache.qsize() == 0
     for i in range(10):
         cache.pop().close()
@@ -153,7 +155,7 @@ def test_min_max_cached():
 def test_max_connections():
     pool = PooledPg(1, 2, 3)
     assert pool._cache.qsize() == 1
-    cache = [pool.connection() for i in range(3)]
+    cache = [pool.connection() for _i in range(3)]
     assert pool._cache.qsize() == 0
     with pytest.raises(TooManyConnections):
         pool.connection()
@@ -164,7 +166,9 @@ def test_max_connections():
     assert pool._cache.qsize() == 0
     with pytest.raises(TooManyConnections):
         pool.connection()
+    assert db
     del db
+    assert cache
     del cache
     pool = PooledPg(1, 2, 1)
     assert pool._cache.qsize() == 1
@@ -176,7 +180,7 @@ def test_max_connections():
         pool.connection()
     pool = PooledPg(3, 2, 1, False)
     assert pool._cache.qsize() == 3
-    cache = [pool.connection() for i in range(3)]
+    cache = [pool.connection() for _i in range(3)]
     assert len(cache) == 3
     assert pool._cache.qsize() == 0
     with pytest.raises(TooManyConnections):
@@ -204,6 +208,7 @@ def connection():
     db = pool.connection()
     assert pool._cache.qsize() == 0
     assert session == ['thread']
+    assert db
     del db
 
 
@@ -236,34 +241,20 @@ def test_three_threads_two_connections():
     queue = Queue(3)
 
     def connection():
-        try:
-            queue.put(pool.connection(), 1, 1)
-        except TypeError:
-            queue.put(pool.connection(), 1)
+        queue.put(pool.connection(), timeout=1)
 
     for i in range(3):
         Thread(target=connection).start()
-    try:
-        db1 = queue.get(1, 1)
-        db2 = queue.get(1, 1)
-    except TypeError:
-        db1 = queue.get(1)
-        db2 = queue.get(1)
+    db1 = queue.get(timeout=1)
+    db2 = queue.get(timeout=1)
     db1_con = db1._con
     db2_con = db2._con
     assert db1 != db2
     assert db1_con != db2_con
-    try:
-        with pytest.raises(Empty):
-            queue.get(1, 0.1)
-    except TypeError:
-        with pytest.raises(Empty):
-            queue.get(0)
+    with pytest.raises(Empty):
+        queue.get(timeout=0.1)
     del db1
-    try:
-        db1 = queue.get(1, 1)
-    except TypeError:
-        db1 = queue.get(1)
+    db1 = queue.get(timeout=1)
     assert db1 != db2
     assert db1._con != db2._con
     assert db1._con == db1_con
diff --git a/tests/test_simple_pooled_db.py b/tests/test_simple_pooled_db.py
index 3cfd99e..f112d64 100644
--- a/tests/test_simple_pooled_db.py
+++ b/tests/test_simple_pooled_db.py
@@ -12,6 +12,7 @@
 """
 
 from queue import Queue, Empty
+from threading import Thread
 
 import pytest
 
@@ -20,15 +21,16 @@
 from dbutils import simple_pooled_db
 
 
-def my_db_pool(mythreadsafety, max_connections):
-    threadsafety = dbapi.threadsafety
-    dbapi.threadsafety = mythreadsafety
+def my_db_pool(threadsafety, max_connections):
+    """Get simple PooledDB connection."""
+    dbapi_threadsafety = dbapi.threadsafety
+    dbapi.threadsafety = threadsafety
     try:
         return simple_pooled_db.PooledDB(
             dbapi, max_connections,
             'SimplePooledDBTestDB', 'SimplePooledDBTestUser')
     finally:
-        dbapi.threadsafety = threadsafety
+        dbapi.threadsafety = dbapi_threadsafety
 
 
 def test_version():
@@ -87,10 +89,10 @@ def test_close_connection(threadsafety):
 def test_two_connections(threadsafety):
     db_pool = my_db_pool(threadsafety, 2)
     db1 = db_pool.connection()
-    cursors1 = [db1.cursor() for i in range(5)]
+    cursors1 = [db1.cursor() for _i_ in range(5)]
     db2 = db_pool.connection()
     assert db1 != db2
-    cursors2 = [db2.cursor() for i in range(7)]
+    cursors2 = [db2.cursor() for _i in range(7)]
     assert db1.open_cursors == 5
     assert db2.open_cursors == 7
     db1.close()
@@ -113,28 +115,16 @@ def test_threadsafety_1():
     def connection():
         queue.put(db_pool.connection())
 
-    from threading import Thread
-    threads = [Thread(target=connection).start() for i in range(3)]
+    threads = [Thread(target=connection).start() for _i in range(3)]
     assert len(threads) == 3
-    try:
-        db1 = queue.get(1, 1)
-        db2 = queue.get(1, 1)
-    except TypeError:
-        db1 = queue.get(1)
-        db2 = queue.get(1)
+    db1 = queue.get(timeout=1)
+    db2 = queue.get(timeout=1)
     assert db1 != db2
     assert db1._con != db2._con
-    try:
-        with pytest.raises(Empty):
-            queue.get(1, 0.1)
-    except TypeError:
-        with pytest.raises(Empty):
-            queue.get(0)
+    with pytest.raises(Empty):
+        queue.get(timeout=0.1)
     db2.close()
-    try:
-        db3 = queue.get(1, 1)
-    except TypeError:
-        db3 = queue.get(1)
+    db3 = queue.get(timeout=1)
     assert db1 != db3
     assert db1._con != db3._con
 
diff --git a/tests/test_simple_pooled_pg.py b/tests/test_simple_pooled_pg.py
index 2652c4c..1492573 100644
--- a/tests/test_simple_pooled_pg.py
+++ b/tests/test_simple_pooled_pg.py
@@ -20,6 +20,7 @@
 
 
 def my_db_pool(max_connections):
+    """Get simple PooledPg connection."""
     return simple_pooled_pg.PooledPg(
         max_connections, 'SimplePooledPgTestDB', 'SimplePooledPgTestUser')
 
@@ -95,24 +96,13 @@ def connection():
 
     threads = [Thread(target=connection).start() for _i in range(3)]
     assert len(threads) == 3
-    try:
-        db1 = queue.get(1, 1)
-        db2 = queue.get(1, 1)
-    except TypeError:
-        db1 = queue.get(1)
-        db2 = queue.get(1)
+    db1 = queue.get(timeout=1)
+    db2 = queue.get(timeout=1)
     assert db1 != db2
     assert db1._con != db2._con
-    try:
-        with pytest.raises(Empty):
-            queue.get(1, 0.1)
-    except TypeError:
-        with pytest.raises(Empty):
-            queue.get(0)
+    with pytest.raises(Empty):
+        queue.get(timeout=0.1)
     db2.close()
-    try:
-        db3 = queue.get(1, 1)
-    except TypeError:
-        db3 = queue.get(1)
+    db3 = queue.get(timeout=1)
     assert db1 != db3
     assert db1._con != db3._con
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index c2d1f95..c734928 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -111,7 +111,7 @@ def test_broken_connection():
     for i in range(3):
         db.close()
     del db
-    with pytest.raises(dbapi.OperationalError):#
+    with pytest.raises(dbapi.OperationalError):
         SteadyDBconnect(dbapi, database='error')
     db = SteadyDBconnect(dbapi, database='ok')
     cursor = db.cursor()
@@ -724,4 +724,3 @@ def test_rollback_error():
     db.rollback()
     assert db._con.session == ['rollback']
     assert db._con.valid
-
diff --git a/tests/test_steady_pg.py b/tests/test_steady_pg.py
index 094c7bc..ef1659d 100644
--- a/tests/test_steady_pg.py
+++ b/tests/test_steady_pg.py
@@ -24,6 +24,7 @@ def test_version():
     assert steady_pg.__version__ == __version__
     assert steady_pg.SteadyPgConnection.version == __version__
 
+
 def test_mocked_connection():
     db_cls = pg.DB
     db = db_cls(
diff --git a/tests/test_threading_local.py b/tests/test_threading_local.py
index 2caf138..2c86525 100644
--- a/tests/test_threading_local.py
+++ b/tests/test_threading_local.py
@@ -80,4 +80,3 @@ class MyLocal(local):
     thread.start()
     thread.join()
     assert my_data.number == 7
-

From 9e56dfca11960048d3c27a3ed95a69235b4743cd Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 21:44:51 +0200
Subject: [PATCH 55/84] Avoid shadowing names

---
 tests/test_persistent_db.py | 16 ++++++++--------
 tests/test_persistent_pg.py | 17 ++++++++---------
 2 files changed, 16 insertions(+), 17 deletions(-)

diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index d8fb504..84d7fd6 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -71,32 +71,32 @@ def test_threads(dbapi):  # noqa: F811
         query_queue.append(Queue(1))
         result_queue.append(Queue(1))
 
-    def run_queries(i):
+    def run_queries(idx):
         this_db = persist.connection()
         db = None
         while True:
             try:
-                q = query_queue[i].get(timeout=1)
+                q = query_queue[idx].get(timeout=1)
             except Empty:
                 q = None
             if not q:
                 break
             db = persist.connection()
             if db != this_db:
-                r = 'error - not persistent'
+                res = 'error - not persistent'
             else:
                 if q == 'ping':
-                    r = 'ok - thread alive'
+                    res = 'ok - thread alive'
                 elif q == 'close':
                     db.close()
-                    r = 'ok - connection closed'
+                    res = 'ok - connection closed'
                 else:
                     cursor = db.cursor()
                     cursor.execute(q)
-                    r = cursor.fetchone()
+                    res = cursor.fetchone()
                     cursor.close()
-            r = f'{i}({db._usage}): {r}'
-            result_queue[i].put(r, timeout=1)
+            res = f'{idx}({db._usage}): {res}'
+            result_queue[idx].put(res, timeout=1)
         if db:
             db.close()
 
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index c8d66d6..03e5acf 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -49,29 +49,29 @@ def test_threads():
         query_queue.append(Queue(1))
         result_queue.append(Queue(1))
 
-    def run_queries(i):
+    def run_queries(idx):
         this_db = persist.connection().db
         db = None
         while True:
             try:
-                q = query_queue[i].get(timeout=1)
+                q = query_queue[idx].get(timeout=1)
             except Empty:
                 q = None
             if not q:
                 break
             db = persist.connection()
             if db.db != this_db:
-                r = 'error - not persistent'
+                res = 'error - not persistent'
             else:
                 if q == 'ping':
-                    r = 'ok - thread alive'
+                    res = 'ok - thread alive'
                 elif q == 'close':
                     db.db.close()
-                    r = 'ok - connection closed'
+                    res = 'ok - connection closed'
                 else:
-                    r = db.query(q)
-            r = f'{i}({db._usage}): {r}'
-            result_queue[i].put(r, timeout=1)
+                    res = db.query(q)
+            res = f'{idx}({db._usage}): {res}'
+            result_queue[idx].put(res, timeout=1)
         if db:
             db.close()
 
@@ -160,4 +160,3 @@ def test_context_manager():
     with persist.connection() as db:
         db.query('select test')
         assert db.num_queries == 1
-

From 24f98dfa03f851a9c0fda7e0d76d3d98799d6a71 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 21:54:04 +0200
Subject: [PATCH 56/84] Simplify setup.py

---
 setup.py | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/setup.py b/setup.py
index e5bb435..43d4ce6 100755
--- a/setup.py
+++ b/setup.py
@@ -2,20 +2,10 @@
 
 """Setup Script for DBUtils"""
 
-import warnings
-
 try:
     from setuptools import setup
 except ImportError:
     from distutils.core import setup
-from sys import version_info
-
-py_version = version_info[:2]
-if not (3, 6) <= py_version < (4, 0):
-    raise ImportError(
-        'Python {}.{} is not supported by DBUtils.'.format(*py_version))
-
-warnings.filterwarnings('ignore', 'Unknown distribution option')
 
 __version__ = '3.1.0b1'
 
@@ -60,6 +50,7 @@
     platforms=['any'],
     license='MIT License',
     packages=['dbutils'],
+    python_requires='>3.7',
     extras_require={
         "pg": ["PyGreSQL>=5"]
     },

From de277e264f7c40106bd3b40221818ee060014461 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 22:09:03 +0200
Subject: [PATCH 57/84] Use Queue nowait methods for better readability

---
 dbutils/pooled_pg.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index e2389c7..b3752df 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -206,7 +206,7 @@ def connection(self):
             if not self._connections.acquire(self._blocking):
                 raise TooManyConnections
         try:
-            con = self._cache.get(0)
+            con = self._cache.get_nowait()
         except Empty:
             con = self.steady_connection()
         return PooledPgConnection(self, con)
@@ -222,7 +222,7 @@ def cache(self, con):
                         con.rollback()  # rollback a possible transaction
                     except Exception:
                         pass
-            self._cache.put(con, 0)  # and then put it back into the cache
+            self._cache.put_nowait(con)  # and then put it back into the cache
         except Full:
             con.close()
         if self._connections:
@@ -232,7 +232,7 @@ def close(self):
         """Close all connections in the pool."""
         while 1:
             try:
-                con = self._cache.get(0)
+                con = self._cache.get_nowait()
                 try:
                     con.close()
                 except Exception:

From 45f40b9772e9266c40891533fb777246c23db305 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 22:13:23 +0200
Subject: [PATCH 58/84] Some grammar fixes, use American spelling

---
 dbutils/pooled_db.py        | 8 ++++----
 dbutils/pooled_pg.py        | 8 ++++----
 dbutils/simple_pooled_db.py | 2 +-
 dbutils/simple_pooled_pg.py | 2 +-
 dbutils/steady_db.py        | 4 ++--
 dbutils/steady_pg.py        | 2 +-
 docs/main.html              | 4 ++--
 docs/main.rst               | 4 ++--
 8 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index fb39ab4..163ad9d 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -97,7 +97,7 @@
 
     db = pool.dedicated_connection()
 
-If you don't need it any more, you should immediately return it to the
+If you don't need it anymore, you should immediately return it to the
 pool with db.close().  You can get another connection in the same way.
 
 Warning: In a threaded environment, never do the following:
@@ -386,7 +386,7 @@ def __del__(self):
         """Delete the pool."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
 
     def _wait_lock(self):
@@ -433,7 +433,7 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
 
     def __enter__(self):
@@ -525,7 +525,7 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
 
     def __enter__(self):
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index b3752df..767a032 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -68,7 +68,7 @@
 proxy class for the hardened SteadyPg version of the connection.
 
 The connection will not be shared with other threads.  If you don't need
-it any more, you should immediately return it to the pool with db.close().
+it anymore, you should immediately return it to the pool with db.close().
 You can get another connection in the same way or with db.reopen().
 
 Warning: In a threaded environment, never do the following:
@@ -246,7 +246,7 @@ def __del__(self):
         """Delete the pool."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
 
 
@@ -267,7 +267,7 @@ def __init__(self, pool, con):
     def close(self):
         """Close the pooled connection."""
         # Instead of actually closing the connection,
-        # return it to the pool so it can be reused.
+        # return it to the pool so that it can be reused.
         if self._con:
             self._pool.cache(self._con)
             self._con = None
@@ -292,7 +292,7 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
 
     def __enter__(self):
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index 606e188..9a37a7b 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -97,7 +97,7 @@ def __init__(self, pool, con):
     def close(self):
         """Close the pooled connection."""
         # Instead of actually closing the connection,
-        # return it to the pool so it can be reused.
+        # return it to the pool so that it can be reused.
         if self._con is not None:
             self._pool.returnConnection(self._con)
             self._con = None
diff --git a/dbutils/simple_pooled_pg.py b/dbutils/simple_pooled_pg.py
index b18ad7b..94dfed7 100644
--- a/dbutils/simple_pooled_pg.py
+++ b/dbutils/simple_pooled_pg.py
@@ -86,7 +86,7 @@ def __init__(self, pool, con):
     def close(self):
         """Close the pooled connection."""
         # Instead of actually closing the connection,
-        # return it to the pool so it can be reused.
+        # return it to the pool so that it can be reused.
         if self._con is not None:
             self._pool.cache(self._con)
             self._con = None
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 15ddd6d..667611e 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -517,7 +517,7 @@ def __del__(self):
         """Delete the steady connection."""
         try:
             self._close()  # make sure the connection is closed
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
 
 
@@ -708,5 +708,5 @@ def __del__(self):
         """Delete the steady cursor."""
         try:
             self.close()  # make sure the cursor is closed
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index a553078..6ccefe1 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -305,5 +305,5 @@ def __del__(self):
         """Delete the steady connection."""
         try:
             self._close()  # make sure the connection is closed
-        except:  # builtin Exceptions might not exist any more
+        except:  # builtin Exceptions might not exist anymore
             pass
diff --git a/docs/main.html b/docs/main.html
index c77a83b..50c9ffd 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -213,7 +213,7 @@ <h3>PooledDB (pooled_db)</h3>
 at least <span class="docutils literal">mincached</span> and at the most <span class="docutils literal">maxcached</span> idle connections that
 will be used whenever a thread is requesting a dedicated database connection
 or the pool of shared connections is not yet full. When a thread closes a
-connection that is not shared any more, it is returned back to the pool of
+connection that is not shared anymore, it is returned back to the pool of
 idle connections so that it can be recycled again.</p>
 <p>If the underlying DB-API module is not thread-safe, thread locks will be
 used to ensure that the <span class="docutils literal">pooled_db</span> connections are thread-safe. So you
@@ -372,7 +372,7 @@ <h3>PooledDB (pooled_db)</h3>
 <pre class="literal-block">db = pool.connection(shareable=False)</pre>
 <p>Instead of this, you can also get a dedicated connection as follows:</p>
 <pre class="literal-block">db = pool.dedicated_connection()</pre>
-<p>If you don't need it any more, you should immediately return it to the
+<p>If you don't need it anymore, you should immediately return it to the
 pool with <span class="docutils literal">db.close()</span>. You can get another connection in the same way.</p>
 <p><em>Warning:</em> In a threaded environment, never do the following:</p>
 <pre class="literal-block">pool.connection().cursor().execute(...)</pre>
diff --git a/docs/main.rst b/docs/main.rst
index 4793d30..fa574b7 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -189,7 +189,7 @@ Besides the pool of shared connections, you can also set up a pool of
 at least ``mincached`` and at the most ``maxcached`` idle connections that
 will be used whenever a thread is requesting a dedicated database connection
 or the pool of shared connections is not yet full. When a thread closes a
-connection that is not shared any more, it is returned back to the pool of
+connection that is not shared anymore, it is returned back to the pool of
 idle connections so that it can be recycled again.
 
 If the underlying DB-API module is not thread-safe, thread locks will be
@@ -389,7 +389,7 @@ Instead of this, you can also get a dedicated connection as follows::
 
   db = pool.dedicated_connection()
 
-If you don't need it any more, you should immediately return it to the
+If you don't need it anymore, you should immediately return it to the
 pool with ``db.close()``. You can get another connection in the same way.
 
 *Warning:* In a threaded environment, never do the following::

From 21a9b33aafe413f6e97e027db6d12684c66f218f Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 22:20:20 +0200
Subject: [PATCH 59/84] Simplify control flow in mock_pg

---
 pyproject.toml   |  1 -
 tests/mock_pg.py | 19 ++++++++-----------
 2 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index ce67283..d523c13 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -77,4 +77,3 @@ max-statements = 95
 [tool.ruff.per-file-ignores]
 "docs/make.py" = ["INP001"]
 "tests/*" = ["B023", "I001", "N8", "PGH004", "PLR0915", "PT009", "S101"]
-"tests/mock_pg.py" = ["RET505"]
diff --git a/tests/mock_pg.py b/tests/mock_pg.py
index bbb393c..380215e 100644
--- a/tests/mock_pg.py
+++ b/tests/mock_pg.py
@@ -61,14 +61,13 @@ def query(self, qstr):
         if qstr in ('begin', 'end', 'commit', 'rollback'):
             self.session.append(qstr)
             return None
-        elif qstr.startswith('select '):
+        if qstr.startswith('select '):
             self.num_queries += 1
             return qstr[7:]
-        elif qstr.startswith('set '):
+        if qstr.startswith('set '):
             self.session.append(qstr[4:])
             return None
-        else:
-            raise ProgrammingError
+        raise ProgrammingError
 
 
 class DB:
@@ -80,17 +79,15 @@ def __init__(self, *args, **kw):
         self.__args = args, kw
 
     def __getattr__(self, name):
-        if self.db:
-            return getattr(self.db, name)
-        else:
+        if not self.db:
             raise AttributeError
+        return getattr(self.db, name)
 
     def close(self):
-        if self.db:
-            self.db.close()
-            self.db = None
-        else:
+        if not self.db:
             raise InternalError
+        self.db.close()
+        self.db = None
 
     def reopen(self):
         if self.db:

From c60205d91cffe4d3bae191dbe47101acf8189771 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 22:26:50 +0200
Subject: [PATCH 60/84] Fix import sorting in tests

---
 pyproject.toml                 |  5 +++--
 tests/mock_pg.py               |  1 -
 tests/test_persistent_db.py    |  6 +++---
 tests/test_persistent_pg.py    |  2 +-
 tests/test_pooled_db.py        | 14 +++++++++-----
 tests/test_pooled_pg.py        |  4 ++--
 tests/test_simple_pooled_db.py |  6 +++---
 tests/test_simple_pooled_pg.py |  2 +-
 tests/test_steady_db.py        |  6 +++---
 9 files changed, 25 insertions(+), 21 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index d523c13..ac2345b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,7 +48,7 @@ select = [
   # "T20",  # flake8-print
   # "TRY",  # tryceratops
 ]
-# When removing rules from the `ignore` list, do `ruff rule ARG002` to see the docs
+# You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
   "ARG002",
   "B007",
@@ -75,5 +75,6 @@ max-branches = 41
 max-statements = 95
 
 [tool.ruff.per-file-ignores]
+# You can use `ruff rule ...` to see what these ignored rules check
 "docs/make.py" = ["INP001"]
-"tests/*" = ["B023", "I001", "N8", "PGH004", "PLR0915", "PT009", "S101"]
+"tests/*" = ["N8", "PGH004", "PLR0915", "PT009", "S101"]
diff --git a/tests/mock_pg.py b/tests/mock_pg.py
index 380215e..b341bea 100644
--- a/tests/mock_pg.py
+++ b/tests/mock_pg.py
@@ -2,7 +2,6 @@
 
 import sys
 
-
 sys.modules['pg'] = sys.modules[__name__]
 
 
diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index 84d7fd6..2f11b7c 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -10,15 +10,15 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-from queue import Queue, Empty
+from queue import Empty, Queue
 from threading import Thread
 
 import pytest
 
-from .mock_db import dbapi  # noqa: F401
-
 from dbutils.persistent_db import NotSupportedError, PersistentDB, local
 
+from .mock_db import dbapi  # noqa: F401
+
 
 def test_version():
     from dbutils import __version__, persistent_db
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index 03e5acf..de866c3 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -10,7 +10,7 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-from queue import Queue, Empty
+from queue import Empty, Queue
 from threading import Thread
 
 import pg
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 8bbd866..453c6e2 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -10,18 +10,22 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-from queue import Queue, Empty
+from queue import Empty, Queue
 from threading import Thread
 
 import pytest
 
-from .mock_db import dbapi  # noqa: F401
-
 from dbutils.pooled_db import (
-    NotSupportedError, PooledDB, SharedDBConnection,
-    InvalidConnection, TooManyConnections)
+    InvalidConnection,
+    NotSupportedError,
+    PooledDB,
+    SharedDBConnection,
+    TooManyConnections,
+)
 from dbutils.steady_db import SteadyDBConnection
 
+from .mock_db import dbapi  # noqa: F401
+
 
 def test_version():
     from dbutils import __version__, pooled_db
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index 93c266c..8b25259 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -10,13 +10,13 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-from queue import Queue, Empty
+from queue import Empty, Queue
 from threading import Thread
 
 import pg  # noqa: F401
 import pytest
 
-from dbutils.pooled_pg import PooledPg, InvalidConnection, TooManyConnections
+from dbutils.pooled_pg import InvalidConnection, PooledPg, TooManyConnections
 from dbutils.steady_pg import SteadyPgConnection
 
 
diff --git a/tests/test_simple_pooled_db.py b/tests/test_simple_pooled_db.py
index f112d64..b0bff30 100644
--- a/tests/test_simple_pooled_db.py
+++ b/tests/test_simple_pooled_db.py
@@ -11,15 +11,15 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-from queue import Queue, Empty
+from queue import Empty, Queue
 from threading import Thread
 
 import pytest
 
-from . import mock_db as dbapi
-
 from dbutils import simple_pooled_db
 
+from . import mock_db as dbapi
+
 
 def my_db_pool(threadsafety, max_connections):
     """Get simple PooledDB connection."""
diff --git a/tests/test_simple_pooled_pg.py b/tests/test_simple_pooled_pg.py
index 1492573..c0856cc 100644
--- a/tests/test_simple_pooled_pg.py
+++ b/tests/test_simple_pooled_pg.py
@@ -10,7 +10,7 @@
 * This test was contributed by Christoph Zwerschke
 """
 
-from queue import Queue, Empty
+from queue import Empty, Queue
 from threading import Thread
 
 import pg  # noqa: F401
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index c734928..05e7f2e 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -11,10 +11,10 @@
 
 import pytest
 
-from . import mock_db as dbapi
+from dbutils.steady_db import SteadyDBConnection, SteadyDBCursor
+from dbutils.steady_db import connect as SteadyDBconnect
 
-from dbutils.steady_db import (
-    connect as SteadyDBconnect, SteadyDBConnection, SteadyDBCursor)
+from . import mock_db as dbapi
 
 
 def test_version():

From d547aef1ea6c3d1f5f277eb4838c45578f591ac0 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 22:39:13 +0200
Subject: [PATCH 61/84] Remove some ignored rules

---
 docs/make.py            |  2 +-
 pyproject.toml          |  9 +++--
 tests/test_pooled_db.py |  4 +--
 tests/test_steady_db.py | 76 ++++++++++++++++++++---------------------
 4 files changed, 47 insertions(+), 44 deletions(-)

diff --git a/docs/make.py b/docs/make.py
index f6736b7..ef88183 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3.9
+#!/usr/bin/python3.10
 
 """Build HTML from reST files."""
 
diff --git a/pyproject.toml b/pyproject.toml
index ac2345b..f3aa784 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -75,6 +75,9 @@ max-branches = 41
 max-statements = 95
 
 [tool.ruff.per-file-ignores]
-# You can use `ruff rule ...` to see what these ignored rules check
-"docs/make.py" = ["INP001"]
-"tests/*" = ["N8", "PGH004", "PLR0915", "PT009", "S101"]
+"docs/*" = [
+  "INP001"  # allow stand-alone scripts
+]
+"tests/*" = [
+  "S101"  # allow assert statements
+]
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 453c6e2..49f8ac4 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -54,7 +54,7 @@ def test_threadsafety(dbapi, threadsafety):  # noqa: F811
 
 
 @pytest.mark.parametrize("threadsafety", [1, 2])
-def test_create_connection(dbapi, threadsafety):  # noqa: F811
+def test_create_connection(dbapi, threadsafety):  # noqa: F811, PLR0915
     dbapi.threadsafety = threadsafety
     shareable = threadsafety > 1
     pool = PooledDB(
@@ -675,7 +675,7 @@ def test_rollback(dbapi, threadsafety):  # noqa: F811
 
 
 @pytest.mark.parametrize("threadsafety", [1, 2])
-def test_maxconnections(dbapi, threadsafety):  # noqa: F811
+def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     dbapi.threadsafety = threadsafety
     shareable = threadsafety > 1
     pool = PooledDB(dbapi, 1, 2, 2, 3)
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index 05e7f2e..dca75af 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -12,7 +12,7 @@
 import pytest
 
 from dbutils.steady_db import SteadyDBConnection, SteadyDBCursor
-from dbutils.steady_db import connect as SteadyDBconnect
+from dbutils.steady_db import connect as steady_db_connect
 
 from . import mock_db as dbapi
 
@@ -107,13 +107,13 @@ def test_broken_connection():
         SteadyDBConnection(None)
     with pytest.raises(TypeError):
         SteadyDBCursor(None)
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     for i in range(3):
         db.close()
     del db
     with pytest.raises(dbapi.OperationalError):
-        SteadyDBconnect(dbapi, database='error')
-    db = SteadyDBconnect(dbapi, database='ok')
+        steady_db_connect(dbapi, database='error')
+    db = steady_db_connect(dbapi, database='ok')
     cursor = db.cursor()
     for i in range(3):
         cursor.close()
@@ -126,7 +126,7 @@ def test_broken_connection():
 
 @pytest.mark.parametrize("closeable", [False, True])
 def test_close(closeable):
-    db = SteadyDBconnect(dbapi, closeable=closeable)
+    db = steady_db_connect(dbapi, closeable=closeable)
     assert db._con.valid
     db.close()
     assert closeable ^ db._con.valid
@@ -138,8 +138,8 @@ def test_close(closeable):
     assert not db._con.valid
 
 
-def test_connection():
-    db = SteadyDBconnect(
+def test_connection():  # noqa: PLR0915
+    db = steady_db_connect(
         dbapi, 0, None, None, None, True,
         'SteadyDBTestDB', user='SteadyDBTestUser')
     assert isinstance(db, SteadyDBConnection)
@@ -259,7 +259,7 @@ def test_connection():
 
 
 def test_connection_context_handler():
-    db = SteadyDBconnect(
+    db = steady_db_connect(
         dbapi, 0, None, None, None, True,
         'SteadyDBTestDB', user='SteadyDBTestUser')
     assert db._con.session == []
@@ -278,7 +278,7 @@ def test_connection_context_handler():
 
 
 def test_cursor_context_handler():
-    db = SteadyDBconnect(
+    db = steady_db_connect(
         dbapi, 0, None, None, None, True,
         'SteadyDBTestDB', user='SteadyDBTestUser')
     assert db._con.open_cursors == 0
@@ -290,7 +290,7 @@ def test_cursor_context_handler():
 
 
 def test_cursor_as_iterator_provided():
-    db = SteadyDBconnect(
+    db = steady_db_connect(
         dbapi, 0, None, None, None, True,
         'SteadyDBTestDB', user='SteadyDBTestUser')
     assert db._con.open_cursors == 0
@@ -309,7 +309,7 @@ def test_cursor_as_iterator_provided():
 
 
 def test_cursor_as_iterator_created():
-    db = SteadyDBconnect(
+    db = steady_db_connect(
         dbapi, 0, None, None, None, True,
         'SteadyDBTestDB', user='SteadyDBTestUser')
     assert db._con.open_cursors == 0
@@ -322,10 +322,10 @@ def test_cursor_as_iterator_created():
 
 
 def test_connection_creator_function():
-    db1 = SteadyDBconnect(
+    db1 = steady_db_connect(
         dbapi, 0, None, None, None, True,
         'SteadyDBTestDB', user='SteadyDBTestUser')
-    db2 = SteadyDBconnect(
+    db2 = steady_db_connect(
         dbapi.connect, 0, None, None, None, True,
         'SteadyDBTestDB', user='SteadyDBTestUser')
     assert db1.dbapi() == db2.dbapi()
@@ -338,7 +338,7 @@ def test_connection_creator_function():
 
 
 def test_connection_maxusage():
-    db = SteadyDBconnect(dbapi, 10)
+    db = steady_db_connect(dbapi, 10)
     cursor = db.cursor()
     for i in range(100):
         cursor.execute(f'select test{i}')
@@ -389,7 +389,7 @@ def test_connection_maxusage():
 
 
 def test_connection_setsession():
-    db = SteadyDBconnect(dbapi, 3, ('set time zone', 'set datestyle'))
+    db = steady_db_connect(dbapi, 3, ('set time zone', 'set datestyle'))
     assert hasattr(db, '_usage')
     assert db._usage == 0
     assert hasattr(db._con, 'open_cursors')
@@ -448,29 +448,29 @@ def test_connection_setsession():
 
 
 def test_connection_failures():
-    db = SteadyDBconnect(dbapi)
+    db = steady_db_connect(dbapi)
     db.close()
     db.cursor()
-    db = SteadyDBconnect(dbapi, failures=dbapi.InternalError)
+    db = steady_db_connect(dbapi, failures=dbapi.InternalError)
     db.close()
     db.cursor()
-    db = SteadyDBconnect(dbapi, failures=dbapi.OperationalError)
+    db = steady_db_connect(dbapi, failures=dbapi.OperationalError)
     db.close()
     with pytest.raises(dbapi.InternalError):
         db.cursor()
-    db = SteadyDBconnect(dbapi, failures=(
+    db = steady_db_connect(dbapi, failures=(
         dbapi.OperationalError, dbapi.InterfaceError))
     db.close()
     with pytest.raises(dbapi.InternalError):
         db.cursor()
-    db = SteadyDBconnect(dbapi, failures=(
+    db = steady_db_connect(dbapi, failures=(
         dbapi.OperationalError, dbapi.InterfaceError, dbapi.InternalError))
     db.close()
     db.cursor()
 
 
 def test_connection_failure_error():
-    db = SteadyDBconnect(dbapi)
+    db = steady_db_connect(dbapi)
     cursor = db.cursor()
     db.close()
     cursor.execute('select test')
@@ -481,7 +481,7 @@ def test_connection_failure_error():
 
 
 def test_connection_set_sizes():
-    db = SteadyDBconnect(dbapi)
+    db = steady_db_connect(dbapi)
     cursor = db.cursor()
     cursor.execute('get sizes')
     result = cursor.fetchone()
@@ -510,7 +510,7 @@ def test_connection_ping_check():
     con_cls = dbapi.Connection
     con_cls.has_ping = False
     con_cls.num_pings = 0
-    db = SteadyDBconnect(dbapi)
+    db = steady_db_connect(dbapi)
     db.cursor().execute('select test')
     assert con_cls.num_pings == 0
     db.close()
@@ -518,7 +518,7 @@ def test_connection_ping_check():
     assert con_cls.num_pings == 0
     assert db._ping_check() is None
     assert con_cls.num_pings == 1
-    db = SteadyDBconnect(dbapi, ping=7)
+    db = steady_db_connect(dbapi, ping=7)
     db.cursor().execute('select test')
     assert con_cls.num_pings == 2
     db.close()
@@ -527,7 +527,7 @@ def test_connection_ping_check():
     assert db._ping_check() is None
     assert con_cls.num_pings == 2
     con_cls.has_ping = True
-    db = SteadyDBconnect(dbapi)
+    db = steady_db_connect(dbapi)
     db.cursor().execute('select test')
     assert con_cls.num_pings == 2
     db.close()
@@ -535,7 +535,7 @@ def test_connection_ping_check():
     assert con_cls.num_pings == 2
     assert db._ping_check()
     assert con_cls.num_pings == 3
-    db = SteadyDBconnect(dbapi, ping=1)
+    db = steady_db_connect(dbapi, ping=1)
     db.cursor().execute('select test')
     assert con_cls.num_pings == 3
     db.close()
@@ -546,13 +546,13 @@ def test_connection_ping_check():
     db.close()
     assert db._ping_check()
     assert con_cls.num_pings == 5
-    db = SteadyDBconnect(dbapi, ping=7)
+    db = steady_db_connect(dbapi, ping=7)
     db.cursor().execute('select test')
     assert con_cls.num_pings == 7
     db.close()
     db.cursor().execute('select test')
     assert con_cls.num_pings == 9
-    db = SteadyDBconnect(dbapi, ping=3)
+    db = steady_db_connect(dbapi, ping=3)
     assert con_cls.num_pings == 9
     db.cursor()
     assert con_cls.num_pings == 10
@@ -561,7 +561,7 @@ def test_connection_ping_check():
     assert con_cls.num_pings == 11
     cursor.execute('select test')
     assert con_cls.num_pings == 11
-    db = SteadyDBconnect(dbapi, ping=5)
+    db = steady_db_connect(dbapi, ping=5)
     assert con_cls.num_pings == 11
     db.cursor()
     assert con_cls.num_pings == 11
@@ -575,7 +575,7 @@ def test_connection_ping_check():
     assert con_cls.num_pings == 12
     cursor.execute('select test')
     assert con_cls.num_pings == 13
-    db = SteadyDBconnect(dbapi, ping=7)
+    db = steady_db_connect(dbapi, ping=7)
     assert con_cls.num_pings == 13
     db.cursor()
     assert con_cls.num_pings == 14
@@ -597,7 +597,7 @@ def test_connection_ping_check():
 
 
 def test_begin_transaction():
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     cursor = db.cursor()
     cursor.close()
     cursor.execute('select test12')
@@ -624,7 +624,7 @@ def test_begin_transaction():
 
 
 def test_with_begin_extension():
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     db._con._begin_called_with = None
 
     def begin(a, b=None, c=7):
@@ -639,7 +639,7 @@ def begin(a, b=None, c=7):
 
 
 def test_cancel_transaction():
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     cursor = db.cursor()
     db.begin()
     cursor.execute('select test14')
@@ -650,7 +650,7 @@ def test_cancel_transaction():
 
 
 def test_with_cancel_extension():
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     db._con._cancel_called = None
 
     def cancel():
@@ -666,12 +666,12 @@ def cancel():
 
 
 def test_reset_transaction():
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     db.begin()
     assert not db._con.session
     db.close()
     assert not db._con.session
-    db = SteadyDBconnect(dbapi, database='ok', closeable=False)
+    db = steady_db_connect(dbapi, database='ok', closeable=False)
     db.begin()
     assert not db._con.session
     db.close()
@@ -679,7 +679,7 @@ def test_reset_transaction():
 
 
 def test_commit_error():
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     db.begin()
     assert not db._con.session
     assert db._con.valid
@@ -703,7 +703,7 @@ def test_commit_error():
 
 
 def test_rollback_error():
-    db = SteadyDBconnect(dbapi, database='ok')
+    db = steady_db_connect(dbapi, database='ok')
     db.begin()
     assert not db._con.session
     assert db._con.valid

From 464afdc3f7eb53d30cadb20383ee3fd515a4d43b Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 22:56:48 +0200
Subject: [PATCH 62/84] Check that no magic values are used

---
 dbutils/pooled_pg.py |  8 ++++++--
 pyproject.toml       | 14 +++++++-------
 2 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index 767a032..4b3a251 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -117,6 +117,10 @@
 from . import __version__
 from .steady_pg import SteadyPgConnection
 
+# constants for "reset" parameter
+RESET_ALWAYS_ROLLBACK = 1
+RESET_COMPLETELY = 2
+
 
 class PooledPgError(Exception):
     """General PooledPg error."""
@@ -214,10 +218,10 @@ def connection(self):
     def cache(self, con):
         """Put a connection back into the pool cache."""
         try:
-            if self._reset == 2:
+            if self._reset == RESET_COMPLETELY:
                 con.reset()  # reset the connection completely
             else:
-                if self._reset or con._transaction:
+                if self._reset == RESET_ALWAYS_ROLLBACK or con._transaction:
                     try:
                         con.rollback()  # rollback a possible transaction
                     except Exception:
diff --git a/pyproject.toml b/pyproject.toml
index f3aa784..dedf4ba 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -66,18 +66,18 @@ line-length = 79
 target-version = "py37"
 
 [tool.ruff.mccabe]
-max-complexity = 31
+max-complexity = 32
 
 [tool.ruff.pylint]
-allow-magic-value-types = ["int", "str"]
-max-args = 12
-max-branches = 41
-max-statements = 95
+max-args = 15
+max-branches = 50
+max-statements = 100
 
 [tool.ruff.per-file-ignores]
 "docs/*" = [
-  "INP001"  # allow stand-alone scripts
+  "INP001",  # allow stand-alone scripts
 ]
 "tests/*" = [
-  "S101"  # allow assert statements
+  "PLR2004",  # allow magic values
+  "S101",  # allow assert statements
 ]

From 23d54b8a992489db482713d1e714ec80d422af60 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 22:59:51 +0200
Subject: [PATCH 63/84] Enforce check for unused arguments

---
 dbutils/persistent_db.py | 2 +-
 pyproject.toml           | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/dbutils/persistent_db.py b/dbutils/persistent_db.py
index 9082488..54ddfcc 100644
--- a/dbutils/persistent_db.py
+++ b/dbutils/persistent_db.py
@@ -201,7 +201,7 @@ def steady_connection(self):
             self._failures, self._ping, self._closeable,
             *self._args, **self._kwargs)
 
-    def connection(self, shareable=False):
+    def connection(self, shareable=False):  # noqa: ARG002
         """Get a steady, persistent DB-API 2 connection.
 
         The shareable parameter exists only for compatibility with the
diff --git a/pyproject.toml b/pyproject.toml
index dedf4ba..434bb27 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,7 +50,6 @@ select = [
 ]
 # You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
-  "ARG002",
   "B007",
   "B904",
   "E722",

From d67f5abf95cd4c0dae5f31ec6fdfa329371d4718 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 23:08:53 +0200
Subject: [PATCH 64/84] Check that variables are used inside loops

---
 dbutils/simple_pooled_db.py    |  2 +-
 dbutils/simple_pooled_pg.py    |  2 +-
 pyproject.toml                 |  1 -
 tests/test_persistent_db.py    |  4 +-
 tests/test_persistent_pg.py    |  4 +-
 tests/test_pooled_db.py        | 81 +++++++++++++++++-----------------
 tests/test_pooled_pg.py        | 24 +++++-----
 tests/test_simple_pooled_db.py |  2 +-
 tests/test_simple_pooled_pg.py |  6 +--
 tests/test_steady_db.py        | 16 +++----
 tests/test_steady_pg.py        |  4 +-
 11 files changed, 73 insertions(+), 73 deletions(-)

diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index 9a37a7b..d95f5d2 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -159,7 +159,7 @@ def __init__(self, dbapi, maxconnections, *args, **kwargs):
                 "Database module threading support cannot be determined.")
         # Establish all database connections (it would be better to
         # only establish a part of them now, and the rest on demand).
-        for i in range(maxconnections):
+        for _i in range(maxconnections):
             self.addConnection(dbapi.connect(*args, **kwargs))
 
     # The following functions are used with DB-API 2 modules
diff --git a/dbutils/simple_pooled_pg.py b/dbutils/simple_pooled_pg.py
index 94dfed7..fe90d57 100644
--- a/dbutils/simple_pooled_pg.py
+++ b/dbutils/simple_pooled_pg.py
@@ -122,7 +122,7 @@ def __init__(self, maxconnections, *args, **kwargs):
         self._queue = Queue(maxconnections)
         # Establish all database connections (it would be better to
         # only establish a part of them now, and the rest on demand).
-        for i in range(maxconnections):
+        for _i in range(maxconnections):
             self.cache(PgConnection(*args, **kwargs))
 
     def cache(self, con):
diff --git a/pyproject.toml b/pyproject.toml
index 434bb27..e9a5f0a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,7 +50,6 @@ select = [
 ]
 # You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
-  "B007",
   "B904",
   "E722",
   "N811",
diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index 2f11b7c..cd35127 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -67,7 +67,7 @@ def test_threads(dbapi):  # noqa: F811
     num_threads = 3
     persist = PersistentDB(dbapi, closeable=True)
     query_queue, result_queue = [], []
-    for i in range(num_threads):
+    for _i in range(num_threads):
         query_queue.append(Queue(1))
         result_queue.append(Queue(1))
 
@@ -164,7 +164,7 @@ def test_setsession(dbapi):  # noqa: F811
     cursor.execute('set test')
     cursor.fetchone()
     cursor.close()
-    for i in range(3):
+    for _i in range(3):
         assert db._con.session == ['datestyle', 'test']
         cursor = db.cursor()
         cursor.execute('select test')
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index de866c3..6e1a258 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -45,7 +45,7 @@ def test_threads():
     num_threads = 3
     persist = PersistentPg()
     query_queue, result_queue = [], []
-    for i in range(num_threads):
+    for _i in range(num_threads):
         query_queue.append(Queue(1))
         result_queue.append(Queue(1))
 
@@ -132,7 +132,7 @@ def test_setsession():
     assert db._setsession_sql == ('set datestyle',)
     assert db.db.session == ['datestyle']
     db.query('set test')
-    for i in range(3):
+    for _i in range(3):
         assert db.db.session == ['datestyle', 'test']
         db.query('select test')
     assert db.db.session == ['datestyle']
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 49f8ac4..1b16322 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -275,7 +275,7 @@ def close(what=closed):
     if shareable:
         assert len(pool._shared_cache) == 0
     cache = []
-    for i in range(5):
+    for _i in range(5):
         cache.append(pool.connection())
     assert len(pool._idle_cache) == 5
     if shareable:
@@ -297,7 +297,7 @@ def close_shared(what=closed):
 
     if shareable:
         cache = []
-        for i in range(5):
+        for _i in range(5):
             cache.append(pool.connection())
         pool._shared_cache[3].con.close = close_shared
     else:
@@ -407,63 +407,64 @@ def test_min_max_cached(dbapi, threadsafety):  # noqa: F811
     shareable = threadsafety > 1
     pool = PooledDB(dbapi, 3)
     assert len(pool._idle_cache) == 3
-    cache = [pool.connection() for i in range(3)]
+    cache = [pool.connection() for _i in range(3)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 3
-    cache = [pool.connection() for i in range(6)]
+    cache = [pool.connection() for _i in range(6)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 6
     pool = PooledDB(dbapi, 0, 3)
     assert len(pool._idle_cache) == 0
-    cache = [pool.connection() for i in range(3)]
+    cache = [pool.connection() for _i in range(3)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 3
-    cache = [pool.connection() for i in range(6)]
+    cache = [pool.connection() for _i in range(6)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 3
     pool = PooledDB(dbapi, 3, 3)
     assert len(pool._idle_cache) == 3
-    cache = [pool.connection() for i in range(3)]
+    cache = [pool.connection() for _i in range(3)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 3
-    cache = [pool.connection() for i in range(6)]
+    cache = [pool.connection() for _i in range(6)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 3
     pool = PooledDB(dbapi, 3, 2)
     assert len(pool._idle_cache) == 3
-    cache = [pool.connection() for i in range(4)]
+    cache = [pool.connection() for _i in range(4)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 3
     pool = PooledDB(dbapi, 2, 5)
     assert len(pool._idle_cache) == 2
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert len(pool._idle_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 5
     pool = PooledDB(dbapi, 1, 2, 3)
     assert len(pool._idle_cache) == 1
-    cache = [pool.connection(False) for i in range(4)]
+    cache = [pool.connection(False) for _i in range(4)]
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 0
+    assert cache
     del cache
     assert len(pool._idle_cache) == 2
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 3
@@ -474,14 +475,14 @@ def test_min_max_cached(dbapi, threadsafety):  # noqa: F811
         assert len(pool._shared_cache) == 0
     pool = PooledDB(dbapi, 1, 3, 2)
     assert len(pool._idle_cache) == 1
-    cache = [pool.connection(False) for i in range(4)]
+    cache = [pool.connection(False) for _i in range(4)]
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 0
     assert cache
     del cache
     assert len(pool._idle_cache) == 3
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     if shareable:
         assert len(pool._idle_cache) == 1
         assert len(pool._shared_cache) == 2
@@ -500,34 +501,34 @@ def test_max_shared(dbapi, threadsafety):  # noqa: F811
     shareable = threadsafety > 1
     pool = PooledDB(dbapi)
     assert len(pool._idle_cache) == 0
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert len(cache) == 10
     assert len(pool._idle_cache) == 0
     pool = PooledDB(dbapi, 1, 1, 0)
     assert len(pool._idle_cache) == 1
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert len(cache) == 10
     assert len(pool._idle_cache) == 0
     pool = PooledDB(dbapi, 0, 0, 1)
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert len(cache) == 10
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 1
     pool = PooledDB(dbapi, 1, 1, 1)
     assert len(pool._idle_cache) == 1
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert len(cache) == 10
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 1
     pool = PooledDB(dbapi, 0, 0, 7)
-    cache = [pool.connection(False) for i in range(3)]
+    cache = [pool.connection(False) for _i in range(3)]
     assert len(cache) == 3
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 0
-    cache = [pool.connection() for i in range(10)]
+    cache = [pool.connection() for _i in range(10)]
     assert len(cache) == 10
     assert len(pool._idle_cache) == 3
     if shareable:
@@ -537,7 +538,7 @@ def test_max_shared(dbapi, threadsafety):  # noqa: F811
 def test_sort_shared(dbapi):  # noqa: F811
     pool = PooledDB(dbapi, 0, 4, 4)
     cache = []
-    for i in range(6):
+    for _i in range(6):
         db = pool.connection()
         db.cursor().execute('select test')
         cache.append(db)
@@ -559,7 +560,7 @@ def test_equally_shared(dbapi, threadsafety):  # noqa: F811
     shareable = threadsafety > 1
     pool = PooledDB(dbapi, 5, 5, 5)
     assert len(pool._idle_cache) == 5
-    for i in range(15):
+    for _i in range(15):
         db = pool.connection(False)
         db.cursor().execute('select test')
         db.close()
@@ -569,7 +570,7 @@ def test_equally_shared(dbapi, threadsafety):  # noqa: F811
         assert con._usage == 3
         assert con._con.num_queries == 3
     cache = []
-    for i in range(35):
+    for _i in range(35):
         db = pool.connection()
         db.cursor().execute('select test')
         cache.append(db)
@@ -595,7 +596,7 @@ def test_many_shared(dbapi, threadsafety):  # noqa: F811
     shareable = threadsafety > 1
     pool = PooledDB(dbapi, 0, 0, 5)
     cache = []
-    for i in range(35):
+    for _i in range(35):
         db = pool.connection()
         db.cursor().execute('select test1')
         db.cursor().execute('select test2')
@@ -622,7 +623,7 @@ def test_many_shared(dbapi, threadsafety):  # noqa: F811
         for db in cache:
             if db:
                 db.cursor().callproc('test4')
-        for i in range(6):
+        for _i in range(6):
             db = pool.connection()
             db.cursor().callproc('test4')
             cache.append(db)
@@ -685,7 +686,7 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert pool._connections == 0
     assert len(pool._idle_cache) == 1
     cache = []
-    for i in range(3):
+    for _i in range(3):
         cache.append(pool.connection(False))
     assert pool._connections == 3
     assert len(pool._idle_cache) == 0
@@ -700,7 +701,7 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert len(pool._idle_cache) == 2
     if shareable:
         assert len(pool._shared_cache) == 0
-    for i in range(3):
+    for _i in range(3):
         cache.append(pool.connection())
     assert len(pool._idle_cache) == 0
     if shareable:
@@ -794,7 +795,7 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert pool._connections == 0
     assert len(pool._idle_cache) == 4
     cache = []
-    for i in range(4):
+    for _i in range(4):
         cache.append(pool.connection(False))
     assert pool._connections == 4
     assert len(pool._idle_cache) == 0
@@ -806,7 +807,7 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert pool._maxconnections == 4
     assert pool._connections == 0
     assert len(pool._idle_cache) == 1
-    for i in range(4):
+    for _i in range(4):
         cache.append(pool.connection())
     assert len(pool._idle_cache) == 0
     if shareable:
@@ -826,7 +827,7 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert pool._maxconnections == 3
     assert pool._connections == 0
     cache = []
-    for i in range(3):
+    for _i in range(3):
         cache.append(pool.connection(False))
     assert pool._connections == 3
     with pytest.raises(TooManyConnections):
@@ -835,11 +836,11 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
         pool.connection(True)
     cache = []
     assert pool._connections == 0
-    for i in range(3):
+    for _i in range(3):
         cache.append(pool.connection())
     assert pool._connections == 3
     if shareable:
-        for i in range(3):
+        for _i in range(3):
             cache.append(pool.connection())
         assert pool._connections == 3
     else:
@@ -851,7 +852,7 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert pool._maxconnections == 0
     assert pool._connections == 0
     cache = []
-    for i in range(10):
+    for _i in range(10):
         cache.append(pool.connection(False))
         cache.append(pool.connection())
     if shareable:
@@ -967,12 +968,12 @@ def test_one_thread_two_connections(dbapi, threadsafety):  # noqa: F811
     shareable = threadsafety > 1
     pool = PooledDB(dbapi, 2)
     db1 = pool.connection()
-    for i in range(5):
+    for _i in range(5):
         db1.cursor().execute('select test')
     db2 = pool.connection()
     assert db1 != db2
     assert db1._con != db2._con
-    for i in range(7):
+    for _i in range(7):
         db2.cursor().execute('select test')
     assert db1._con._con.num_queries == 5
     assert db2._con._con.num_queries == 7
@@ -980,19 +981,19 @@ def test_one_thread_two_connections(dbapi, threadsafety):  # noqa: F811
     db1 = pool.connection()
     assert db1 != db2
     assert db1._con != db2._con
-    for i in range(3):
+    for _i in range(3):
         db1.cursor().execute('select test')
     assert db1._con._con.num_queries == 8
     db2.cursor().execute('select test')
     assert db2._con._con.num_queries == 8
     pool = PooledDB(dbapi, 0, 0, 2)
     db1 = pool.connection()
-    for i in range(5):
+    for _i in range(5):
         db1.cursor().execute('select test')
     db2 = pool.connection()
     assert db1 != db2
     assert db1._con != db2._con
-    for i in range(7):
+    for _i in range(7):
         db2.cursor().execute('select test')
     assert db1._con._con.num_queries == 5
     assert db2._con._con.num_queries == 7
@@ -1000,7 +1001,7 @@ def test_one_thread_two_connections(dbapi, threadsafety):  # noqa: F811
     db1 = pool.connection()
     assert db1 != db2
     assert db1._con != db2._con
-    for i in range(3):
+    for _i in range(3):
         db1.cursor().execute('select test')
     assert db1._con._con.num_queries == 8
     db2.cursor().execute('select test')
@@ -1028,7 +1029,7 @@ def test_three_threads_two_connections(dbapi, threadsafety):  # noqa: F811
     def connection():
         queue.put(pool.connection(), timeout=1)
 
-    for i in range(3):
+    for _i in range(3):
         Thread(target=connection).start()
     db1 = queue.get(timeout=1)
     db2 = queue.get(timeout=1)
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index 8b25259..cc57277 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -111,13 +111,13 @@ def test_min_max_cached():
     assert pool._cache.qsize() == 3
     cache = [pool.connection() for _i in range(3)]
     assert pool._cache.qsize() == 0
-    for i in range(3):
+    for _i in range(3):
         cache.pop().close()
     assert pool._cache.qsize() == 3
-    for i in range(6):
+    for _i in range(6):
         cache.append(pool.connection())
     assert pool._cache.qsize() == 0
-    for i in range(6):
+    for _i in range(6):
         cache.pop().close()
     assert pool._cache.qsize() == 6
     pool = PooledPg(3, 4)
@@ -125,13 +125,13 @@ def test_min_max_cached():
     assert pool._cache.qsize() == 3
     cache = [pool.connection() for _i in range(3)]
     assert pool._cache.qsize() == 0
-    for i in range(3):
+    for _i in range(3):
         cache.pop().close()
     assert pool._cache.qsize() == 3
-    for i in range(6):
+    for _i in range(6):
         cache.append(pool.connection())
     assert pool._cache.qsize() == 0
-    for i in range(6):
+    for _i in range(6):
         cache.pop().close()
     assert pool._cache.qsize() == 4
     pool = PooledPg(3, 2)
@@ -139,7 +139,7 @@ def test_min_max_cached():
     assert pool._cache.qsize() == 3
     cache = [pool.connection() for _i in range(4)]
     assert pool._cache.qsize() == 0
-    for i in range(4):
+    for _i in range(4):
         cache.pop().close()
     assert pool._cache.qsize() == 3
     pool = PooledPg(2, 5)
@@ -147,7 +147,7 @@ def test_min_max_cached():
     assert pool._cache.qsize() == 2
     cache = [pool.connection() for _i in range(10)]
     assert pool._cache.qsize() == 0
-    for i in range(10):
+    for _i in range(10):
         cache.pop().close()
     assert pool._cache.qsize() == 5
 
@@ -215,12 +215,12 @@ def connection():
 def test_one_thread_two_connections():
     pool = PooledPg(2)
     db1 = pool.connection()
-    for i in range(5):
+    for _i in range(5):
         db1.query('select test')
     db2 = pool.connection()
     assert db1 != db2
     assert db1._con != db2._con
-    for i in range(7):
+    for _i in range(7):
         db2.query('select test')
     assert db1.num_queries == 5
     assert db2.num_queries == 7
@@ -229,7 +229,7 @@ def test_one_thread_two_connections():
     assert db1 != db2
     assert db1._con != db2._con
     assert hasattr(db1, 'query')
-    for i in range(3):
+    for _i in range(3):
         db1.query('select test')
     assert db1.num_queries == 8
     db2.query('select test')
@@ -243,7 +243,7 @@ def test_three_threads_two_connections():
     def connection():
         queue.put(pool.connection(), timeout=1)
 
-    for i in range(3):
+    for _i in range(3):
         Thread(target=connection).start()
     db1 = queue.get(timeout=1)
     db2 = queue.get(timeout=1)
diff --git a/tests/test_simple_pooled_db.py b/tests/test_simple_pooled_db.py
index b0bff30..bc2cf50 100644
--- a/tests/test_simple_pooled_db.py
+++ b/tests/test_simple_pooled_db.py
@@ -99,7 +99,7 @@ def test_two_connections(threadsafety):
     db1 = db_pool.connection()
     assert db1 != db2
     assert hasattr(db1, 'cursor')
-    for i in range(3):
+    for _i in range(3):
         cursors1.append(db1.cursor())
     assert db1.open_cursors == 8
     cursors2.append(db2.cursor())
diff --git a/tests/test_simple_pooled_pg.py b/tests/test_simple_pooled_pg.py
index c0856cc..dd2b988 100644
--- a/tests/test_simple_pooled_pg.py
+++ b/tests/test_simple_pooled_pg.py
@@ -66,12 +66,12 @@ def test_close_connection():
 def test_two_connections():
     db_pool = my_db_pool(2)
     db1 = db_pool.connection()
-    for i in range(5):
+    for _i in range(5):
         db1.query('select 1')
     db2 = db_pool.connection()
     assert db1 != db2
     assert db1._con != db2._con
-    for i in range(7):
+    for _i in range(7):
         db2.query('select 1')
     assert db1.num_queries == 5
     assert db2.num_queries == 7
@@ -80,7 +80,7 @@ def test_two_connections():
     assert db1 != db2
     assert db1._con != db2._con
     assert hasattr(db1, 'query')
-    for i in range(3):
+    for _i in range(3):
         db1.query('select 1')
     assert db1.num_queries == 8
     db2.query('select 1')
diff --git a/tests/test_steady_db.py b/tests/test_steady_db.py
index dca75af..e5ce07a 100644
--- a/tests/test_steady_db.py
+++ b/tests/test_steady_db.py
@@ -41,7 +41,7 @@ def test_mocked_connection():
     assert hasattr(db, 'valid')
     assert db.valid
     assert db.open_cursors == 0
-    for i in range(3):
+    for _i in range(3):
         cursor = db.cursor()
         assert db.open_cursors == 1
         cursor.close()
@@ -67,7 +67,7 @@ def test_mocked_connection():
         assert cursor.fetchone() == f'test{i}'
     assert cursor.valid
     assert db.open_cursors == 1
-    for i in range(4):
+    for _i in range(4):
         cursor.callproc('test')
     cursor.close()
     assert not cursor.valid
@@ -108,17 +108,17 @@ def test_broken_connection():
     with pytest.raises(TypeError):
         SteadyDBCursor(None)
     db = steady_db_connect(dbapi, database='ok')
-    for i in range(3):
+    for _i in range(3):
         db.close()
     del db
     with pytest.raises(dbapi.OperationalError):
         steady_db_connect(dbapi, database='error')
     db = steady_db_connect(dbapi, database='ok')
     cursor = db.cursor()
-    for i in range(3):
+    for _i in range(3):
         cursor.close()
     cursor = db.cursor('ok')
-    for i in range(3):
+    for _i in range(3):
         cursor.close()
     with pytest.raises(dbapi.OperationalError):
         db.cursor('error')
@@ -161,7 +161,7 @@ def test_connection():  # noqa: PLR0915
     assert hasattr(db, 'cursor')
     assert hasattr(db, 'close')
     assert db._con.open_cursors == 0
-    for i in range(3):
+    for _i in range(3):
         cursor = db.cursor()
         assert db._con.open_cursors == 1
         cursor.close()
@@ -188,7 +188,7 @@ def test_connection():  # noqa: PLR0915
         assert cursor.fetchone() == f'test{i}'
     assert cursor.valid
     assert db._con.open_cursors == 1
-    for i in range(4):
+    for _i in range(4):
         cursor.callproc('test')
     cursor.close()
     assert not cursor.valid
@@ -400,7 +400,7 @@ def test_connection_setsession():
     assert db._con.num_queries == 0
     assert hasattr(db._con, 'session')
     assert tuple(db._con.session) == ('time zone', 'datestyle')
-    for i in range(11):
+    for _i in range(11):
         db.cursor().execute('select test')
     assert db._con.open_cursors == 0
     assert db._usage == 2
diff --git a/tests/test_steady_pg.py b/tests/test_steady_pg.py
index ef1659d..830c07c 100644
--- a/tests/test_steady_pg.py
+++ b/tests/test_steady_pg.py
@@ -82,7 +82,7 @@ def test_broken_connection():
         SteadyPgConnection('wrong')
     db = SteadyPgConnection(dbname='ok')
     internal_error_cls = sys.modules[db._con.__module__].InternalError
-    for i in range(3):
+    for _i in range(3):
         db.close()
     del db
     with pytest.raises(internal_error_cls):
@@ -259,7 +259,7 @@ def test_connection_setsession():
     assert db.num_queries == 0
     assert hasattr(db, 'session')
     assert tuple(db.session) == ('time zone', 'datestyle')
-    for i in range(11):
+    for _i in range(11):
         db.query('select test')
     assert db.num_queries == 2
     assert db.session == ['time zone', 'datestyle']

From 9923fc2086a59e410efe98c2b4bcc5e5c375d1a1 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Sat, 29 Apr 2023 23:15:56 +0200
Subject: [PATCH 65/84] Use pyproject.toml instead of setup.py (#48)

---
 pyproject.toml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++
 setup.py       | 58 --------------------------------------------------
 2 files changed, 56 insertions(+), 58 deletions(-)
 delete mode 100755 setup.py

diff --git a/pyproject.toml b/pyproject.toml
index e9a5f0a..b7cd471 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,59 @@
+[build-system]
+build-backend = "setuptools.build_meta"
+requires = [
+  "setuptools>=61.2",
+]
+
+[project]
+name = "DBUtils"
+version = "3.1.0b1"
+description = "Database connections for multi-threaded environments."
+license = {text = "MIT License"}
+authors = [{name = "Christoph Zwerschke", email = "cito@online.de"}]
+requires-python = ">3.7"
+classifiers = [
+  "Development Status :: 5 - Production/Stable",
+  "Environment :: Web Environment",
+  "Intended Audience :: Developers",
+  "License :: OSI Approved :: MIT License",
+  "Operating System :: OS Independent",
+  "Programming Language :: Python",
+  "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3.7",
+  "Programming Language :: Python :: 3.8",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Topic :: Database",
+  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
+  "Topic :: Software Development :: Libraries :: Python Modules",
+]
+[project.optional-dependencies]
+pg = [
+  "PyGreSQL>=5",
+]
+testing = [
+  "pytest>=7",
+  "ruff",
+]
+
+[project.readme]
+file = "README.md"
+content-type = "text/markdown"
+
+[project.urls]
+Homepage = "https://webwareforpython.github.io/DBUtils/"
+Download = "https://pypi.org/project/DBUtils/"
+Documentation = "https://webwareforpython.github.io/DBUtils/main.html"
+Changelog = "https://webwareforpython.github.io/DBUtils/changelog.html"
+"Issue Tracker" = "https://github.com/WebwareForPython/DBUtils/issues"
+"Source Code" = "https://github.com/WebwareForPython/DBUtils"
+
+[tool.setuptools]
+packages = ["dbutils"]
+platforms = ["any"]
+include-package-data = false
+
 [tool.ruff]
 select = [
   "A",      # flake8-builtins
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 43d4ce6..0000000
--- a/setup.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env python3
-
-"""Setup Script for DBUtils"""
-
-try:
-    from setuptools import setup
-except ImportError:
-    from distutils.core import setup
-
-__version__ = '3.1.0b1'
-
-readme = open('README.md').read()
-
-setup(
-    name='DBUtils',
-    version=__version__,
-    description='Database connections for multi-threaded environments.',
-    long_description=readme,
-    long_description_content_type='text/markdown',
-    classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Environment :: Web Environment',
-        'Intended Audience :: Developers',
-        'Topic :: Database',
-        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
-        'Topic :: Software Development :: Libraries :: Python Modules',
-        'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Operating System :: OS Independent',
-    ],
-    author='Christoph Zwerschke',
-    author_email='cito@online.de',
-    url='https://webwareforpython.github.io/DBUtils/',
-    download_url="https://pypi.org/project/DBUtils/",
-    project_urls={
-        'Documentation':
-            'https://webwareforpython.github.io/DBUtils/main.html',
-        'Changelog':
-            'https://webwareforpython.github.io/DBUtils/changelog.html',
-        'Issue Tracker':
-            'https://github.com/WebwareForPython/DBUtils/issues',
-        'Source Code':
-            'https://github.com/WebwareForPython/DBUtils'},
-    platforms=['any'],
-    license='MIT License',
-    packages=['dbutils'],
-    python_requires='>3.7',
-    extras_require={
-        "pg": ["PyGreSQL>=5"]
-    },
-    tests_require=["pytest>=7", "ruff"]
-)

From 9980bf56bc7833ef1f73493bca02374acd4e0ff6 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 23:20:54 +0200
Subject: [PATCH 66/84] Use from clause when re-raising exceptions

---
 dbutils/persistent_db.py | 5 +++--
 dbutils/steady_db.py     | 9 +++++----
 pyproject.toml           | 1 -
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/dbutils/persistent_db.py b/dbutils/persistent_db.py
index 54ddfcc..7ca7f7c 100644
--- a/dbutils/persistent_db.py
+++ b/dbutils/persistent_db.py
@@ -210,10 +210,11 @@ def connection(self, shareable=False):  # noqa: ARG002
         """
         try:
             con = self.thread.connection
-        except AttributeError:
+        except AttributeError as error:
             con = self.steady_connection()
             if not con.threadsafety():
-                raise NotSupportedError("Database module is not thread-safe.")
+                raise NotSupportedError(
+                    "Database module is not thread-safe.") from error
             self.thread.connection = con
         con._ping_check()
         return con
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 667611e..24f953f 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -277,10 +277,11 @@ def _create(self):
                                 con.OperationalError,
                                 con.InterfaceError,
                                 con.InternalError)
-                        except AttributeError:
+                        except AttributeError as error:
                             raise AttributeError(
                                 "Could not determine failure exceptions"
-                                " (please set failures or creator.dbapi).")
+                                " (please set failures or creator.dbapi)."
+                            ) from error
             if isinstance(self._failures, tuple):
                 self._failure = self._failures[0]
             else:
@@ -535,8 +536,8 @@ def __init__(self, con, *args, **kwargs):
         self._clearsizes()
         try:
             self._cursor = con._cursor(*args, **kwargs)
-        except AttributeError:
-            raise TypeError(f"{con!r} is not a SteadyDBConnection.")
+        except AttributeError as error:
+            raise TypeError(f"{con!r} is not a SteadyDBConnection.") from error
         self._closed = False
 
     def __enter__(self):
diff --git a/pyproject.toml b/pyproject.toml
index b7cd471..a66bc58 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -106,7 +106,6 @@ select = [
 ]
 # You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
-  "B904",
   "E722",
   "N811",
   "N818",

From df38633ad7432d67745a0bc4196a285c9a5e7203 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 23:26:20 +0200
Subject: [PATCH 67/84] Check that no bare except is used

---
 dbutils/pooled_db.py | 6 +++---
 dbutils/pooled_pg.py | 4 ++--
 dbutils/steady_db.py | 4 ++--
 dbutils/steady_pg.py | 2 +-
 pyproject.toml       | 1 -
 5 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 163ad9d..0e93d8a 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -386,7 +386,7 @@ def __del__(self):
         """Delete the pool."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
 
     def _wait_lock(self):
@@ -433,7 +433,7 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
 
     def __enter__(self):
@@ -525,7 +525,7 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
 
     def __enter__(self):
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index 4b3a251..281d175 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -250,7 +250,7 @@ def __del__(self):
         """Delete the pool."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
 
 
@@ -296,7 +296,7 @@ def __del__(self):
         """Delete the pooled connection."""
         try:
             self.close()
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
 
     def __enter__(self):
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 24f953f..ed6fdda 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -518,7 +518,7 @@ def __del__(self):
         """Delete the steady connection."""
         try:
             self._close()  # make sure the connection is closed
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
 
 
@@ -709,5 +709,5 @@ def __del__(self):
         """Delete the steady cursor."""
         try:
             self.close()  # make sure the cursor is closed
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index 6ccefe1..fb9d4bb 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -305,5 +305,5 @@ def __del__(self):
         """Delete the steady connection."""
         try:
             self._close()  # make sure the connection is closed
-        except:  # builtin Exceptions might not exist anymore
+        except:  # noqa: E722 - builtin Exceptions might not exist anymore
             pass
diff --git a/pyproject.toml b/pyproject.toml
index a66bc58..3edc6d4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -106,7 +106,6 @@ select = [
 ]
 # You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
-  "E722",
   "N811",
   "N818",
   "PIE790",

From 6414e009945a129e6175b871e8136a5fc9e64070 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 23:38:45 +0200
Subject: [PATCH 68/84] Exceptions classes should have Error suffix

---
 dbutils/pooled_db.py        | 15 ++++++++----
 dbutils/pooled_pg.py        | 13 ++++++----
 dbutils/simple_pooled_pg.py |  2 +-
 dbutils/steady_db.py        |  8 +++++--
 dbutils/steady_pg.py        | 10 +++++---
 pyproject.toml              |  2 --
 tests/test_pooled_db.py     | 48 ++++++++++++++++++-------------------
 tests/test_pooled_pg.py     | 20 +++++++++-------
 8 files changed, 69 insertions(+), 49 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 0e93d8a..5b10aad 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -156,7 +156,7 @@ class PooledDBError(Exception):
     """General PooledDB error."""
 
 
-class InvalidConnection(PooledDBError):
+class InvalidConnectionError(PooledDBError):
     """Database connection is invalid."""
 
 
@@ -164,10 +164,15 @@ class NotSupportedError(PooledDBError):
     """DB-API module not supported by PooledDB."""
 
 
-class TooManyConnections(PooledDBError):
+class TooManyConnectionsError(PooledDBError):
     """Too many database connections were opened."""
 
 
+# deprecated alias names for error classes
+InvalidConnection = InvalidConnectionError
+TooManyConnections = TooManyConnectionsError
+
+
 class PooledDB:
     """Pool for DB-API 2 connections.
 
@@ -392,7 +397,7 @@ def __del__(self):
     def _wait_lock(self):
         """Wait until notified or report an error."""
         if not self._blocking:
-            raise TooManyConnections
+            raise TooManyConnectionsError
         self._lock.wait()
 
 
@@ -427,7 +432,7 @@ def __getattr__(self, name):
         """Proxy all members of the class."""
         if self._con:
             return getattr(self._con, name)
-        raise InvalidConnection
+        raise InvalidConnectionError
 
     def __del__(self):
         """Delete the pooled connection."""
@@ -519,7 +524,7 @@ def __getattr__(self, name):
         """Proxy all members of the class."""
         if self._con:
             return getattr(self._con, name)
-        raise InvalidConnection
+        raise InvalidConnectionError
 
     def __del__(self):
         """Delete the pooled connection."""
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index 281d175..c8b9aeb 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -126,14 +126,19 @@ class PooledPgError(Exception):
     """General PooledPg error."""
 
 
-class InvalidConnection(PooledPgError):
+class InvalidConnectionError(PooledPgError):
     """Database connection is invalid."""
 
 
-class TooManyConnections(PooledPgError):
+class TooManyConnectionsError(PooledPgError):
     """Too many database connections were opened."""
 
 
+# deprecated alias names for error classes
+InvalidConnection = InvalidConnectionError
+TooManyConnections = TooManyConnectionsError
+
+
 class PooledPg:
     """Pool for classic PyGreSQL connections.
 
@@ -208,7 +213,7 @@ def connection(self):
         """Get a steady, cached PostgreSQL connection from the pool."""
         if self._connections:
             if not self._connections.acquire(self._blocking):
-                raise TooManyConnections
+                raise TooManyConnectionsError
         try:
             con = self._cache.get_nowait()
         except Empty:
@@ -290,7 +295,7 @@ def __getattr__(self, name):
         """Proxy all members of the class."""
         if self._con:
             return getattr(self._con, name)
-        raise InvalidConnection
+        raise InvalidConnectionError
 
     def __del__(self):
         """Delete the pooled connection."""
diff --git a/dbutils/simple_pooled_pg.py b/dbutils/simple_pooled_pg.py
index fe90d57..599213a 100644
--- a/dbutils/simple_pooled_pg.py
+++ b/dbutils/simple_pooled_pg.py
@@ -67,7 +67,7 @@
 Licensed under the MIT license.
 """
 
-from pg import DB as PgConnection
+from pg import DB as PgConnection  # noqa: N811
 
 from . import __version__
 
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index ed6fdda..0cf846f 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -98,10 +98,14 @@ class SteadyDBError(Exception):
     """General SteadyDB error."""
 
 
-class InvalidCursor(SteadyDBError):
+class InvalidCursorError(SteadyDBError):
     """Database cursor is invalid."""
 
 
+# deprecated alias names for error classes
+InvalidCursor = InvalidCursorError
+
+
 def connect(
         creator, maxusage=None, setsession=None,
         failures=None, ping=1, closeable=True, *args, **kwargs):
@@ -703,7 +707,7 @@ def __getattr__(self, name):
                 # make execution methods "tough"
                 return self._get_tough_method(name)
             return getattr(self._cursor, name)
-        raise InvalidCursor
+        raise InvalidCursorError
 
     def __del__(self):
         """Delete the steady cursor."""
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index fb9d4bb..b847573 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -69,7 +69,7 @@
 Licensed under the MIT license.
 """
 
-from pg import DB as PgConnection
+from pg import DB as PgConnection  # noqa: N811
 
 from . import __version__
 
@@ -78,10 +78,14 @@ class SteadyPgError(Exception):
     """General SteadyPg error."""
 
 
-class InvalidConnection(SteadyPgError):
+class InvalidConnectionError(SteadyPgError):
     """Database connection is invalid."""
 
 
+# deprecated alias names for error classes
+InvalidConnection = InvalidConnectionError
+
+
 class SteadyPgConnection:
     """Class representing steady connections to a PostgreSQL database.
 
@@ -299,7 +303,7 @@ def __getattr__(self, name):
                     or name.startswith('get_')):
                 attr = self._get_tough_method(attr)
             return attr
-        raise InvalidConnection
+        raise InvalidConnectionError
 
     def __del__(self):
         """Delete the steady connection."""
diff --git a/pyproject.toml b/pyproject.toml
index 3edc6d4..daa7da3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -106,8 +106,6 @@ select = [
 ]
 # You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
-  "N811",
-  "N818",
   "PIE790",
   "PLR5501",
   "PTH122",
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 1b16322..48c8ce5 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -16,11 +16,11 @@
 import pytest
 
 from dbutils.pooled_db import (
-    InvalidConnection,
+    InvalidConnectionError,
     NotSupportedError,
     PooledDB,
     SharedDBConnection,
-    TooManyConnections,
+    TooManyConnectionsError,
 )
 from dbutils.steady_db import SteadyDBConnection
 
@@ -207,7 +207,7 @@ def test_close_connection(dbapi, threadsafety):  # noqa: F811
     if shareable:
         assert db._shared_con is None
         assert shared_con.shared == 0
-    with pytest.raises(InvalidConnection):
+    with pytest.raises(InvalidConnectionError):
         assert db._usage
     assert not hasattr(db_con, '_num_queries')
     assert len(pool._idle_cache) == 1
@@ -692,9 +692,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     cache = []
     assert pool._connections == 0
@@ -712,13 +712,13 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
         assert len(pool._shared_cache) == 2
     else:
         assert pool._connections == 3
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
     if shareable:
         cache.append(pool.connection(True))
         assert pool._connections == 3
     else:
-        with pytest.raises(TooManyConnections):
+        with pytest.raises(TooManyConnectionsError):
             pool.connection()
     del cache
     assert pool._connections == 0
@@ -732,9 +732,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     assert db
     del db
@@ -750,9 +750,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
         assert len(pool._shared_cache) == 1
         assert pool._shared_cache[0].shared == 2
     else:
-        with pytest.raises(TooManyConnections):
+        with pytest.raises(TooManyConnectionsError):
             pool.connection()
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
     if shareable:
         cache.append(pool.connection(True))
@@ -760,7 +760,7 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
         assert len(pool._shared_cache) == 1
         assert pool._shared_cache[0].shared == 3
     else:
-        with pytest.raises(TooManyConnections):
+        with pytest.raises(TooManyConnectionsError):
             pool.connection(True)
     del cache
     assert pool._connections == 0
@@ -786,9 +786,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     assert len(pool._idle_cache) == 0
     if shareable:
         assert len(pool._shared_cache) == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     pool = PooledDB(dbapi, 4, 3, 2, 1, False)
     assert pool._maxconnections == 4
@@ -799,9 +799,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
         cache.append(pool.connection(False))
     assert pool._connections == 4
     assert len(pool._idle_cache) == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     pool = PooledDB(dbapi, 1, 2, 3, 4, False)
     assert pool._maxconnections == 4
@@ -819,9 +819,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
         assert pool._connections == 4
     else:
         assert pool._connections == 4
-        with pytest.raises(TooManyConnections):
+        with pytest.raises(TooManyConnectionsError):
             pool.connection()
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
     pool = PooledDB(dbapi, 0, 0, 3, 3, False)
     assert pool._maxconnections == 3
@@ -830,9 +830,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
     for _i in range(3):
         cache.append(pool.connection(False))
     assert pool._connections == 3
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(True)
     cache = []
     assert pool._connections == 0
@@ -844,9 +844,9 @@ def test_maxconnections(dbapi, threadsafety):  # noqa: F811, PLR0915
             cache.append(pool.connection())
         assert pool._connections == 3
     else:
-        with pytest.raises(TooManyConnections):
+        with pytest.raises(TooManyConnectionsError):
             pool.connection()
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection(False)
     pool = PooledDB(dbapi, 0, 0, 3)
     assert pool._maxconnections == 0
@@ -1165,7 +1165,7 @@ def test_shared_in_transaction(dbapi):  # noqa: F811
     db = pool.connection()
     db.begin()
     pool.connection(False)
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     pool = PooledDB(dbapi, 0, 2, 2)
     db1 = pool.connection()
@@ -1183,7 +1183,7 @@ def test_shared_in_transaction(dbapi):  # noqa: F811
     db.close()
     db2.begin()
     pool.connection(False)
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     db1.rollback()
     db = pool.connection()
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index cc57277..1bf3405 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -16,7 +16,11 @@
 import pg  # noqa: F401
 import pytest
 
-from dbutils.pooled_pg import InvalidConnection, PooledPg, TooManyConnections
+from dbutils.pooled_pg import (
+    InvalidConnectionError,
+    PooledPg,
+    TooManyConnectionsError,
+)
 from dbutils.steady_pg import SteadyPgConnection
 
 
@@ -88,7 +92,7 @@ def test_close_connection():
     db.query('select test')
     assert db.num_queries == 1
     db.close()
-    with pytest.raises(InvalidConnection):
+    with pytest.raises(InvalidConnectionError):
         assert db.num_queries
     db = pool.connection()
     assert hasattr(db, 'dbname')
@@ -157,14 +161,14 @@ def test_max_connections():
     assert pool._cache.qsize() == 1
     cache = [pool.connection() for _i in range(3)]
     assert pool._cache.qsize() == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     pool = PooledPg(0, 1, 1, False)
     assert pool._blocking == 0
     assert pool._cache.qsize() == 0
     db = pool.connection()
     assert pool._cache.qsize() == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     assert db
     del db
@@ -176,14 +180,14 @@ def test_max_connections():
     assert pool._cache.qsize() == 0
     cache.append(pool.connection())
     assert pool._cache.qsize() == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     pool = PooledPg(3, 2, 1, False)
     assert pool._cache.qsize() == 3
     cache = [pool.connection() for _i in range(3)]
     assert len(cache) == 3
     assert pool._cache.qsize() == 0
-    with pytest.raises(TooManyConnections):
+    with pytest.raises(TooManyConnectionsError):
         pool.connection()
     pool = PooledPg(1, 1, 1, True)
     assert pool._blocking == 1
@@ -307,11 +311,11 @@ def test_context_manager():
         db_con = db._con._con
         db.query('select test')
         assert db_con.num_queries == 1
-        with pytest.raises(TooManyConnections):
+        with pytest.raises(TooManyConnectionsError):
             pool.connection()
     with pool.connection() as db:
         db_con = db._con._con
         db.query('select test')
         assert db_con.num_queries == 2
-        with pytest.raises(TooManyConnections):
+        with pytest.raises(TooManyConnectionsError):
             pool.connection()

From f33853853249becc323e6d8c5b3f99719e9cef60 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sat, 29 Apr 2023 23:52:00 +0200
Subject: [PATCH 69/84] Simplify control flow

---
 dbutils/pooled_pg.py        | 11 +++++------
 dbutils/simple_pooled_db.py |  2 +-
 pyproject.toml              |  2 --
 tests/test_persistent_db.py | 19 +++++++++----------
 tests/test_persistent_pg.py | 13 ++++++-------
 5 files changed, 21 insertions(+), 26 deletions(-)

diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index c8b9aeb..df1c907 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -225,12 +225,11 @@ def cache(self, con):
         try:
             if self._reset == RESET_COMPLETELY:
                 con.reset()  # reset the connection completely
-            else:
-                if self._reset == RESET_ALWAYS_ROLLBACK or con._transaction:
-                    try:
-                        con.rollback()  # rollback a possible transaction
-                    except Exception:
-                        pass
+            elif self._reset == RESET_ALWAYS_ROLLBACK or con._transaction:
+                try:
+                    con.rollback()  # rollback a possible transaction
+                except Exception:
+                    pass
             self._cache.put_nowait(con)  # and then put it back into the cache
         except Full:
             con.close()
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index d95f5d2..5e5c926 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -212,4 +212,4 @@ def _threadsafe_return_connection(self, con):
         In this case, the connections always stay in the pool,
         so there is no need to do anything here.
         """
-        pass
+        # we don't need to do anything here
diff --git a/pyproject.toml b/pyproject.toml
index daa7da3..266480e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -106,8 +106,6 @@ select = [
 ]
 # You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
-  "PIE790",
-  "PLR5501",
   "PTH122",
   "PTH123",
   "S110",
diff --git a/tests/test_persistent_db.py b/tests/test_persistent_db.py
index cd35127..32afd21 100644
--- a/tests/test_persistent_db.py
+++ b/tests/test_persistent_db.py
@@ -84,17 +84,16 @@ def run_queries(idx):
             db = persist.connection()
             if db != this_db:
                 res = 'error - not persistent'
+            elif q == 'ping':
+                res = 'ok - thread alive'
+            elif q == 'close':
+                db.close()
+                res = 'ok - connection closed'
             else:
-                if q == 'ping':
-                    res = 'ok - thread alive'
-                elif q == 'close':
-                    db.close()
-                    res = 'ok - connection closed'
-                else:
-                    cursor = db.cursor()
-                    cursor.execute(q)
-                    res = cursor.fetchone()
-                    cursor.close()
+                cursor = db.cursor()
+                cursor.execute(q)
+                res = cursor.fetchone()
+                cursor.close()
             res = f'{idx}({db._usage}): {res}'
             result_queue[idx].put(res, timeout=1)
         if db:
diff --git a/tests/test_persistent_pg.py b/tests/test_persistent_pg.py
index 6e1a258..189ca18 100644
--- a/tests/test_persistent_pg.py
+++ b/tests/test_persistent_pg.py
@@ -62,14 +62,13 @@ def run_queries(idx):
             db = persist.connection()
             if db.db != this_db:
                 res = 'error - not persistent'
+            elif q == 'ping':
+                res = 'ok - thread alive'
+            elif q == 'close':
+                db.db.close()
+                res = 'ok - connection closed'
             else:
-                if q == 'ping':
-                    res = 'ok - thread alive'
-                elif q == 'close':
-                    db.db.close()
-                    res = 'ok - connection closed'
-                else:
-                    res = db.query(q)
+                res = db.query(q)
             res = f'{idx}({db._usage}): {res}'
             result_queue[idx].put(res, timeout=1)
         if db:

From e8e73167e4f121ca4519f0f2b9f6284b9f389960 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 00:02:19 +0200
Subject: [PATCH 70/84] Modernize docs make script

---
 docs/make.py   | 13 +++++++------
 pyproject.toml |  2 --
 2 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/docs/make.py b/docs/make.py
index ef88183..59e974b 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -3,26 +3,27 @@
 """Build HTML from reST files."""
 
 from glob import glob
-from os.path import splitext
+from pathlib import Path
 
 from docutils.core import publish_file
 
 print("Creating the documentation...")
 
 for rst_file in glob('*.rst'):
-    name = splitext(rst_file)[0]
-    lang = splitext(name)[1]
+    rst_path = Path(rst_file)
+    name = Path(rst_file).stem
+    lang = Path(name).suffix
     if lang.startswith('.'):
         lang = lang[1:]
         if lang == 'zh':
             lang = 'zh_cn'
     else:
         lang = 'en'
-    html_file = name + '.html'
+    html_path = Path(name + '.html')
     print(name, lang)
 
-    with open(rst_file, encoding='utf-8-sig') as source:
-        with open(html_file, 'w', encoding='utf-8') as destination:
+    with rst_path.open(encoding='utf-8-sig') as source:
+        with html_path.open('w', encoding='utf-8') as destination:
             output = publish_file(
                 writer_name='html5', source=source, destination=destination,
                 enable_exit_status=True,
diff --git a/pyproject.toml b/pyproject.toml
index 266480e..f6cc8d9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -106,8 +106,6 @@ select = [
 ]
 # You can use `ruff rule ...` to see what these ignored rules check
 ignore = [
-  "PTH122",
-  "PTH123",
   "S110",
 ]
 line-length = 79

From c287b10843c91bd13cbd1dd37fab5006d194b263 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 00:14:27 +0200
Subject: [PATCH 71/84] Add and use extra requirements for docs and tests

---
 pyproject.toml | 5 ++++-
 tox.ini        | 4 ++--
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index f6cc8d9..8c7613d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,7 +32,10 @@ classifiers = [
 pg = [
   "PyGreSQL>=5",
 ]
-testing = [
+docs = [
+  "docutils",
+]
+tests = [
   "pytest>=7",
   "ruff",
 ]
diff --git a/tox.ini b/tox.ini
index 812d428..529d75c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@ envlist = py3{7,8,9,10,11}, ruff, manifest, docs, spell
 [testenv]
 setenv =
     PYTHONPATH = {toxinidir}
-deps = pytest>=7
+extras = tests
 commands =
     pytest {posargs}
 
@@ -28,7 +28,7 @@ commands =
 
 [testenv:docs]
 basepython = python3.10
-deps = docutils
+extras = docs
 changedir = docs
 commands =
     python make.py

From f2ed0242b1e22374be049efc28419afe0ef5dce9 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 00:17:32 +0200
Subject: [PATCH 72/84] Allow silently catching errors

---
 pyproject.toml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 8c7613d..6b68c26 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -107,9 +107,9 @@ select = [
   # "T20",  # flake8-print
   # "TRY",  # tryceratops
 ]
-# You can use `ruff rule ...` to see what these ignored rules check
+# Note: use `ruff rule ...` to see explanations of rules
 ignore = [
-  "S110",
+  "S110",  # allow silently catching errors
 ]
 line-length = 79
 target-version = "py37"

From 2aed93cf4b4760666ce8935f61436dec22159780 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 00:22:37 +0200
Subject: [PATCH 73/84] Check comma conventions

---
 dbutils/steady_db.py    | 2 +-
 pyproject.toml          | 2 +-
 tests/test_pooled_pg.py | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 0cf846f..3360eb7 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -284,7 +284,7 @@ def _create(self):
                         except AttributeError as error:
                             raise AttributeError(
                                 "Could not determine failure exceptions"
-                                " (please set failures or creator.dbapi)."
+                                " (please set failures or creator.dbapi).",
                             ) from error
             if isinstance(self._failures, tuple):
                 self._failure = self._failures[0]
diff --git a/pyproject.toml b/pyproject.toml
index 6b68c26..67d7c58 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -64,6 +64,7 @@ select = [
   "B",      # flake8-bugbear
   "C4",     # flake8-comprehensions
   "C90",    # McCabe cyclomatic complexity
+  "COM",    # flake8-commas
   "DTZ",    # flake8-datetimez
   "E",      # pycodestyle
   "EXE",    # flake8-executable
@@ -93,7 +94,6 @@ select = [
   "YTT",    # flake8-2020
   # "ANN",  # flake8-annotations
   # "BLE",  # flake8-blind-except
-  # "COM",  # flake8-commas
   # "D",    # pydocstyle
   # "DJ",   # flake8-django
   # "EM",   # flake8-errmsg
diff --git a/tests/test_pooled_pg.py b/tests/test_pooled_pg.py
index 1bf3405..0eed92b 100644
--- a/tests/test_pooled_pg.py
+++ b/tests/test_pooled_pg.py
@@ -70,7 +70,7 @@ def test_create_connection():
     assert db.user is None
     assert hasattr(db, 'num_queries')
     assert db.num_queries == 0
-    pool = PooledPg(0, 0, 0, False, 3, ('set datestyle',),)
+    pool = PooledPg(0, 0, 0, False, 3, ('set datestyle',))
     assert pool._maxusage == 3
     assert pool._setsession == ('set datestyle',)
     db = pool.connection()

From 0e871d34ad0df608fd2c65c9ef0aa7cb3f23fcdb Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 00:52:54 +0200
Subject: [PATCH 74/84] Check and improve docstrings

---
 dbutils/__init__.py         |  2 +-
 dbutils/pooled_db.py        | 18 ++++--------------
 dbutils/simple_pooled_db.py |  5 ++++-
 dbutils/simple_pooled_pg.py |  5 ++++-
 dbutils/steady_db.py        | 13 +++++++++----
 dbutils/steady_pg.py        |  2 ++
 pyproject.toml              |  6 ++++--
 tests/__init__.py           |  2 +-
 8 files changed, 29 insertions(+), 24 deletions(-)

diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index f7a4ee8..9008cfc 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -1,4 +1,4 @@
-# DBUtils main package
+"""The DBUtils main package."""
 
 __all__ = [
     '__version__',
diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 5b10aad..35d0bae 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -146,6 +146,7 @@
 Licensed under the MIT license.
 """
 
+from functools import total_ordering
 from threading import Condition
 
 from . import __version__
@@ -450,6 +451,7 @@ def __exit__(self, *exc):
         self.close()
 
 
+@total_ordering
 class SharedDBConnection:
     """Auxiliary class for shared connections."""
 
@@ -462,28 +464,16 @@ def __init__(self, con):
         self.shared = 1
 
     def __lt__(self, other):
+        """Check whether this connection should come before the other one."""
         if self.con._transaction == other.con._transaction:
             return self.shared < other.shared
         return not self.con._transaction
 
-    def __le__(self, other):
-        if self.con._transaction == other.con._transaction:
-            return self.shared <= other.shared
-        return not self.con._transaction
-
     def __eq__(self, other):
+        """Check whether this connection is the same as the other one."""
         return (self.con._transaction == other.con._transaction
                 and self.shared == other.shared)
 
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def __gt__(self, other):
-        return other.__lt__(self)
-
-    def __ge__(self, other):
-        return other.__le__(self)
-
     def share(self):
         """Increase the share of this connection."""
         self.shared += 1
diff --git a/dbutils/simple_pooled_db.py b/dbutils/simple_pooled_db.py
index 5e5c926..2558814 100644
--- a/dbutils/simple_pooled_db.py
+++ b/dbutils/simple_pooled_db.py
@@ -91,6 +91,7 @@ class PooledDBConnection:
     """
 
     def __init__(self, pool, con):
+        """Initialize pooled connection."""
         self._con = con
         self._pool = pool
 
@@ -103,10 +104,12 @@ def close(self):
             self._con = None
 
     def __getattr__(self, name):
-        # All other members are the same.
+        """Get the attribute with the given name."""
+        # All other attributes are the same.
         return getattr(self._con, name)
 
     def __del__(self):
+        """Delete the pooled connection."""
         self.close()
 
 
diff --git a/dbutils/simple_pooled_pg.py b/dbutils/simple_pooled_pg.py
index 599213a..5997e27 100644
--- a/dbutils/simple_pooled_pg.py
+++ b/dbutils/simple_pooled_pg.py
@@ -80,6 +80,7 @@ class PooledPgConnection:
     """
 
     def __init__(self, pool, con):
+        """Initialize pooled connection."""
         self._con = con
         self._pool = pool
 
@@ -92,10 +93,12 @@ def close(self):
             self._con = None
 
     def __getattr__(self, name):
-        # All other members are the same.
+        """Get the attribute with the given name."""
+        # All other attributes are the same.
         return getattr(self._con, name)
 
     def __del__(self):
+        """Delete the pooled connection."""
         self.close()
 
 
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 3360eb7..bc7ea81 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -109,7 +109,9 @@ class InvalidCursorError(SteadyDBError):
 def connect(
         creator, maxusage=None, setsession=None,
         failures=None, ping=1, closeable=True, *args, **kwargs):
-    """A tough version of the connection constructor of a DB-API 2 module.
+    """Create a "tough" connection.
+
+    A hardened version of the connection function of a DB-API 2 module.
 
     creator: either an arbitrary function returning new DB-API 2 compliant
         connection objects or a DB-API 2 compliant database module
@@ -138,7 +140,7 @@ def connect(
 
 
 class SteadyDBConnection:
-    """A "tough" version of DB-API 2 connections."""
+    """A hardened version of DB-API 2 connections."""
 
     version = __version__
 
@@ -477,7 +479,10 @@ def ping(self, *args, **kwargs):
         return self._con.ping(*args, **kwargs)
 
     def _cursor(self, *args, **kwargs):
-        """A "tough" version of the method cursor()."""
+        """Create a "tough" cursor.
+
+        This is a hardened version of the method cursor().
+        """
         # The args and kwargs are not part of the standard,
         # but some database modules seem to use these.
         transaction = self._transaction
@@ -527,7 +532,7 @@ def __del__(self):
 
 
 class SteadyDBCursor:
-    """A "tough" version of DB-API 2 cursors."""
+    """A hardened version of DB-API 2 cursors."""
 
     def __init__(self, con, *args, **kwargs):
         """Create a "tough" DB-API 2 cursor."""
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index b847573..fa67a83 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -105,6 +105,8 @@ def __init__(
             *args, **kwargs):
         """Create a "tough" PostgreSQL connection.
 
+        A hardened version of the DB wrapper class of PyGreSQL.
+
         maxusage: maximum usage limit for the underlying PyGreSQL connection
             (number of uses, 0 or None means unlimited usage)
             When this limit is reached, the connection is automatically reset.
diff --git a/pyproject.toml b/pyproject.toml
index 67d7c58..1b249d9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -65,6 +65,7 @@ select = [
   "C4",     # flake8-comprehensions
   "C90",    # McCabe cyclomatic complexity
   "COM",    # flake8-commas
+  "D",      # pydocstyle
   "DTZ",    # flake8-datetimez
   "E",      # pycodestyle
   "EXE",    # flake8-executable
@@ -94,8 +95,6 @@ select = [
   "YTT",    # flake8-2020
   # "ANN",  # flake8-annotations
   # "BLE",  # flake8-blind-except
-  # "D",    # pydocstyle
-  # "DJ",   # flake8-django
   # "EM",   # flake8-errmsg
   # "ERA",  # eradicate
   # "FBT",  # flake8-boolean-trap
@@ -109,6 +108,8 @@ select = [
 ]
 # Note: use `ruff rule ...` to see explanations of rules
 ignore = [
+  "D203",  # no blank linke before class docstring
+  "D213",  # multi-line docstrings should not start at second line
   "S110",  # allow silently catching errors
 ]
 line-length = 79
@@ -127,6 +128,7 @@ max-statements = 100
   "INP001",  # allow stand-alone scripts
 ]
 "tests/*" = [
+  "D",  # no docstrings necessary here
   "PLR2004",  # allow magic values
   "S101",  # allow assert statements
 ]
diff --git a/tests/__init__.py b/tests/__init__.py
index 49922b8..3c7c5ff 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,4 +1,4 @@
-"""DBUtils tests"""
+"""The DBUtils tests package."""
 
 # make sure the mock pg module is installed
 from . import mock_pg as pg  # noqa: F401

From 236b58142e522c793d861bae3568e0432ce2bf8c Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 00:58:23 +0200
Subject: [PATCH 75/84] Fix typo

---
 pyproject.toml | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 1b249d9..c9f9fe8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -68,6 +68,7 @@ select = [
   "D",      # pydocstyle
   "DTZ",    # flake8-datetimez
   "E",      # pycodestyle
+  "ERA",    # eradicate
   "EXE",    # flake8-executable
   "F",      # Pyflakes
   "G",      # flake8-logging-format
@@ -96,10 +97,7 @@ select = [
   # "ANN",  # flake8-annotations
   # "BLE",  # flake8-blind-except
   # "EM",   # flake8-errmsg
-  # "ERA",  # eradicate
   # "FBT",  # flake8-boolean-trap
-  # "NPY",  # NumPy-specific rules
-  # "PD",   # pandas-vet
   # "Q",    # flake8-quotes
   # "SIM",  # flake8-simplify
   # "SLF",  # flake8-self
@@ -108,7 +106,7 @@ select = [
 ]
 # Note: use `ruff rule ...` to see explanations of rules
 ignore = [
-  "D203",  # no blank linke before class docstring
+  "D203",  # no blank line before class docstring
   "D213",  # multi-line docstrings should not start at second line
   "S110",  # allow silently catching errors
 ]

From 13fffe30f9c68beea9bd5ca2e9d4adf826b7a598 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 01:37:25 +0200
Subject: [PATCH 76/84] Simplify and add checks for simple code

---
 dbutils/pooled_db.py    | 33 ++++++++--------
 dbutils/pooled_pg.py    | 29 +++++++-------
 dbutils/steady_db.py    | 83 +++++++++++++++--------------------------
 dbutils/steady_pg.py    | 23 +++++-------
 docs/make.py            | 22 +++++------
 pyproject.toml          | 11 +++---
 tests/test_pooled_db.py | 11 ++----
 7 files changed, 90 insertions(+), 122 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index 35d0bae..be18c94 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -146,6 +146,7 @@
 Licensed under the MIT license.
 """
 
+from contextlib import suppress
 from functools import total_ordering
 from threading import Condition
 
@@ -349,11 +350,10 @@ def unshare(self, con):
         with self._lock:
             con.unshare()
             shared = con.shared
-            if not shared:  # connection is idle,
-                try:  # so try to remove it
-                    self._shared_cache.remove(con)  # from shared cache
-                except ValueError:
-                    pass  # pool has already been closed
+            if not shared:  # connection is idle
+                # try to remove it from shared cache
+                with suppress(ValueError):  # if pool has already been closed
+                    self._shared_cache.remove(con)
         if not shared:  # connection has become idle,
             self.cache(con.con)  # so add it to the idle cache
 
@@ -374,25 +374,22 @@ def close(self):
         with self._lock:
             while self._idle_cache:  # close all idle connections
                 con = self._idle_cache.pop(0)
-                try:
+                with suppress(Exception):
                     con.close()
-                except Exception:
-                    pass
             if self._maxshared:  # close all shared connections
                 while self._shared_cache:
                     con = self._shared_cache.pop(0).con
-                    try:
+                    with suppress(Exception):
                         con.close()
-                    except Exception:
-                        pass
                     self._connections -= 1
             self._lock.notify_all()
 
     def __del__(self):
         """Delete the pool."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self.close()
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
 
     def _wait_lock(self):
@@ -437,9 +434,10 @@ def __getattr__(self, name):
 
     def __del__(self):
         """Delete the pooled connection."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self.close()
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
 
     def __enter__(self):
@@ -518,9 +516,10 @@ def __getattr__(self, name):
 
     def __del__(self):
         """Delete the pooled connection."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self.close()
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
 
     def __enter__(self):
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index df1c907..eb73708 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -112,6 +112,7 @@
 Licensed under the MIT license.
 """
 
+from contextlib import suppress
 from queue import Empty, Full, Queue
 
 from . import __version__
@@ -186,9 +187,8 @@ def __init__(
             maxcached = 0
         if maxconnections is None:
             maxconnections = 0
-        if maxcached:
-            if maxcached < mincached:
-                maxcached = mincached
+        if maxcached and maxcached < mincached:
+            maxcached = mincached
         if maxconnections:
             if maxconnections < maxcached:
                 maxconnections = maxcached
@@ -211,9 +211,8 @@ def steady_connection(self):
 
     def connection(self):
         """Get a steady, cached PostgreSQL connection from the pool."""
-        if self._connections:
-            if not self._connections.acquire(self._blocking):
-                raise TooManyConnectionsError
+        if self._connections and not self._connections.acquire(self._blocking):
+            raise TooManyConnectionsError
         try:
             con = self._cache.get_nowait()
         except Empty:
@@ -226,10 +225,8 @@ def cache(self, con):
             if self._reset == RESET_COMPLETELY:
                 con.reset()  # reset the connection completely
             elif self._reset == RESET_ALWAYS_ROLLBACK or con._transaction:
-                try:
+                with suppress(Exception):
                     con.rollback()  # rollback a possible transaction
-                except Exception:
-                    pass
             self._cache.put_nowait(con)  # and then put it back into the cache
         except Full:
             con.close()
@@ -241,10 +238,8 @@ def close(self):
         while 1:
             try:
                 con = self._cache.get_nowait()
-                try:
+                with suppress(Exception):
                     con.close()
-                except Exception:
-                    pass
                 if self._connections:
                     self._connections.release()
             except Empty:
@@ -252,9 +247,10 @@ def close(self):
 
     def __del__(self):
         """Delete the pool."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self.close()
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
 
 
@@ -298,9 +294,10 @@ def __getattr__(self, name):
 
     def __del__(self):
         """Delete the pooled connection."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self.close()
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
 
     def __enter__(self):
diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index bc7ea81..52e1b60 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -90,6 +90,7 @@
 """
 
 import sys
+from contextlib import suppress
 
 from . import __version__
 
@@ -232,10 +233,7 @@ def _create(self):
                     else:
                         break
                     i = mod.rfind('.')
-                    if i < 0:
-                        mod = None
-                    else:
-                        mod = mod[:i]
+                    mod = None if i < 0 else mod[:i]
                 else:
                     try:
                         mod = con.OperationalError.__module__
@@ -251,20 +249,15 @@ def _create(self):
                         else:
                             break
                         i = mod.rfind('.')
-                        if i < 0:
-                            mod = None
-                        else:
-                            mod = mod[:i]
+                        mod = None if i < 0 else mod[:i]
                     else:
                         self._dbapi = None
             if self._threadsafety is None:
                 try:
                     self._threadsafety = self._dbapi.threadsafety
                 except AttributeError:
-                    try:
+                    with suppress(AttributeError):
                         self._threadsafety = con.threadsafety
-                    except AttributeError:
-                        pass
             if self._failures is None:
                 try:
                     self._failures = (
@@ -296,10 +289,8 @@ def _create(self):
         except Exception as error:
             # the database module could not be determined
             # or the session could not be prepared
-            try:  # close the connection first
-                con.close()
-            except Exception:
-                pass
+            with suppress(Exception):
+                con.close()  # close the connection first
             raise error  # re-raise the original error again
         return con
 
@@ -323,14 +314,12 @@ def _store(self, con):
     def _close(self):
         """Close the tough connection.
 
-        You can always close a tough connection with this method
+        You can always close a tough connection with this method,
         and it will not complain if you close it more than once.
         """
         if not self._closed:
-            try:
+            with suppress(Exception):
                 self._con.close()
-            except Exception:
-                pass
             self._transaction = False
             self._closed = True
 
@@ -340,15 +329,13 @@ def _reset(self, force=False):
         Rollback if forced or the connection was in a transaction.
         """
         if not self._closed and (force or self._transaction):
-            try:
+            with suppress(Exception):
                 self.rollback()
-            except Exception:
-                pass
 
     def _ping_check(self, ping=1, reconnect=True):
         """Check whether the connection is still alive using ping().
 
-        If the the underlying connection is not active and the ping
+        If the underlying connection is not active and the ping
         parameter is set accordingly, the connection will be recreated
         unless the connection is currently inside a transaction.
         """
@@ -372,7 +359,7 @@ def _ping_check(self, ping=1, reconnect=True):
             if reconnect and not self._transaction:
                 try:  # try to reopen the connection
                     con = self._create()
-                except Exception:
+                except Exception:  # noqa: S110
                     pass
                 else:
                     self._close()
@@ -402,7 +389,7 @@ def threadsafety(self):
     def close(self):
         """Close the tough connection.
 
-        You are allowed to close a tough connection by default
+        You are allowed to close a tough connection by default,
         and it will not complain if you close it more than once.
 
         You can disallow closing connections by setting
@@ -439,7 +426,7 @@ def commit(self):
         except self._failures as error:  # cannot commit
             try:  # try to reopen the connection
                 con = self._create()
-            except Exception:
+            except Exception:  # noqa: S110
                 pass
             else:
                 self._close()
@@ -454,7 +441,7 @@ def rollback(self):
         except self._failures as error:  # cannot rollback
             try:  # try to reopen the connection
                 con = self._create()
-            except Exception:
+            except Exception:  # noqa: S110
                 pass
             else:
                 self._close()
@@ -497,12 +484,12 @@ def _cursor(self, *args, **kwargs):
         except self._failures as error:  # error in getting cursor
             try:  # try to reopen the connection
                 con = self._create()
-            except Exception:
+            except Exception:  # noqa: S110
                 pass
             else:
                 try:  # and try one more time to get a cursor
                     cursor = con.cursor(*args, **kwargs)
-                except Exception:
+                except Exception:  # noqa: S110
                     pass
                 else:
                     self._close()
@@ -510,10 +497,8 @@ def _cursor(self, *args, **kwargs):
                     if transaction:
                         raise error  # re-raise the original error again
                     return cursor
-                try:
+                with suppress(Exception):
                     con.close()
-                except Exception:
-                    pass
             if transaction:
                 self._transaction = False
             raise error  # re-raise the original error again
@@ -525,9 +510,10 @@ def cursor(self, *args, **kwargs):
 
     def __del__(self):
         """Delete the steady connection."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self._close()  # make sure the connection is closed
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
 
 
@@ -596,10 +582,8 @@ def close(self):
         It will not complain if you close it more than once.
         """
         if not self._closed:
-            try:
+            with suppress(Exception):
                 self._cursor.close()
-            except Exception:
-                pass
             self._closed = True
 
     def _get_tough_method(self, name):
@@ -626,7 +610,7 @@ def tough_method(*args, **kwargs):
                     try:
                         cursor2 = con._cursor(
                             *self._args, **self._kwargs)  # open new cursor
-                    except Exception:
+                    except Exception:  # noqa: S110
                         pass
                     else:
                         try:  # and try one more time to execute
@@ -636,26 +620,24 @@ def tough_method(*args, **kwargs):
                             result = method(*args, **kwargs)
                             if execute:
                                 self._clearsizes()
-                        except Exception:
+                        except Exception:  # noqa: S110
                             pass
                         else:
                             self.close()
                             self._cursor = cursor2
                             con._usage += 1
                             return result
-                        try:
+                        with suppress(Exception):
                             cursor2.close()
-                        except Exception:
-                            pass
                 try:  # try to reopen the connection
                     con2 = con._create()
-                except Exception:
+                except Exception:  # noqa: S110
                     pass
                 else:
                     try:
                         cursor2 = con2.cursor(
                             *self._args, **self._kwargs)  # open new cursor
-                    except Exception:
+                    except Exception:  # noqa: S110
                         pass
                     else:
                         if transaction:
@@ -689,14 +671,10 @@ def tough_method(*args, **kwargs):
                             if error2:
                                 raise error2  # raise the other error
                             return result
-                        try:
+                        with suppress(Exception):
                             cursor2.close()
-                        except Exception:
-                            pass
-                    try:
+                    with suppress(Exception):
                         con2.close()
-                    except Exception:
-                        pass
                 if transaction:
                     self._transaction = False
                 raise error  # re-raise the original error again
@@ -716,7 +694,8 @@ def __getattr__(self, name):
 
     def __del__(self):
         """Delete the steady cursor."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self.close()  # make sure the cursor is closed
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
diff --git a/dbutils/steady_pg.py b/dbutils/steady_pg.py
index fa67a83..d17d3bb 100644
--- a/dbutils/steady_pg.py
+++ b/dbutils/steady_pg.py
@@ -69,6 +69,8 @@
 Licensed under the MIT license.
 """
 
+from contextlib import suppress
+
 from pg import DB as PgConnection  # noqa: N811
 
 from . import __version__
@@ -155,21 +157,19 @@ def _setsession(self):
     def _close(self):
         """Close the tough connection.
 
-        You can always close a tough connection with this method
+        You can always close a tough connection with this method,
         and it will not complain if you close it more than once.
         """
         if not self._closed:
-            try:
+            with suppress(Exception):
                 self._con.close()
-            except Exception:
-                pass
             self._transaction = False
             self._closed = True
 
     def close(self):
         """Close the tough connection.
 
-        You are allowed to close a tough connection by default
+        You are allowed to close a tough connection by default,
         and it will not complain if you close it more than once.
 
         You can disallow closing connections by setting
@@ -191,10 +191,8 @@ def reopen(self):
         except Exception:
             if self._transaction:
                 self._transaction = False
-                try:
+                with suppress(Exception):
                     self._con.query('rollback')
-                except Exception:
-                    pass
         else:
             self._transaction = False
             self._closed = False
@@ -216,10 +214,8 @@ def reset(self):
             try:
                 self.reopen()
             except Exception:
-                try:
+                with suppress(Exception):
                     self.rollback()
-                except Exception:
-                    pass
 
     def begin(self, sql=None):
         """Begin a transaction."""
@@ -309,7 +305,8 @@ def __getattr__(self, name):
 
     def __del__(self):
         """Delete the steady connection."""
-        try:
+        # builtins (including Exceptions) might not exist anymore
+        try:  # noqa: SIM105
             self._close()  # make sure the connection is closed
-        except:  # noqa: E722 - builtin Exceptions might not exist anymore
+        except:  # noqa: E722, S110
             pass
diff --git a/docs/make.py b/docs/make.py
index 59e974b..c071071 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -22,16 +22,16 @@
     html_path = Path(name + '.html')
     print(name, lang)
 
-    with rst_path.open(encoding='utf-8-sig') as source:
-        with html_path.open('w', encoding='utf-8') as destination:
-            output = publish_file(
-                writer_name='html5', source=source, destination=destination,
-                enable_exit_status=True,
-                settings_overrides={
-                    "stylesheet_path": 'doc.css',
-                    "embed_stylesheet": False,
-                    "toc_backlinks": False,
-                    "language_code": lang,
-                    "exit_status_level": 2})
+    with rst_path.open(encoding='utf-8-sig') as source, \
+            html_path.open('w', encoding='utf-8') as destination:
+        output = publish_file(
+            writer_name='html5', source=source, destination=destination,
+            enable_exit_status=True,
+            settings_overrides={
+                "stylesheet_path": 'doc.css',
+                "embed_stylesheet": False,
+                "toc_backlinks": False,
+                "language_code": lang,
+                "exit_status_level": 2})
 
 print("Done.")
diff --git a/pyproject.toml b/pyproject.toml
index c9f9fe8..f6d6772 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -88,6 +88,7 @@ select = [
   "RSE",    # flake8-raise
   "RUF",    # Ruff-specific rules
   "S",      # flake8-bandit
+  "SIM",    # flake8-simplify
   "T10",    # flake8-debugger
   "TCH",    # flake8-type-checking
   "TID",    # flake8-tidy-imports
@@ -99,7 +100,6 @@ select = [
   # "EM",   # flake8-errmsg
   # "FBT",  # flake8-boolean-trap
   # "Q",    # flake8-quotes
-  # "SIM",  # flake8-simplify
   # "SLF",  # flake8-self
   # "T20",  # flake8-print
   # "TRY",  # tryceratops
@@ -108,18 +108,17 @@ select = [
 ignore = [
   "D203",  # no blank line before class docstring
   "D213",  # multi-line docstrings should not start at second line
-  "S110",  # allow silently catching errors
 ]
 line-length = 79
 target-version = "py37"
 
 [tool.ruff.mccabe]
-max-complexity = 32
+max-complexity = 30
 
 [tool.ruff.pylint]
-max-args = 15
-max-branches = 50
-max-statements = 100
+max-args = 12
+max-branches = 35
+max-statements = 95
 
 [tool.ruff.per-file-ignores]
 "docs/*" = [
diff --git a/tests/test_pooled_db.py b/tests/test_pooled_db.py
index 48c8ce5..b5ef7cf 100644
--- a/tests/test_pooled_db.py
+++ b/tests/test_pooled_db.py
@@ -922,10 +922,7 @@ def test_maxusage(dbapi, threadsafety, maxusage):  # noqa: F811
         assert r == f'test{i}'
         cursor.close()
         assert db._con._con.open_cursors == 0
-        if maxusage:
-            j = i % maxusage + 1
-        else:
-            j = i + 1
+        j = i % maxusage + 1 if maxusage else i + 1
         assert db._usage == j
         assert db._con._con.num_uses == j
         assert db._con._con.num_queries == j
@@ -1269,18 +1266,18 @@ def test_shared_db_connection_compare(dbapi):  # noqa: F811
     assert con1 == con2
     assert con1 <= con2
     assert con1 >= con2
-    assert not con1 != con2
+    assert not con1 != con2  # noqa: SIM202
     assert not con1 < con2
     assert not con1 > con2
     con2.share()
-    assert not con1 == con2
+    assert not con1 == con2  # noqa: SIM201
     assert con1 <= con2
     assert not con1 >= con2
     assert con1 != con2
     assert con1 < con2
     assert not con1 > con2
     con1.con._transaction = True
-    assert not con1 == con2
+    assert not con1 == con2  # noqa: SIM201
     assert not con1 <= con2
     assert con1 >= con2
     assert con1 != con2

From 4d77b8db2ed2091d48f28cb17611a34b6c4a84b5 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 01:42:57 +0200
Subject: [PATCH 77/84] Check for print statements

---
 pyproject.toml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index f6d6772..12b3d0f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -90,6 +90,7 @@ select = [
   "S",      # flake8-bandit
   "SIM",    # flake8-simplify
   "T10",    # flake8-debugger
+  "T20",    # flake8-print
   "TCH",    # flake8-type-checking
   "TID",    # flake8-tidy-imports
   "UP",     # pyupgrade
@@ -101,7 +102,6 @@ select = [
   # "FBT",  # flake8-boolean-trap
   # "Q",    # flake8-quotes
   # "SLF",  # flake8-self
-  # "T20",  # flake8-print
   # "TRY",  # tryceratops
 ]
 # Note: use `ruff rule ...` to see explanations of rules
@@ -123,6 +123,7 @@ max-statements = 95
 [tool.ruff.per-file-ignores]
 "docs/*" = [
   "INP001",  # allow stand-alone scripts
+  "T201",  # allow print statements
 ]
 "tests/*" = [
   "D",  # no docstrings necessary here

From 7f168ce3c9f267c3a23a5a0862928d0ad6fd216f Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 01:58:25 +0200
Subject: [PATCH 78/84] Adapt bumpversion config

---
 .bumpversion.cfg | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 7d741e2..0c629a3 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,9 +1,9 @@
 [bumpversion]
 current_version = 3.1.0b1
 
-[bumpversion:file:setup.py]
-search = __version__ = '{current_version}'
-replace = __version__ = '{new_version}'
+[bumpversion:file:pyproject.toml]
+search = version = "{current_version}"
+replace = version = "{new_version}"
 
 [bumpversion:file:dbutils/__init__.py]
 search = __version__ = '{current_version}'

From ddc86dc9f6f4fa3bf1de2b963e2fbd69face4776 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 02:01:56 +0200
Subject: [PATCH 79/84] Move codespell config to pyproject.toml

---
 .codespellrc   | 3 ---
 MANIFEST.in    | 1 -
 pyproject.toml | 4 ++++
 3 files changed, 4 insertions(+), 4 deletions(-)
 delete mode 100644 .codespellrc

diff --git a/.codespellrc b/.codespellrc
deleted file mode 100644
index ab81ffd..0000000
--- a/.codespellrc
+++ /dev/null
@@ -1,3 +0,0 @@
-[codespell]
-skip = .git,.tox,.venv,*.de.html,*.de.rst,build,dist,local
-quiet-level = 2
diff --git a/MANIFEST.in b/MANIFEST.in
index 6408840..835317a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,7 +4,6 @@ include LICENSE
 include README.md
 
 include .bumpversion.cfg
-include .codespellrc
 include pyproject.toml
 include tox.ini
 
diff --git a/pyproject.toml b/pyproject.toml
index 12b3d0f..3426c54 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -130,3 +130,7 @@ max-statements = 95
   "PLR2004",  # allow magic values
   "S101",  # allow assert statements
 ]
+
+[tool.codespell]
+skip = '.git,.tox,.venv,*.de.html,*.de.rst,build,dist,local'
+quiet-level = 2

From 4e2f3f18533d50991bc6d32bb3101dee288dcaed Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 30 Apr 2023 02:16:19 +0200
Subject: [PATCH 80/84] Adapt GitHub action

---
 .github/workflows/publish_on_pypi.yml | 20 +++++++-------------
 .github/workflows/test_with_tox.yml   |  2 +-
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
index cd0df25..7ce0c8b 100644
--- a/.github/workflows/publish_on_pypi.yml
+++ b/.github/workflows/publish_on_pypi.yml
@@ -6,28 +6,22 @@ on:
       - 'Release-*'
 
 jobs:
-  build:
+  publish:
     runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        python: ['3.10']
 
     steps:
     - uses: actions/checkout@v3
 
-    - name: Set up Python ${{ matrix.python }}
+    - name: Set up Python
       uses: actions/setup-python@v4
       with:
-        python-version: ${{ matrix.python }}
+        python-version: "3.10"
 
-    - name: Build source tarball
-      if: matrix.python == 3.10
-      run: python setup.py sdist
+    - name: Install build tool
+      run: python -m pip install build --user
 
-    - name: Build wheel
-      run: |
-        pip install wheel
-        python setup.py bdist_wheel
+    - name: Build source tarball and wheel
+      run: python -m build
 
     - name: Publish distribution to PyPI
       uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index dbc4ace..d829641 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -3,7 +3,7 @@ name: Test DBUtils using tox
 on: [push, pull_request]
 
 jobs:
-  build:
+  test:
     runs-on: ubuntu-latest
     strategy:
       matrix:

From 06a7b9469969e484dd0caa8f207ea03a81119c26 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 17 Mar 2024 13:55:31 +0100
Subject: [PATCH 81/84] Prepare for minor release

---
 .bumpversion.cfg                      |  2 +-
 .github/workflows/publish_on_pypi.yml |  6 ++--
 .github/workflows/test_with_tox.yml   |  6 ++--
 LICENSE                               |  2 +-
 README.md                             |  2 +-
 dbutils/__init__.py                   |  2 +-
 docs/changelog.html                   | 47 ++++++++++++++++-----------
 docs/changelog.rst                    | 11 +++++++
 docs/main.de.html                     |  8 ++---
 docs/main.de.rst                      |  6 ++--
 docs/main.html                        |  8 ++---
 docs/main.rst                         |  6 ++--
 docs/make.py                          |  5 ++-
 pyproject.toml                        | 34 +++++++++++--------
 tests/test_threading_local.py         |  2 +-
 tox.ini                               | 12 +++----
 16 files changed, 92 insertions(+), 67 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 0c629a3..ea56cf5 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 3.1.0b1
+current_version = 3.1.0
 
 [bumpversion:file:pyproject.toml]
 search = version = "{current_version}"
diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
index 7ce0c8b..99cba8a 100644
--- a/.github/workflows/publish_on_pypi.yml
+++ b/.github/workflows/publish_on_pypi.yml
@@ -10,12 +10,12 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
 
     - name: Set up Python
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
-        python-version: "3.10"
+        python-version: "3.11"
 
     - name: Install build tool
       run: python -m pip install build --user
diff --git a/.github/workflows/test_with_tox.yml b/.github/workflows/test_with_tox.yml
index d829641..5a0f068 100644
--- a/.github/workflows/test_with_tox.yml
+++ b/.github/workflows/test_with_tox.yml
@@ -7,10 +7,10 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: ['3.7', '3.8', '3.9', '3.10', '3.11']
+        python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: Setup Python ${{ matrix.python }}
         uses: actions/setup-python@v4
@@ -21,5 +21,5 @@ jobs:
 
       - run: tox -e py
 
-      - if: matrix.python == 3.10
+      - if: matrix.python == 3.11
         run: TOXENV=ruff,manifest,docs,spell tox
diff --git a/LICENSE b/LICENSE
index 15287a3..de120c4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2023 Christoph Zwerschke
+Copyright (c) 2024 Christoph Zwerschke
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 09ccb37..fa3b0ef 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ to a database that can be used in all kinds of multi-threaded environments.
 The suite supports DB-API 2 compliant database interfaces
 and the classic PyGreSQL interface.
 
-The current version 3.0.3 of DBUtils supports Python versions 3.6 to 3.11.
+The current version 3.1.0 of DBUtils supports Python versions 3.7 to 3.12.
 
 **Please have a look at the [changelog](https://webwareforpython.github.io/DBUtils/changelog.html), because there were some breaking changes in version 2.0.**
 
diff --git a/dbutils/__init__.py b/dbutils/__init__.py
index 9008cfc..e503c5d 100644
--- a/dbutils/__init__.py
+++ b/dbutils/__init__.py
@@ -5,4 +5,4 @@
     'simple_pooled_pg', 'steady_pg', 'pooled_pg', 'persistent_pg',
     'simple_pooled_db', 'steady_db', 'pooled_db', 'persistent_db']
 
-__version__ = '3.0.3'
+__version__ = '3.1.0'
diff --git a/docs/changelog.html b/docs/changelog.html
index 1f5ce90..c30f65b 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -2,8 +2,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8" />
+<meta name="generator" content="Docutils 0.20.1: https://docutils.sourceforge.io/" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
 <title>Changelog for DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -12,6 +12,15 @@
 <h1 class="title">Changelog for DBUtils</h1>
 
 <section id="section-1">
+<h2>3.1.0</h2>
+<p>DBUtils 3.1.0 was released on March 17, 2024.</p>
+<p>Changes:</p>
+<ul class="simple">
+<li><p>Support Python version 3.12, cease support for Python 3.6.</p></li>
+<li><p>Various small internal improvements and modernizations.</p></li>
+</ul>
+</section>
+<section id="section-2">
 <h2>3.0.3</h2>
 <p>DBUtils 3.0.3 was released on April 27, 2023.</p>
 <p>Changes:</p>
@@ -21,19 +30,19 @@ <h2>3.0.3</h2>
 <li><p>Minor fixes and section an advanced usage in docs.</p></li>
 </ul>
 </section>
-<section id="section-2">
+<section id="section-3">
 <h2>3.0.2</h2>
 <p>DBUtils 3.0.2 was released on January 14, 2022.</p>
 <p>The optional iterator protocol on cursors is now supported.</p>
 </section>
-<section id="section-3">
+<section id="section-4">
 <h2>3.0.1</h2>
 <p>DBUtils 3.0.1 was released on December 22, 2021.</p>
 <p>It includes <span class="docutils literal">InterfaceError</span> to the default list of exceptions
 for which the connection failover mechanism is applied.
 You can override this with the <span class="docutils literal">failures</span> parameter.</p>
 </section>
-<section id="section-4">
+<section id="section-5">
 <h2>3.0.0</h2>
 <p>DBUtils 3.0.0 was released on November 26, 2021.</p>
 <p>It is intended to be used with Python versions 3.6 to 3.10.</p>
@@ -42,7 +51,7 @@ <h2>3.0.0</h2>
 <li><p>Cease support for Python 2 and 3.5, minor optimizations.</p></li>
 </ul>
 </section>
-<section id="section-5">
+<section id="section-6">
 <h2>2.0.3</h2>
 <p>DBUtils 2.0.3 was released on November 26, 2021.</p>
 <p>Changes:</p>
@@ -50,7 +59,7 @@ <h2>2.0.3</h2>
 <li><p>Support Python version 3.10.</p></li>
 </ul>
 </section>
-<section id="section-6">
+<section id="section-7">
 <h2>2.0.2</h2>
 <p>DBUtils 2.0.2 was released on June 8, 2021.</p>
 <p>Changes:</p>
@@ -58,7 +67,7 @@ <h2>2.0.2</h2>
 <li><p>Allow using context managers for pooled connections.</p></li>
 </ul>
 </section>
-<section id="section-7">
+<section id="section-8">
 <h2>2.0.1</h2>
 <p>DBUtils 2.0.1 was released on April 8, 2021.</p>
 <p>Changes:</p>
@@ -66,7 +75,7 @@ <h2>2.0.1</h2>
 <li><p>Avoid &quot;name Exception is not defined&quot; when exiting.</p></li>
 </ul>
 </section>
-<section id="section-8">
+<section id="section-9">
 <h2>2.0</h2>
 <p>DBUtils 2.0 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -82,7 +91,7 @@ <h2>2.0</h2>
 <li><p>This changelog has been compiled from the former release notes.</p></li>
 </ul>
 </section>
-<section id="section-9">
+<section id="section-10">
 <h2>1.4</h2>
 <p>DBUtils 1.4 was released on September 26, 2020.</p>
 <p>It is intended to be used with Python versions 2.7 and 3.5 to 3.9.</p>
@@ -93,7 +102,7 @@ <h2>1.4</h2>
 inside a transaction.</p></li>
 </ul>
 </section>
-<section id="section-10">
+<section id="section-11">
 <h2>1.3</h2>
 <p>DBUtils 1.3 was released on March 3, 2018.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.4 to 3.7.</p>
@@ -102,12 +111,12 @@ <h2>1.3</h2>
 <li><p>This version now supports context handlers for connections and cursors.</p></li>
 </ul>
 </section>
-<section id="section-11">
+<section id="section-12">
 <h2>1.2</h2>
 <p>DBUtils 1.2 was released on February 5, 2017.</p>
 <p>It is intended to be used with Python versions 2.6, 2.7 and 3.0 to 3.6.</p>
 </section>
-<section id="section-12">
+<section id="section-13">
 <h2>1.1.1</h2>
 <p>DBUtils 1.1.1 was released on February 4, 2017.</p>
 <p>It is intended to be used with Python versions 2.3 to 2.7.</p>
@@ -121,7 +130,7 @@ <h2>1.1.1</h2>
 <li><p>Fixed a problem when running under Jython (reported by Vitaly Kruglikov).</p></li>
 </ul>
 </section>
-<section id="section-13">
+<section id="section-14">
 <h2>1.1</h2>
 <p>DBUtils 1.1 was released on August 14, 2011.</p>
 <p>Improvements:</p>
@@ -150,7 +159,7 @@ <h2>1.1</h2>
 <li><p>Fixed some minor issues with the <span class="docutils literal">DBUtilsExample</span> for Webware.</p></li>
 </ul>
 </section>
-<section id="section-14">
+<section id="section-15">
 <h2>1.0</h2>
 <p>DBUtils 1.0 was released on November 29, 2008.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.6.</p>
@@ -183,7 +192,7 @@ <h2>1.0</h2>
 the MySQLdb module (problem reported by Gregory Pinero).</p></li>
 </ul>
 </section>
-<section id="section-15">
+<section id="section-16">
 <h2>0.9.4</h2>
 <p>DBUtils 0.9.4 was released on July 7, 2007.</p>
 <p>This release fixes a problem in the destructor code and has been supplemented
@@ -192,7 +201,7 @@ <h2>0.9.4</h2>
 in the last release, since you can now pass custom creator functions
 for database connections instead of DB-API 2 modules.</p>
 </section>
-<section id="section-16">
+<section id="section-17">
 <h2>0.9.3</h2>
 <p>DBUtils 0.9.3 was released on May 21, 2007.</p>
 <p>Changes:</p>
@@ -207,7 +216,7 @@ <h2>0.9.3</h2>
 Added Chinese translation of the User's Guide, kindly contributed by gashero.</p></li>
 </ul>
 </section>
-<section id="section-17">
+<section id="section-18">
 <h2>0.9.2</h2>
 <p>DBUtils 0.9.2 was released on September 22, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.5.</p>
@@ -217,7 +226,7 @@ <h2>0.9.2</h2>
 storage engine. Accordingly, renamed <span class="docutils literal">SolidPg</span> to <span class="docutils literal">SteadyPg</span>.</p></li>
 </ul>
 </section>
-<section id="section-18">
+<section id="section-19">
 <h2>0.9.1</h2>
 <p>DBUtils 0.9.1 was released on May 8, 2006.</p>
 <p>It is intended to be used with Python versions 2.2 to 2.4.</p>
@@ -231,7 +240,7 @@ <h2>0.9.1</h2>
 <li><p>Improved the documentation and added a User's Guide.</p></li>
 </ul>
 </section>
-<section id="section-19">
+<section id="section-20">
 <h2>0.8.1 - 2005-09-13</h2>
 <p>DBUtils 0.8.1 was released on September 13, 2005.</p>
 <p>It is intended to be used with Python versions 2.0 to 2.4.</p>
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 6d0833f..796684e 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,17 @@
 Changelog for DBUtils
 +++++++++++++++++++++
 
+3.1.0
+=====
+
+DBUtils 3.1.0 was released on March 17, 2024.
+
+Changes:
+
+* Support Python version 3.12, cease support for Python 3.6.
+* Various small internal improvements and modernizations.
+
+
 3.0.3
 =====
 
diff --git a/docs/main.de.html b/docs/main.de.html
index 1a2dda7..e4f5834 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -2,8 +2,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
 <head>
 <meta charset="utf-8" />
+<meta name="generator" content="Docutils 0.20.1: https://docutils.sourceforge.io/" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
 <title>Benutzeranleitung für DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -12,7 +12,7 @@
 <h1 class="title">Benutzeranleitung für DBUtils</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.3</dd>
+<dd class="version">3.1.0</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p><a class="reference external" href="main.html">English</a> | German</p>
 </dd>
@@ -137,7 +137,7 @@ <h3>Installation</h3>
 </section>
 <section id="anforderungen">
 <h2>Anforderungen</h2>
-<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 3.6 bis 3.11.</p>
+<p>DBUtils unterstützt die <a class="reference external" href="https://www.python.org">Python</a> Versionen 3.7 bis 3.12.</p>
 <p>Die Module in der Variante für klassisches PyGreSQL benötigen <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a>
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
 für DB-API 2 mit jedem beliebigen Python-Datenbankadapter-Modul zusammenarbeiten,
@@ -536,7 +536,7 @@ <h2>Autoren</h2>
 </section>
 <section id="copyright-und-lizenz">
 <h2>Copyright und Lizenz</h2>
-<p>Copyright © 2005-2023 Christoph Zwerschke.
+<p>Copyright © 2005-2024 Christoph Zwerschke.
 Alle Rechte vorbehalten.</p>
 <p>DBUtils ist freie und quelloffene Software,
 lizenziert unter der <a class="reference external" href="https://opensource.org/licenses/MIT">MIT-Lizenz</a>.</p>
diff --git a/docs/main.de.rst b/docs/main.de.rst
index aa800d6..a42853b 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -1,7 +1,7 @@
 Benutzeranleitung für DBUtils
 +++++++++++++++++++++++++++++
 
-:Version: 3.0.3
+:Version: 3.1.0
 :Translations: English_ | German
 
 .. _English: main.html
@@ -98,7 +98,7 @@ herunterzuladen und zu installieren::
 Anforderungen
 =============
 
-DBUtils unterstützt die Python_ Versionen 3.6 bis 3.11.
+DBUtils unterstützt die Python_ Versionen 3.7 bis 3.12.
 
 Die Module in der Variante für klassisches PyGreSQL benötigen PyGreSQL_
 Version 4.0 oder höher, während die Module in der allgemeinen Variante
@@ -583,7 +583,7 @@ Autoren
 Copyright und Lizenz
 ====================
 
-Copyright © 2005-2023 Christoph Zwerschke.
+Copyright © 2005-2024 Christoph Zwerschke.
 Alle Rechte vorbehalten.
 
 DBUtils ist freie und quelloffene Software,
diff --git a/docs/main.html b/docs/main.html
index 50c9ffd..9290bb2 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -2,8 +2,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8" />
+<meta name="generator" content="Docutils 0.20.1: https://docutils.sourceforge.io/" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
-<meta name="generator" content="Docutils 0.19: https://docutils.sourceforge.io/" />
 <title>DBUtils User's Guide</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
 </head>
@@ -12,7 +12,7 @@
 <h1 class="title">DBUtils User's Guide</h1>
 <dl class="docinfo simple">
 <dt class="version">Version<span class="colon">:</span></dt>
-<dd class="version">3.0.3</dd>
+<dd class="version">3.1.0</dd>
 <dt class="translations">Translations<span class="colon">:</span></dt>
 <dd class="translations"><p>English | <a class="reference external" href="main.de.html">German</a></p>
 </dd>
@@ -134,7 +134,7 @@ <h3>Installation</h3>
 </section>
 <section id="requirements">
 <h2>Requirements</h2>
-<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> versions 3.6 to 3.11.</p>
+<p>DBUtils supports <a class="reference external" href="https://www.python.org">Python</a> versions 3.7 to 3.12.</p>
 <p>The modules in the classic PyGreSQL variant need <a class="reference external" href="https://www.pygresql.org/">PyGreSQL</a> version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
 any Python <a class="reference external" href="https://www.python.org/dev/peps/pep-0249/">DB-API 2</a> compliant database interface module.</p>
@@ -494,7 +494,7 @@ <h2>Credits</h2>
 </section>
 <section id="copyright-and-license">
 <h2>Copyright and License</h2>
-<p>Copyright © 2005-2023 by Christoph Zwerschke.
+<p>Copyright © 2005-2024 by Christoph Zwerschke.
 All Rights Reserved.</p>
 <p>DBUtils is free and open source software,
 licensed under the <a class="reference external" href="https://opensource.org/licenses/MIT">MIT license</a>.</p>
diff --git a/docs/main.rst b/docs/main.rst
index fa574b7..e2fdbd9 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -1,7 +1,7 @@
 DBUtils User's Guide
 ++++++++++++++++++++
 
-:Version: 3.0.3
+:Version: 3.1.0
 :Translations: English | German_
 
 .. _German: main.de.html
@@ -95,7 +95,7 @@ It is even easier to download and install the package in one go using `pip`_::
 Requirements
 ============
 
-DBUtils supports Python_ versions 3.6 to 3.11.
+DBUtils supports Python_ versions 3.7 to 3.12.
 
 The modules in the classic PyGreSQL variant need PyGreSQL_ version 4.0
 or above, while the modules in the universal DB-API 2 variant run with
@@ -543,7 +543,7 @@ Credits
 Copyright and License
 =====================
 
-Copyright © 2005-2023 by Christoph Zwerschke.
+Copyright © 2005-2024 by Christoph Zwerschke.
 All Rights Reserved.
 
 DBUtils is free and open source software,
diff --git a/docs/make.py b/docs/make.py
index c071071..a00977b 100755
--- a/docs/make.py
+++ b/docs/make.py
@@ -1,15 +1,14 @@
-#!/usr/bin/python3.10
+#!/usr/bin/python3.11
 
 """Build HTML from reST files."""
 
-from glob import glob
 from pathlib import Path
 
 from docutils.core import publish_file
 
 print("Creating the documentation...")
 
-for rst_file in glob('*.rst'):
+for rst_file in Path().glob('*.rst'):
     rst_path = Path(rst_file)
     name = Path(rst_file).stem
     lang = Path(name).suffix
diff --git a/pyproject.toml b/pyproject.toml
index 3426c54..d4f3aa4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,12 @@
 [build-system]
 build-backend = "setuptools.build_meta"
 requires = [
-  "setuptools>=61.2",
+  "setuptools>=69",
 ]
 
 [project]
 name = "DBUtils"
-version = "3.1.0b1"
+version = "3.1.0"
 description = "Database connections for multi-threaded environments."
 license = {text = "MIT License"}
 authors = [{name = "Christoph Zwerschke", email = "cito@online.de"}]
@@ -24,6 +24,7 @@ classifiers = [
   "Programming Language :: Python :: 3.9",
   "Programming Language :: Python :: 3.10",
   "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: 3.12",
   "Topic :: Database",
   "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
   "Topic :: Software Development :: Libraries :: Python Modules",
@@ -58,19 +59,27 @@ platforms = ["any"]
 include-package-data = false
 
 [tool.ruff]
+line-length = 79
+target-version = "py37"
+
+[tool.ruff.lint]
 select = [
   "A",      # flake8-builtins
+  # "ANN",  # flake8-annotations
   "ARG",    # flake8-unused-arguments
   "B",      # flake8-bugbear
+  # "BLE",  # flake8-blind-except
   "C4",     # flake8-comprehensions
   "C90",    # McCabe cyclomatic complexity
   "COM",    # flake8-commas
   "D",      # pydocstyle
   "DTZ",    # flake8-datetimez
   "E",      # pycodestyle
+  # "EM",   # flake8-errmsg
   "ERA",    # eradicate
   "EXE",    # flake8-executable
   "F",      # Pyflakes
+  # "FBT",  # flake8-boolean-trap
   "G",      # flake8-logging-format
   "I",      # isort
   "ICN",    # flake8-import-conventions
@@ -84,43 +93,40 @@ select = [
   "PT",     # flake8-pytest-style
   "PTH",    # flake8-use-pathlib
   "PYI",    # flake8-pyi
+  # "Q",    # flake8-quotes
   "RET",    # flake8-return
   "RSE",    # flake8-raise
   "RUF",    # Ruff-specific rules
   "S",      # flake8-bandit
+  # "SLF",  # flake8-self
   "SIM",    # flake8-simplify
   "T10",    # flake8-debugger
   "T20",    # flake8-print
   "TCH",    # flake8-type-checking
   "TID",    # flake8-tidy-imports
+  # "TRY",  # tryceratops
   "UP",     # pyupgrade
   "W",      # pycodestyle
   "YTT",    # flake8-2020
-  # "ANN",  # flake8-annotations
-  # "BLE",  # flake8-blind-except
-  # "EM",   # flake8-errmsg
-  # "FBT",  # flake8-boolean-trap
-  # "Q",    # flake8-quotes
-  # "SLF",  # flake8-self
-  # "TRY",  # tryceratops
 ]
 # Note: use `ruff rule ...` to see explanations of rules
 ignore = [
   "D203",  # no blank line before class docstring
   "D213",  # multi-line docstrings should not start at second line
 ]
-line-length = 79
-target-version = "py37"
 
-[tool.ruff.mccabe]
+[tool.ruff.lint.mccabe]
 max-complexity = 30
 
-[tool.ruff.pylint]
+[tool.ruff.lint.flake8-quotes]
+inline-quotes = "double"
+
+[tool.ruff.lint.pylint]
 max-args = 12
 max-branches = 35
 max-statements = 95
 
-[tool.ruff.per-file-ignores]
+[tool.ruff.lint.per-file-ignores]
 "docs/*" = [
   "INP001",  # allow stand-alone scripts
   "T201",  # allow print statements
diff --git a/tests/test_threading_local.py b/tests/test_threading_local.py
index 2c86525..3871e6c 100644
--- a/tests/test_threading_local.py
+++ b/tests/test_threading_local.py
@@ -71,7 +71,7 @@ def f():
     assert not hasattr(my_data, 'color')
 
     class MyLocal(local):
-        __slots__ = 'number'
+        __slots__ = ('number',)
 
     my_data = MyLocal()
     my_data.number = 42
diff --git a/tox.ini b/tox.ini
index 529d75c..62aab9d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py3{7,8,9,10,11}, ruff, manifest, docs, spell
+envlist = py3{7,8,9,10,11,12}, ruff, manifest, docs, spell
 
 [testenv]
 setenv =
@@ -9,25 +9,25 @@ commands =
     pytest {posargs}
 
 [testenv:spell]
-basepython = python3.10
+basepython = python3.11
 deps = codespell
 commands =
     codespell .
 
 [testenv:ruff]
-basepython = python3.10
+basepython = python3.11
 deps = ruff
 commands =
-    ruff .
+    ruff check .
 
 [testenv:manifest]
-basepython = python3.10
+basepython = python3.11
 deps = check-manifest
 commands =
     check-manifest -v
 
 [testenv:docs]
-basepython = python3.10
+basepython = python3.11
 extras = docs
 changedir = docs
 commands =

From 58b9fb4f1d0741929369a0f97e771aa45007baa8 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Sun, 17 Mar 2024 14:07:35 +0100
Subject: [PATCH 82/84] Allow setuptools compatible with Python 3.7

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index d4f3aa4..02a8ff7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
 [build-system]
 build-backend = "setuptools.build_meta"
 requires = [
-  "setuptools>=69",
+  "setuptools>=68",
 ]
 
 [project]

From b01054ba3730af777fbd838a22cc78653c3e2699 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 8 Nov 2024 20:00:23 +0100
Subject: [PATCH 83/84] Make ruff happy

---
 dbutils/pooled_db.py | 9 +++------
 dbutils/pooled_pg.py | 3 +--
 docs/changelog.html  | 2 +-
 docs/main.de.html    | 2 +-
 docs/main.html       | 2 +-
 tests/mock_db.py     | 2 +-
 6 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/dbutils/pooled_db.py b/dbutils/pooled_db.py
index be18c94..ed19740 100644
--- a/dbutils/pooled_db.py
+++ b/dbutils/pooled_db.py
@@ -257,8 +257,7 @@ def __init__(
         if maxconnections is None:
             maxconnections = 0
         if maxcached:
-            if maxcached < mincached:
-                maxcached = mincached
+            maxcached = max(maxcached, mincached)
             self._maxcached = maxcached
         else:
             self._maxcached = 0
@@ -268,10 +267,8 @@ def __init__(
         else:
             self._maxshared = 0
         if maxconnections:
-            if maxconnections < maxcached:
-                maxconnections = maxcached
-            if maxconnections < maxshared:
-                maxconnections = maxshared
+            maxconnections = max(maxconnections, maxcached)
+            maxconnections = max(maxconnections, maxshared)
             self._maxconnections = maxconnections
         else:
             self._maxconnections = 0
diff --git a/dbutils/pooled_pg.py b/dbutils/pooled_pg.py
index eb73708..18585df 100644
--- a/dbutils/pooled_pg.py
+++ b/dbutils/pooled_pg.py
@@ -190,8 +190,7 @@ def __init__(
         if maxcached and maxcached < mincached:
             maxcached = mincached
         if maxconnections:
-            if maxconnections < maxcached:
-                maxconnections = maxcached
+            maxconnections = max(maxconnections, maxcached)
             # Create semaphore for number of allowed connections generally:
             from threading import Semaphore
             self._connections = Semaphore(maxconnections)
diff --git a/docs/changelog.html b/docs/changelog.html
index c30f65b..0707d36 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -2,7 +2,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8" />
-<meta name="generator" content="Docutils 0.20.1: https://docutils.sourceforge.io/" />
+<meta name="generator" content="Docutils 0.21.2: https://docutils.sourceforge.io/" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title>Changelog for DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
diff --git a/docs/main.de.html b/docs/main.de.html
index e4f5834..632c0ff 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -2,7 +2,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
 <head>
 <meta charset="utf-8" />
-<meta name="generator" content="Docutils 0.20.1: https://docutils.sourceforge.io/" />
+<meta name="generator" content="Docutils 0.21.2: https://docutils.sourceforge.io/" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title>Benutzeranleitung für DBUtils</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
diff --git a/docs/main.html b/docs/main.html
index 9290bb2..5650867 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -2,7 +2,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
 <meta charset="utf-8" />
-<meta name="generator" content="Docutils 0.20.1: https://docutils.sourceforge.io/" />
+<meta name="generator" content="Docutils 0.21.2: https://docutils.sourceforge.io/" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title>DBUtils User's Guide</title>
 <link rel="stylesheet" href="doc.css" type="text/css" />
diff --git a/tests/mock_db.py b/tests/mock_db.py
index e3965a6..32ad498 100644
--- a/tests/mock_db.py
+++ b/tests/mock_db.py
@@ -10,7 +10,7 @@
 threadsafety = 2
 
 
-@pytest.fixture()
+@pytest.fixture
 def dbapi():
     """Get mock DB API 2 module."""
     mock_db = sys.modules[__name__]

From c68e3c982a971f904bc69bd955fd1d98586be5b3 Mon Sep 17 00:00:00 2001
From: Christoph Zwerschke <cito@online.de>
Date: Fri, 8 Nov 2024 21:22:35 +0100
Subject: [PATCH 84/84] Improve CSS style for the documentation

---
 dbutils/steady_db.py |   2 +
 docs/doc.css         | 134 ++++++-------------------------------------
 docs/docutils.css    |   4 +-
 docs/main.de.html    |   6 ++
 docs/main.de.rst     |  25 ++++----
 docs/main.html       |   8 ++-
 docs/main.rst        |  22 +++----
 7 files changed, 58 insertions(+), 143 deletions(-)

diff --git a/dbutils/steady_db.py b/dbutils/steady_db.py
index 52e1b60..8b6f11f 100644
--- a/dbutils/steady_db.py
+++ b/dbutils/steady_db.py
@@ -651,6 +651,8 @@ def tough_method(*args, **kwargs):
                             if execute:
                                 self._setsizes(cursor2)
                             method2 = getattr(cursor2, name)
+                            # if the following call hangs,
+                            # you may have forgotten to call begin()
                             result = method2(*args, **kwargs)
                             if execute:
                                 self._clearsizes()
diff --git a/docs/doc.css b/docs/doc.css
index c815371..c647434 100644
--- a/docs/doc.css
+++ b/docs/doc.css
@@ -1,39 +1,13 @@
 /*
-	Webware for Python (https://webwareforpython.github.io/w4py/)
-
-	Common style sheet for Webware's documentation pages
+	Style sheet for DBUtils documentation pages.
 */
 
 /* First import default style for pages created with Docutils: */
 
 @import url(docutils.css);
 
-/* Customization for Webware goes here: */
+/* Customization for DBUtils goes here: */
 
-body {
-	background-color: #FFFFFF;
-	font-family: Verdana, Arial, Helvetica, sans-serif;
-	font-size: 10pt;
-	padding: 12pt;
-}
-table {
-	empty-cells: show;
-	border-collapse: collapse;
-	margin: 0 auto;
-}
-table.doc {
-	border-spacing: 2px;
-	border-collapse: separate;
-	border-style: none;
-}
-td, th {
-	font-family: Verdana, Arial, Helvetica, sans-serif;
-	font-size: 10pt;
-}
-table.doc td, table.doc th {
-	padding: 4px;
-	border-style: none;
-}
 p {
 	margin-top: 6pt;
 	margin-bottom: 6pt;
@@ -43,11 +17,9 @@ li {
 	margin-bottom: 6pt;
 }
 h1, h2 {
-	font-family: Verdana, Arial, Helvetica, sans-serif;
 	color: #002352;
 }
 h3, h4 {
-	font-family: Verdana, Arial, Helvetica, sans-serif;
 	color: #002352;
 }
 h1 {
@@ -59,55 +31,13 @@ h2 {
 h3 {
 	font-size: 14pt;
 }
-h4 {
-	font-size: 12pt;
-}
-h5 {
-	font-size: 11pt;
-}
-h6 {
-	font-size: 10pt;
-}
-h1.titlebar {
-	padding: 4pt;
-	margin-bottom: 12pt;
-	text-align: center;
-	color: white;
-	background-color: #025;
-}
-h1.title, h1.header {
+h1.title {
 	padding: 4pt;
 	margin-bottom: 12pt;
 	text-align: center;
 	border-bottom: 1pt solid #025;
 	padding-bottom: 8pt;
 }
-div.footer {
-	font-family: Tahoma, Arial, Helvetica, sans-serif;
-	font-size: 9pt;
-	text-align: center;
-	padding: 4pt;
-	margin-top: 16pt;
-	border-top: 1pt solid #025;
-}
-.left {
-	text-align: left;
-}
-.right {
-	text-align: right;
-}
-.center {
-	text-align: center;
-}
-.top {
-	vertical-align: top;
-}
-.nowrap {
-	white-space: nowrap;
-}
-.contents {
-	font-family: Tahoma, Arial, Helvetica, sans-serif;
-}
 .contents ul {
 	list-style: none;
 	margin-bottom: 24pt;
@@ -115,63 +45,31 @@ div.footer {
 	margin-left: 2em;
 }
 .contents ul li {
-	font-size: 11pt;
-	margin-bottom: 3pt;
+	font-size: 14pt;
+	margin-bottom: 2pt;
 }
 .contents ul ul {
 	list-style-type: none;
-	margin-top: 2pt;
-	margin-bottom: 2pt;
+	margin-top: 1pt;
+	margin-bottom: 1pt;
 	padding-left: 0em;
 	margin-left: 1.5em;
 }
 .contents ul ul li {
-	font-size: 10pt;
+	font-size: 13pt;
 	margin-bottom: 1pt;
 }
-.contents .topic-title {
-	font-size: 16pt;
-}
-span.name {
-	font-weight: bold;
-}
-span.filename {
-	font-family: Tahoma, Arial, Helvetica, sans-serif;
-	font-size: 9pt;
-}
-code, .literal, .literal-block, .pre, .py {
-	font-family: "Andale Mono", "Lucida Console", Monaco, "Courier New", Courier, monospace;
-	font-size: 10pt;
-	color: #052;
-}
-tt.literal, span.pre {
-	background-color: #FFFFFF;
-}
-pre.py, pre.literal-block {
-	margin: 0;
-	padding: 2pt 1pt 1pt 2pt;
-	background-color: #F0F0F8;
-}
-.typed {
-	font-weight: bold;
-}
-.error {
-	color: red;
+.contents > p.topic-title {
+	font-size: 24pt;
 }
 .warning {
 	color: brown;
 }
-
-/* Configuration documentation: */
-
-dl.config {
-}
-dt.config {
-}
-dd.config {
+.admonition-title {
+	background-color: #F5F5DC;
+	padding: 1pt 3pt;
 }
-span.setting {
-	font-family: Tahoma, Arial, Helvetica, sans-serif;
-	font-size: 9pt;
-	font-weight: bold;
+.admonition-title::before {
+    content: "⚠";
+    margin-right: .5em;
 }
diff --git a/docs/docutils.css b/docs/docutils.css
index bebeb07..50d0088 100644
--- a/docs/docutils.css
+++ b/docs/docutils.css
@@ -1,2 +1,2 @@
-/* CSS 3.1 style sheet for the output of Docutils 0.17 HTML writer. */
-body{margin:0;background-color:#dbdbdb}main,footer,header{line-height:1.3;max-width:50rem;padding:1px 2%;margin:auto}main{counter-reset:table figure;background-color:white}footer,header{font-size:smaller;padding:.5em 2%;border:0}hr.docutils{width:80%;margin-top:1em;margin-bottom:1em;clear:both}p,ol,ul,dl,li,dd,div.line-block,div.topic,table{margin-top:.5em;margin-bottom:.5em}p:first-child{margin-top:0}p:last-child{margin-bottom:0}h1,h2,h3,h4,h5,h6,dl>dd{margin-bottom:.5em}dl>dd,ol>li,dd>ul:only-child,dd>ol:only-child{padding-left:1em}dl.description>dt{font-weight:bold;clear:left;float:left;margin:0;padding:0;padding-right:.5em}dl.field-list.narrow>dd{margin-left:5em}dl.field-list.run-in>dd p{display:block}div.abstract p.topic-title{text-align:center}div.dedication{margin:2em 5em;text-align:center;font-style:italic}div.dedication p.topic-title{font-style:normal}pre.literal-block,pre.doctest-block,pre.math,pre.code{font-family:monospace}blockquote>table,div.topic>table{margin-top:0;margin-bottom:0}blockquote p.attribution,div.topic p.attribution{text-align:right;margin-left:20%}table tr{text-align:left}table.booktabs{border:0;border-top:2px solid;border-bottom:2px solid;border-collapse:collapse}table.booktabs *{border:0}table.booktabs th{border-bottom:thin solid}table.numbered>caption:before{counter-increment:table;content:"Table " counter(table) ": ";font-weight:bold}dl.footnote{padding-left:1ex;border-left:solid;border-left-width:thin}figure.align-left,img.align-left,video.align-left,object.align-left{clear:left;float:left;margin-right:1em}figure.align-right,img.align-right,video.align-right,object.align-right{clear:right;float:right;margin-left:1em}h1,h2,h3,h4,footer,header{clear:both}figure.numbered>figcaption>p:before{counter-increment:figure;content:"Figure " counter(figure) ": ";font-weight:bold}.caution p.admonition-title,.attention p.admonition-title,.danger p.admonition-title,.error p.admonition-title,.warning p.admonition-title,div.error{color:red}aside.sidebar{width:30%;max-width:26em;margin-left:1em;margin-right:-2%;background-color:#ffe}pre.code{padding:.7ex}pre.code,code{background-color:#eee}pre.code .comment,code .comment{color:#5c6576}pre.code .keyword,code .keyword{color:#3b0d06;font-weight:bold}pre.code .literal.string,code .literal.string{color:#0c5404}pre.code .name.builtin,code .name.builtin{color:#352b84}pre.code .deleted,code .deleted{background-color:#deb0a1}pre.code .inserted,code .inserted{background-color:#a3d289}a{text-decoration:none}
\ No newline at end of file
+/* CSS 3 style sheet for the output of Docutils 0.21 HTML writer. */
+div.dedication,nav.contents{padding:0;font-style:italic}h1.title,table tr{text-align:left}.footnote,pre.code,pre.doctest-block,pre.literal-block,pre.math{overflow:auto}body{font-family:Georgia,serif;background-color:#fafaf6;font-size:1.2em;line-height:1.4;margin:auto}main{counter-reset:figure table}footer,header,main{padding:.5em 5%;background-color:#fefef8;max-width:100rem}.citation,.footnote,.topic,div.line-block,dl,h1,h2,h3,h4,h5,h6,li,ol,p,table,ul{margin-top:.5em;margin-bottom:.5em}.topic,blockquote,figure{margin:.5em 2%;padding-left:1em}dl>dd{margin-bottom:.5em}p:first-child{margin-top:0}p:last-child{margin-bottom:0}div.line-block div.line-block,dl.option-list,figure>img,pre.code,pre.doctest-block,pre.literal-block,pre.math{margin-left:calc(2% + 1em)}footer,header{font-size:smaller}h2,h3,h4,p.section-subtitle,p.sidebar-subtitle,p.sidebar-title,p.subtitle,p.topic-title{font-weight:400;font-style:italic;text-align:left}.sectnum{font-style:normal}h1.title{margin-top:2.4em;margin-bottom:2em;font-size:2.4em}h1+p.subtitle{margin-top:-2em;margin-bottom:2em;font-size:2em}section{margin-top:2em}.contents>p.topic-title,h2{font-size:2.2em}h2+p.section-subtitle{font-size:1.6em}h3{font-size:1.2em}h3+p.section-subtitle{font-size:1.1em}figure.marginal>figcaption,h4,p.section-subtitle{font-size:1em}div.dedication{margin-left:0;font-size:1.2em}div.dedication p.topic-title{display:none}.topic p.attribution,blockquote p.attribution{text-align:right}ul.auto-toc>li>p{padding-left:1em;text-indent:-1em}nav.contents ul{padding-left:1em}hr{border:0;border-top:1px solid #ccc;margin:1em 10%}ol,ul{padding-left:1.1em}dd{margin-left:1.5em}dd>dl:first-child,dd>ol:first-child,dd>ul:first-child{clear:left}dl.docinfo>dd,dl.field-list>dd,dl.option-list>dd{margin-left:4em}dl.field-list.narrow>dd{margin-left:3em}dl.field-list.run-in>dd p{display:block}dl.description>dt,dl.docinfo>dt,dl.field-list>dt{font-weight:400;font-style:italic}dl.description>dt{clear:left;float:left;margin:0;padding:0 .5em 0 0}dl.description>dd:after{display:block;content:"";clear:both}.citation-list,.footnote-list{display:contents}.citation{padding-left:1.5em}.citation .label{margin-left:-1.5em}figure{display:flex;flex-wrap:wrap;align-items:flex-start}figure.fullwidth>img,figure>img{margin:0 .5em .5em 0;padding:0}figcaption{font-size:.8em}.fullwidth>figcaption{font-size:inherit}figure.numbered>figcaption>p:before{counter-increment:figure;content:"Figure " counter(figure) ": "}table.booktabs{border-top:2px solid;border-bottom:2px solid}table.booktabs *{border:0}table.booktabs th{border-bottom:thin solid}table.numbered>caption:before{counter-increment:table;content:"Table " counter(table) ": "}.admonition,.system-message{border-style:solid;border-color:silver;border-width:thin;margin:1em 0;padding:.5em}.attention p.admonition-title,.caution p.admonition-title,.danger p.admonition-title,.warning p.admonition-title,div.error{color:maroon}code .comment,pre.code .comment{color:#5c6576}code .keyword,pre.code .keyword{color:#3b0d06;font-weight:700}code .literal.string,pre.code .literal.string{color:#0c5404}code .name.builtin,pre.code .name.builtin{color:#352b84}code .deleted,pre.code .deleted{background-color:#deb0a1}code .inserted,pre.code .inserted{background-color:#a3d289}.sans{font-family:"Gill Sans","Gill Sans MT",Calibri,"Lucida Sans","Noto Sans",sans-serif;letter-spacing:.02em}a{color:inherit}a:link,a:link:hover{text-decoration:underline}.backrefs a:link,.contents a:link,a.citation-reference:link,a.image-reference:link,a.toc-backref:link,a[href^="#system-message"],a[role=doc-backlink]:link,a[role=doc-noteref]:link{text-decoration:none}.contents>p.topic-title,.fullwidth,footer,h1,h2,h3,header,hr.docutils{clear:both}div.align-left,figure.align-left,img.align-left,svg.align-left,table.align-left,video.align-left{margin-left:0;padding-left:0;padding-right:.5em;clear:left;float:left}figure.align-left>img{margin-left:0;padding-left:0}div.align-right,img.align-right,svg.align-right,video.align-right{padding-left:.5em;clear:right;float:right}figure.align-right{clear:right;float:right}figure.align-right>img{justify-self:right;padding:0}table.align-right{margin-right:2.5%}figure.align-center{align-content:center;justify-content:center}figure.align-center>img{padding-left:0;justify-self:center}.admonition.marginal,.marginal,.topic.marginal,aside.sidebar{background-color:#efefea;box-sizing:border-box;margin-left:2%;margin-right:0;padding:.5em;font-size:.8em}aside.sidebar{background-color:inherit}.footnote{font-size:smaller}@media (min-width:35em){footer,header,main{padding:.5em calc(15% - 3rem);line-height:1.6}.admonition.marginal,.marginal,.topic.marginal,aside.sidebar{max-width:45%;float:right;clear:right}dl.docinfo>dd,dl.field-list>dd,dl.option-list>dd{margin-left:6em}}@media (min-width:65em){main,section{display:grid;grid-template-columns:[content] minmax(0,6fr) [margin] 3fr [end];grid-column-gap:calc(3em + 1%)}main>section,section>section{grid-column:1/end}footer,header,main{padding-right:5%}section>figure{display:contents}.citation.align-left,.footnote.align-left,figure>img,main>*,section>*{grid-column:content}.citation.align-left{font-size:1em;padding-left:1.5em}.citation.align-left .label{margin-left:-1.5em}figure>img{margin:.5em 2%;padding-left:1em}.admonition.marginal,.citation,.footnote,.marginal,.topic.marginal,aside.sidebar,figcaption{grid-column:margin;width:auto;max-width:55em;margin:.5em 0;border:none;padding:0;font-size:.8em;text-align:initial;background-color:inherit}.admonition.marginal{padding:.5em}figure.marginal{display:block;margin:.5em 0}.citation,.footnote{padding-left:0}.citation .label,.footnote .label{margin-left:0}.fullwidth,.fullwidth figcaption,.fullwidth img,aside.system-message,div.abstract,div.dedication,dl.docinfo,h1.title,nav.contents,p.subtitle,pre{grid-column:content/end;margin-right:calc(10% - 3rem);max-width:55em}}@media (min-width:100em){footer,header,main{padding-left:30%}main>nav.contents{position:fixed;top:0;left:0;box-sizing:border-box;width:25%;height:100vh;margin:0;background-color:#fafaf6;padding:5.5em 2%;overflow:auto}main>nav.contents>*{padding-left:0}}
\ No newline at end of file
diff --git a/docs/main.de.html b/docs/main.de.html
index 632c0ff..3627cf0 100644
--- a/docs/main.de.html
+++ b/docs/main.de.html
@@ -321,11 +321,14 @@ <h3>PersistentDB (persistent_db)</h3>
 Stattdessen wird die Verbindung automatisch dann geschlossen, wenn der Thread
 endet. Sie können dieses Verhalten ändern, indem Sie den Parameter namens
 <span class="docutils literal">closeable</span> setzen.</p>
+<aside class="admonition warning">
+<p class="admonition-title">Warnung</p>
 <p>Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
 <span class="docutils literal">begin()</span> eingeleitet werden müssen. Hierdurch wird sichergestellt, dass
 das transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, before sie vom
 gleichen Thread erneut benutzt wird.</p>
+</aside>
 <p>Das Holen einer Verbindung kann etwas beschleunigt werden, indem man den
 Parameter <span class="docutils literal">threadlocal</span> auf <span class="docutils literal">threading.local</span> setzt; dies könnte aber in
 einigen Umgebungen nicht funktionieren (es ist zum Beispiel bekannt, dass
@@ -428,12 +431,15 @@ <h3>PooledDB (pooled_db)</h3>
     with db.cursor() as cur:
         cur.execute(...)
         res = cur.fetchone()</pre>
+<aside class="admonition warning">
+<p class="admonition-title">Warnung</p>
 <p>Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
 <span class="docutils literal">begin()</span> eingeleitet werden müssen. Hierdurch wird sichergestellt,
 dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das
 transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
 ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
 wieder an den Verbindungspool zurückgegeben wird.</p>
+</aside>
 </section>
 </section>
 <section id="besonderheiten-bei-der-benutzung">
diff --git a/docs/main.de.rst b/docs/main.de.rst
index a42853b..ce97364 100644
--- a/docs/main.de.rst
+++ b/docs/main.de.rst
@@ -317,11 +317,12 @@ Stattdessen wird die Verbindung automatisch dann geschlossen, wenn der Thread
 endet. Sie können dieses Verhalten ändern, indem Sie den Parameter namens
 ``closeable`` setzen.
 
-Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
-``begin()`` eingeleitet werden müssen. Hierdurch wird sichergestellt, dass
-das transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
-ausgesetzt wird, und dass die Verbindung zurückgerollt wird, before sie vom
-gleichen Thread erneut benutzt wird.
+.. warning::
+    Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
+    ``begin()`` eingeleitet werden müssen. Hierdurch wird sichergestellt, dass
+    das transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
+    ausgesetzt wird, und dass die Verbindung zurückgerollt wird, before sie vom
+    gleichen Thread erneut benutzt wird.
 
 Das Holen einer Verbindung kann etwas beschleunigt werden, indem man den
 Parameter ``threadlocal`` auf ``threading.local`` setzt; dies könnte aber in
@@ -452,13 +453,13 @@ Sie können dies auch durch Verwendung von Kontext-Managern vereinfachen::
           cur.execute(...)
           res = cur.fetchone()
 
-
-Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
-``begin()`` eingeleitet werden müssen. Hierdurch wird sichergestellt,
-dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das
-transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
-ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
-wieder an den Verbindungspool zurückgegeben wird.
+.. warning::
+    Bitte beachten Sie, dass Transaktionen explizit durch Aufruf der Methode
+    ``begin()`` eingeleitet werden müssen. Hierdurch wird sichergestellt,
+    dass die Verbindung nicht mehr mit anderen Threads geteilt wird, dass das
+    transparente Neueröffnen von Verbindungen bis zum Ende der Transaktion
+    ausgesetzt wird, und dass die Verbindung zurückgerollt wird, bevor sie
+    wieder an den Verbindungspool zurückgegeben wird.
 
 
 Besonderheiten bei der Benutzung
diff --git a/docs/main.html b/docs/main.html
index 5650867..dede0dc 100644
--- a/docs/main.html
+++ b/docs/main.html
@@ -298,10 +298,13 @@ <h3>PersistentDB (persistent_db)</h3>
 contrary to the intent of having persistent connections. Instead,
 the connection will be automatically closed when the thread dies.
 You can change this behavior by setting the <span class="docutils literal">closeable</span> parameter.</p>
+<aside class="admonition warning">
+<p class="admonition-title">Warning</p>
 <p>Note that you need to explicitly start transactions by calling the
 <span class="docutils literal">begin()</span> method. This ensures that the transparent reopening will be
 suspended until the end of the transaction, and that the connection
 will be rolled back before being reused by the same thread.</p>
+</aside>
 <p>By setting the <span class="docutils literal">threadlocal</span> parameter to <span class="docutils literal">threading.local</span>, getting
 connections may become a bit faster, but this may not work in all
 environments (for instance, <span class="docutils literal">mod_wsgi</span> is known to cause problems
@@ -374,7 +377,7 @@ <h3>PooledDB (pooled_db)</h3>
 <pre class="literal-block">db = pool.dedicated_connection()</pre>
 <p>If you don't need it anymore, you should immediately return it to the
 pool with <span class="docutils literal">db.close()</span>. You can get another connection in the same way.</p>
-<p><em>Warning:</em> In a threaded environment, never do the following:</p>
+<p>⚠ Warning: In a threaded environment, never do the following:</p>
 <pre class="literal-block">pool.connection().cursor().execute(...)</pre>
 <p>This would release the connection too early for reuse which may be fatal
 if the connections are not thread-safe. Make sure that the connection
@@ -390,11 +393,14 @@ <h3>PooledDB (pooled_db)</h3>
     with db.cursor() as cur:
         cur.execute(...)
         res = cur.fetchone()</pre>
+<aside class="admonition warning">
+<p class="admonition-title">Warning</p>
 <p>Note that you need to explicitly start transactions by calling the
 <span class="docutils literal">begin()</span> method. This ensures that the connection will not be shared
 with other threads, that the transparent reopening will be suspended
 until the end of the transaction, and that the connection will be rolled
 back before being given back to the connection pool.</p>
+</aside>
 </section>
 </section>
 <section id="advanced-usage">
diff --git a/docs/main.rst b/docs/main.rst
index e2fdbd9..816ae80 100644
--- a/docs/main.rst
+++ b/docs/main.rst
@@ -294,10 +294,11 @@ contrary to the intent of having persistent connections. Instead,
 the connection will be automatically closed when the thread dies.
 You can change this behavior by setting the ``closeable`` parameter.
 
-Note that you need to explicitly start transactions by calling the
-``begin()`` method. This ensures that the transparent reopening will be
-suspended until the end of the transaction, and that the connection
-will be rolled back before being reused by the same thread.
+.. warning::
+    Note that you need to explicitly start transactions by calling the
+    ``begin()`` method. This ensures that the transparent reopening will be
+    suspended until the end of the transaction, and that the connection
+    will be rolled back before being reused by the same thread.
 
 By setting the ``threadlocal`` parameter to ``threading.local``, getting
 connections may become a bit faster, but this may not work in all
@@ -392,7 +393,7 @@ Instead of this, you can also get a dedicated connection as follows::
 If you don't need it anymore, you should immediately return it to the
 pool with ``db.close()``. You can get another connection in the same way.
 
-*Warning:* In a threaded environment, never do the following::
+⚠ Warning: In a threaded environment, never do the following::
 
   pool.connection().cursor().execute(...)
 
@@ -414,11 +415,12 @@ You can also use context managers for simpler code::
           cur.execute(...)
           res = cur.fetchone()
 
-Note that you need to explicitly start transactions by calling the
-``begin()`` method. This ensures that the connection will not be shared
-with other threads, that the transparent reopening will be suspended
-until the end of the transaction, and that the connection will be rolled
-back before being given back to the connection pool.
+.. warning::
+    Note that you need to explicitly start transactions by calling the
+    ``begin()`` method. This ensures that the connection will not be shared
+    with other threads, that the transparent reopening will be suspended
+    until the end of the transaction, and that the connection will be rolled
+    back before being given back to the connection pool.
 
 
 Advanced Usage