Skip to content

Commit

Permalink
Graceful exit of DB destructor on closed connection
Browse files Browse the repository at this point in the history
Also, in the 5.1 branch, the DB wrapper can now be closed
(without closing the underlying connection) and reopened
(reusing the same connection).

This fixed GitHub issue #11
  • Loading branch information
Cito committed Jan 5, 2019
1 parent dc4a990 commit 5a16a57
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 20 deletions.
5 changes: 4 additions & 1 deletion docs/contents/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ ChangeLog

Version 5.1 (2019-mm-dd)
------------------------
- ...
- DB wrapper objects based on existing connections can not be closed and
reopened properly (but the underlying connection will not be affected).

Vesion 5.0.7 (2019-mm-dd)
-------------------------
- This version officially supports the new PostgreSQL 11.
- Fixed a bug in parsing array subscript ranges (reported by Justin Pryzby).
- Fixed an issue when deleting a DB wrapper object with the underlying
connection already closed (bug report by Jacob Champion).

Vesion 5.0.6 (2018-07-29)
-------------------------
Expand Down
36 changes: 25 additions & 11 deletions pg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1502,16 +1502,17 @@ def __init__(self, *args, **kw):
pass
if not db or not hasattr(db, 'db') or not hasattr(db, 'query'):
db = connect(*args, **kw)
self._db_args = args, kw
self._closeable = True
else:
self._db_args = db
self._closeable = False
self.db = db
self.dbname = db.db
self._regtypes = False
self._attnames = {}
self._pkeys = {}
self._privileges = {}
self._args = args, kw
self.adapter = Adapter(self)
self.dbtypes = DbTypes(self)
if db.server_version < 80400:
Expand Down Expand Up @@ -1572,9 +1573,15 @@ def __del__(self):
except AttributeError:
db = None
if db:
db.set_cast_hook(None)
try:
db.set_cast_hook(None)
except TypeError:
pass # probably already closed
if self._closeable:
db.close()
try:
db.close()
except InternalError:
pass # probably already closed

# Auxiliary methods

Expand Down Expand Up @@ -1629,13 +1636,17 @@ def encode_json(self, d):
def close(self):
"""Close the database connection."""
# Wraps shared library function so we can track state.
if self._closeable:
if self.db:
self.db.set_cast_hook(None)
self.db.close()
self.db = None
else:
raise _int_error('Connection already closed')
db = self.db
if db:
try:
db.set_cast_hook(None)
except TypeError:
pass # probably already closed
if self._closeable:
db.close()
self.db = None
else:
raise _int_error('Connection already closed')

def reset(self):
"""Reset connection with current parameters.
Expand All @@ -1658,11 +1669,14 @@ def reopen(self):
"""
# There is no such shared library function.
if self._closeable:
db = connect(*self._args[0], **self._args[1])
db = connect(*self._db_args[0], **self._db_args[1])
if self.db:
self.db.set_cast_hook(None)
self.db.close()
db.set_cast_hook(self.dbtypes.typecast)
self.db = db
else:
self.db = self._db_args

def begin(self, mode=None):
"""Begin a transaction."""
Expand Down
32 changes: 24 additions & 8 deletions tests/test_classic_dbwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,26 +375,42 @@ def testMethodReopen(self):

def testExistingConnection(self):
db = pg.DB(self.db.db)
self.assertIsNotNone(db.db)
self.assertEqual(self.db.db, db.db)
self.assertTrue(db.db)
db.close()
self.assertTrue(db.db)
self.assertIsNone(db.db)
self.assertIsNotNone(self.db.db)
db.reopen()
self.assertTrue(db.db)
self.assertIsNotNone(db.db)
self.assertEqual(self.db.db, db.db)
db.close()
self.assertTrue(db.db)
self.assertIsNone(db.db)
db = pg.DB(self.db)
self.assertEqual(self.db.db, db.db)
db = pg.DB(db=self.db.db)
self.assertEqual(self.db.db, db.db)

class DB2:
pass
def testExistingDbApi2Connection(self):

class DBApi2Con:

def __init__(self, cnx):
self._cnx = cnx

def close(self):
self._cnx.close()

db2 = DB2()
db2._cnx = self.db.db
db2 = DBApi2Con(self.db.db)
db = pg.DB(db2)
self.assertEqual(self.db.db, db.db)
db.close()
self.assertIsNone(db.db)
db.reopen()
self.assertIsNotNone(db.db)
self.assertEqual(self.db.db, db.db)
db.close()
self.assertIsNone(db.db)
db2.close()


class TestDBClass(unittest.TestCase):
Expand Down

0 comments on commit 5a16a57

Please sign in to comment.