Skip to content

Commit 2f9d655

Browse files
committed
Support ping() method for steady connections.
1 parent 323d38e commit 2f9d655

4 files changed

Lines changed: 145 additions & 21 deletions

File tree

DBUtils/PersistentDB.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def steady_connection(self):
176176
"""Get a steady, non-persistent DB-API 2 connection."""
177177
return connect(self._creator,
178178
self._maxusage, self._setsession,
179-
self._failures, self._closeable,
179+
self._failures, self._closeable, None,
180180
*self._args, **self._kwargs)
181181

182182
def connection(self, shareable=False):

DBUtils/PooledDB.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,9 @@ def wait():
246246
def steady_connection(self):
247247
"""Get a steady, unpooled DB-API 2 connection."""
248248
return connect(self._creator,
249-
self._maxusage, self._setsession, self._failures, True,
250-
*self._args, **self._kwargs)
249+
self._maxusage, self._setsession,
250+
self._failures, True, None,
251+
*self._args, **self._kwargs)
251252

252253
def connection(self, shareable=True):
253254
"""Get a steady, cached DB-API 2 connection from the pool.

DBUtils/SteadyDB.py

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class InvalidCursor(SteadyDBError):
104104

105105

106106
def connect(creator, maxusage=None, setsession=None, failures=None,
107-
closeable=True, *args, **kwargs):
107+
closeable=True, ping=None, *args, **kwargs):
108108
"""A tough version of the connection constructor of a DB-API 2 module.
109109
110110
creator: either an arbitrary function returning new DB-API 2 compliant
@@ -120,12 +120,14 @@ def connect(creator, maxusage=None, setsession=None, failures=None,
120120
(OperationalError, InternalError) is not adequate
121121
closeable: if this is set to false, then closing the connection will
122122
be silently ignored, but by default the connection can be closed
123+
ping: determines when the connection should be checked with ping()
124+
(0 = default = never, 1 = on cursor(), 2 = on execute(), 3 = both)
123125
args, kwargs: the parameters that shall be passed to the creator
124126
function or the connection constructor of the DB-API 2 module
125127
126128
"""
127129
return SteadyDBConnection(creator, maxusage, setsession, failures,
128-
closeable, *args, **kwargs)
130+
closeable, ping, *args, **kwargs)
129131

130132

131133
class SteadyDBConnection:
@@ -134,7 +136,7 @@ class SteadyDBConnection:
134136
version = __version__
135137

136138
def __init__(self, creator, maxusage=None, setsession=None, failures=None,
137-
closeable=True, *args, **kwargs):
139+
closeable=True, ping=None, *args, **kwargs):
138140
"""Create a "tough" DB-API 2 connection."""
139141
# basic initialization to make finalizer work
140142
self._con = None
@@ -175,6 +177,7 @@ def __init__(self, creator, maxusage=None, setsession=None, failures=None,
175177
raise TypeError("'failures' must be a tuple of exceptions.")
176178
self._failures = failures
177179
self._closeable = closeable
180+
self._ping = ping or 0
178181
self._args, self._kwargs = args, kwargs
179182
self._store(self._create())
180183

@@ -333,6 +336,10 @@ def rollback(self):
333336
"""Rollback pending transaction."""
334337
self._con.rollback()
335338

339+
def ping(self, *args, **kwargs):
340+
"""Ping connection."""
341+
return self._con.ping(*args, **kwargs)
342+
336343
def _cursor(self, *args, **kwargs):
337344
"""A "tough" version of the method cursor()."""
338345
# The args and kwargs are not part of the standard,
@@ -342,7 +349,18 @@ def _cursor(self, *args, **kwargs):
342349
if self._usage >= self._maxusage:
343350
# the connection was used too often
344351
raise self._failure
345-
cursor = self._con.cursor(*args, **kwargs) # try to get a cursor
352+
con = self._con
353+
if self._ping & 1:
354+
try: # if possible, ping the connection
355+
ping = con.ping()
356+
except (AttributeError, TypeError, ValueError):
357+
self._ping = 0 # ping() is not available
358+
ping = None
359+
except Exception:
360+
ping = False
361+
if ping is not None and not ping:
362+
raise self._failure # connection is dead
363+
cursor = con.cursor(*args, **kwargs) # try to get a cursor
346364
except self._failures, error: # error in getting cursor
347365
try: # try to reopen the connection
348366
con2 = self._create()
@@ -436,20 +454,31 @@ def _get_tough_method(self, name):
436454
"""Return a "tough" version of the given cursor method."""
437455
def tough_method(*args, **kwargs):
438456
execute = name.startswith('execute')
457+
con = self._con
439458
try:
440-
if self._con._maxusage:
441-
if self._con._usage >= self._con._maxusage:
459+
if con._maxusage:
460+
if con._usage >= con._maxusage:
442461
# the connection was used too often
443-
raise self._con._failure
462+
raise con._failure
463+
if con._ping & 2:
464+
try: # if possible, ping the connection
465+
ping = con._con.ping()
466+
except (AttributeError, TypeError, ValueError):
467+
self._ping = 0 # ping() is not available
468+
ping = None
469+
except Exception:
470+
ping = False
471+
if ping is not None and not ping:
472+
raise con._failure # connection is dead
444473
if execute:
445474
self._setsizes()
446475
method = getattr(self._cursor, name)
447476
result = method(*args, **kwargs) # try to execute
448477
if execute:
449478
self._clearsizes()
450-
except self._con._failures, error: # execution error
479+
except con._failures, error: # execution error
451480
try:
452-
cursor2 = self._con._cursor(
481+
cursor2 = con._cursor(
453482
*self._args, **self._kwargs) # open new cursor
454483
except Exception:
455484
pass
@@ -466,14 +495,14 @@ def tough_method(*args, **kwargs):
466495
else:
467496
self.close()
468497
self._cursor = cursor2
469-
self._con._usage += 1
498+
con._usage += 1
470499
return result
471500
try:
472501
cursor2.close()
473502
except Exception:
474503
pass
475504
try: # try to reopen the connection
476-
con2 = self._con._create()
505+
con2 = con._create()
477506
except Exception:
478507
pass
479508
else:
@@ -499,10 +528,10 @@ def tough_method(*args, **kwargs):
499528
error = None
500529
if use2:
501530
self.close()
502-
self._con._close()
503-
self._con._store(con2)
531+
con._close()
532+
con._store(con2)
504533
self._cursor = cursor2
505-
self._con._usage += 1
534+
con._usage += 1
506535
if error:
507536
raise error # raise the other error
508537
return result
@@ -516,7 +545,7 @@ def tough_method(*args, **kwargs):
516545
pass
517546
raise error # reraise the original error again
518547
else:
519-
self._con._usage += 1
548+
con._usage += 1
520549
return result
521550
return tough_method
522551

DBUtils/Tests/TestSteadyDB.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def connect(database=None, user=None):
3636

3737
class Connection:
3838

39+
has_ping = False
40+
num_pings = 0
41+
3942
def __init__(self, database=None, user=None):
4043
self.database = database
4144
self.user = user
@@ -45,6 +48,7 @@ def __init__(self, database=None, user=None):
4548
self.open_cursors = 0
4649
self.num_uses = 0
4750
self.num_queries = 0
51+
self.num_pings = 0
4852
self.session = []
4953
self.valid = True
5054

@@ -63,6 +67,14 @@ def commit(self):
6367
def rollback(self):
6468
self.session.append('rollback')
6569

70+
def ping(self):
71+
cls = self.__class__
72+
cls.num_pings += 1
73+
if not cls.has_ping:
74+
raise AttributeError
75+
if not self.valid:
76+
raise OperationalError
77+
6678
def cursor(self, name=None):
6779
if not self.valid:
6880
raise InternalError
@@ -200,12 +212,23 @@ def test01_MockedDBConnection(self):
200212
self.assertRaises(InternalError, cursor.close)
201213
self.assertRaises(InternalError, cursor.execute, 'select test')
202214
self.assert_(db.valid)
215+
self.assert_(not db.__class__.has_ping)
216+
self.assertEqual(db.__class__.num_pings, 0)
217+
self.assertRaises(AttributeError, db.ping)
218+
self.assertEqual(db.__class__.num_pings, 1)
219+
db.__class__.has_ping = True
220+
self.assert_(db.ping() is None)
221+
self.assertEqual(db.__class__.num_pings, 2)
203222
db.close()
204223
self.assert_(not db.valid)
205224
self.assertEqual(db.num_uses, 0)
206225
self.assertEqual(db.num_queries, 0)
207226
self.assertRaises(InternalError, db.close)
208227
self.assertRaises(InternalError, db.cursor)
228+
self.assertRaises(OperationalError, db.ping)
229+
self.assertEqual(db.__class__.num_pings, 3)
230+
db.__class__.has_ping = False
231+
db.__class__.num_pings = 0
209232

210233
def test02_BrokenDBConnection(self):
211234
self.assertRaises(TypeError, SteadyDBConnection, None)
@@ -239,7 +262,7 @@ def test03_SteadyDBClose(self):
239262
self.assert_(not db._con.valid)
240263

241264
def test04_SteadyDBConnection(self):
242-
db = SteadyDBconnect(dbapi, 0, None, None, 1,
265+
db = SteadyDBconnect(dbapi, 0, None, None, True, 0,
243266
'SteadyDBTestDB', user='SteadyDBTestUser')
244267
self.assert_(isinstance(db, SteadyDBConnection))
245268
self.assert_(hasattr(db, '_con'))
@@ -355,9 +378,9 @@ def test04_SteadyDBConnection(self):
355378
['doit', 'commit', 'dont', 'rollback'])
356379

357380
def test05_SteadyDBConnectionCreatorFunction(self):
358-
db1 = SteadyDBconnect(dbapi, 0, None, None, 1,
381+
db1 = SteadyDBconnect(dbapi, 0, None, None, True,
359382
'SteadyDBTestDB', user='SteadyDBTestUser')
360-
db2 = SteadyDBconnect(connect, 0, None, None, 1,
383+
db2 = SteadyDBconnect(connect, 0, None, None, True,
361384
'SteadyDBTestDB', user='SteadyDBTestUser')
362385
self.assertEqual(db1.dbapi(), db2.dbapi())
363386
self.assertEqual(db1.threadsafety(), db2.threadsafety())
@@ -520,6 +543,77 @@ def test10_SteadyDBConnectionSetSizes(self):
520543
result = cursor.fetchone()
521544
self.assertEqual(result, ([6, 42, 7], {None: 7, 3: 15, 9: 42}))
522545

546+
def test11_SteadyDBConnectionPing(self):
547+
Connection.has_ping = False
548+
Connection.num_pings = 0
549+
db = SteadyDBconnect(dbapi)
550+
db.cursor().execute('select test')
551+
self.assertEqual(Connection.num_pings, 0)
552+
db.close()
553+
db.cursor().execute('select test')
554+
self.assertEqual(Connection.num_pings, 0)
555+
db = SteadyDBconnect(dbapi, ping=3)
556+
db.cursor().execute('select test')
557+
self.assertEqual(Connection.num_pings, 1)
558+
db.close()
559+
db.cursor().execute('select test')
560+
self.assertEqual(Connection.num_pings, 1)
561+
Connection.has_ping = True
562+
db = SteadyDBconnect(dbapi)
563+
db.cursor().execute('select test')
564+
self.assertEqual(Connection.num_pings, 1)
565+
db.close()
566+
db.cursor().execute('select test')
567+
self.assertEqual(Connection.num_pings, 1)
568+
db = SteadyDBconnect(dbapi, ping=3)
569+
db.cursor().execute('select test')
570+
self.assertEqual(Connection.num_pings, 3)
571+
db.close()
572+
db.cursor().execute('select test')
573+
self.assertEqual(Connection.num_pings, 5)
574+
db = SteadyDBconnect(dbapi, ping=1)
575+
self.assertEqual(Connection.num_pings, 5)
576+
db.cursor()
577+
self.assertEqual(Connection.num_pings, 6)
578+
db.close()
579+
cursor = db.cursor()
580+
self.assertEqual(Connection.num_pings, 7)
581+
cursor.execute('select test')
582+
self.assertEqual(Connection.num_pings, 7)
583+
db = SteadyDBconnect(dbapi, ping=2)
584+
self.assertEqual(Connection.num_pings, 7)
585+
db.cursor()
586+
self.assertEqual(Connection.num_pings, 7)
587+
db.close()
588+
cursor = db.cursor()
589+
self.assertEqual(Connection.num_pings, 7)
590+
cursor.execute('select test')
591+
self.assertEqual(Connection.num_pings, 8)
592+
db.close()
593+
cursor = db.cursor()
594+
self.assertEqual(Connection.num_pings, 8)
595+
cursor.execute('select test')
596+
self.assertEqual(Connection.num_pings, 9)
597+
db = SteadyDBconnect(dbapi, ping=3)
598+
self.assertEqual(Connection.num_pings, 9)
599+
db.cursor()
600+
self.assertEqual(Connection.num_pings, 10)
601+
db.close()
602+
cursor = db.cursor()
603+
self.assertEqual(Connection.num_pings, 11)
604+
cursor.execute('select test')
605+
self.assertEqual(Connection.num_pings, 12)
606+
db.close()
607+
cursor = db.cursor()
608+
self.assertEqual(Connection.num_pings, 13)
609+
cursor.execute('select test')
610+
self.assertEqual(Connection.num_pings, 14)
611+
db.close()
612+
cursor.execute('select test')
613+
self.assertEqual(Connection.num_pings, 16)
614+
Connection.has_ping = False
615+
Connection.num_pings = 0
616+
523617

524618
if __name__ == '__main__':
525619
unittest.main()

0 commit comments

Comments
 (0)