Skip to content

Commit 08e71ca

Browse files
committed
The __del__() methods of steady connections and cursors not only were of no use, they could even result in infinite lookup recursions if the initialization had failed! Now, they really try to close the objects in question and check whether the objects are really opened. Also, it is now avoided to close things multiple times, and some more small things were improved in this context.
1 parent fd1a0cb commit 08e71ca

5 files changed

Lines changed: 122 additions & 60 deletions

File tree

DBUtils/SteadyDB.py

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def connect(creator, maxusage=0, setsession=None, *args, **kwargs):
114114
class SteadyDBConnection:
115115
"""A "tough" version of DB-API 2 connections."""
116116

117+
_closed = True
118+
117119
def __init__(self, creator, maxusage=0, setsession=None, *args, **kwargs):
118120
""""Create a "tough" DB-API 2 connection."""
119121
try:
@@ -133,13 +135,31 @@ def __init__(self, creator, maxusage=0, setsession=None, *args, **kwargs):
133135
self._setsession_sql = setsession
134136
self._args, self._kwargs = args, kwargs
135137
self._closeable = 1
136-
self._usage = 0
137138
self._store(self._create())
138139

139140
def _create(self):
140141
"""Create a new connection using the creator function."""
141142
con = self._creator(*self._args, **self._kwargs)
142-
self._setsession(con)
143+
try:
144+
try:
145+
if self._dbapi.connect != self._creator:
146+
raise AttributeError
147+
except AttributeError:
148+
try:
149+
self._dbapi = sys.modules[con.__module__]
150+
if not callable(self._dbapi.connect):
151+
raise AttributeError
152+
except (AttributeError, KeyError):
153+
raise TypeError("Cannot determine DB-API 2 module.")
154+
self._setsession(con)
155+
except Exception, error:
156+
# the database module could not be determined
157+
# or the session could not be prepared
158+
try: # close the connection first
159+
con.close()
160+
except Exception:
161+
pass
162+
raise error # reraise the original error again
143163
return con
144164

145165
def _setsession(self, con=None):
@@ -155,16 +175,8 @@ def _setsession(self, con=None):
155175
def _store(self, con):
156176
"""Store a database connection for subsequent use."""
157177
self._con = con
158-
try:
159-
if self._dbapi.connect != self._creator:
160-
raise AttributeError
161-
except AttributeError:
162-
try:
163-
self._dbapi = sys.modules[con.__module__]
164-
if not callable(self._dbapi.connect):
165-
raise AttributeError
166-
except (AttributeError, KeyError):
167-
raise TypeError("Cannot determine DB-API 2 module.")
178+
self._closed = False
179+
self._usage = 0
168180

169181
def _close(self):
170182
"""Close the tough connection.
@@ -173,11 +185,12 @@ def _close(self):
173185
and it will not complain if you close it more than once.
174186
175187
"""
176-
try:
177-
self._con.close()
178-
except Exception:
179-
pass
180-
self._usage = 0
188+
if not self._closed:
189+
try:
190+
self._con.close()
191+
except Exception:
192+
pass
193+
self._closed = True
181194

182195
def dbapi(self):
183196
"""Return the underlying DB-API 2 module of the connection."""
@@ -223,7 +236,7 @@ def _cursor(self, *args, **kwargs):
223236
raise self._dbapi.OperationalError
224237
cursor = self._con.cursor(*args, **kwargs) # try to get a cursor
225238
except (self._dbapi.OperationalError,
226-
self._dbapi.InternalError): # error in getting cursor
239+
self._dbapi.InternalError), error: # error in getting cursor
227240
try: # try to reopen the connection
228241
con2 = self._create()
229242
except Exception:
@@ -241,7 +254,7 @@ def _cursor(self, *args, **kwargs):
241254
con2.close()
242255
except Exception:
243256
pass
244-
raise # raise the original error again
257+
raise error # reraise the original error again
245258
return cursor
246259

247260
def cursor(self, *args, **kwargs):
@@ -250,19 +263,21 @@ def cursor(self, *args, **kwargs):
250263

251264
def __del__(self):
252265
"""Delete the steady connection."""
253-
if hasattr(self, '_con'):
254-
del self._con
266+
self._close() # make sure the connection is closed
255267

256268

257269
class SteadyDBCursor:
258270
"""A "tough" version of DB-API 2 cursors."""
259271

272+
_closed = True
273+
260274
def __init__(self, con, *args, **kwargs):
261275
""""Create a "tough" DB-API 2 cursor."""
262276
self._con = con
263277
self._args, self._kwargs = args, kwargs
264278
self._clearsizes()
265279
self._cursor = con._cursor(*args, **kwargs)
280+
self._closed = False
266281

267282
def setinputsizes(self, sizes):
268283
"""Store input sizes in case cursor needs to be reopened."""
@@ -298,10 +313,12 @@ def close(self):
298313
It will not complain if you close it more than once.
299314
300315
"""
301-
try:
302-
self._cursor.close()
303-
except Exception:
304-
pass
316+
if not self._closed:
317+
try:
318+
self._cursor.close()
319+
except Exception:
320+
pass
321+
self._closed = True
305322

306323
def _get_tough_method(self, name):
307324
"""Return a "tough" version of the method."""
@@ -319,7 +336,7 @@ def tough_method(*args, **kwargs):
319336
if execute:
320337
self._clearsizes()
321338
except (self._con._dbapi.OperationalError,
322-
self._con._dbapi.InternalError): # execution error
339+
self._con._dbapi.InternalError), error: # execution error
323340
try:
324341
cursor2 = self._con._cursor(
325342
*self._args, **self._kwargs) # open new cursor
@@ -379,7 +396,7 @@ def tough_method(*args, **kwargs):
379396
con2.close()
380397
except Exception:
381398
pass
382-
raise # raise the original error again
399+
raise error # reraise the original error again
383400
else:
384401
self._con._usage += 1
385402
return result
@@ -395,5 +412,4 @@ def __getattr__(self, name):
395412

396413
def __del__(self):
397414
"""Delete the steady cursor."""
398-
if hasattr(self, '_cursor'):
399-
del self._cursor
415+
self.close() # make sure the cursor is closed

DBUtils/SteadyPg.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class SteadyPgConnection:
8787
8888
"""
8989

90+
_closed = True
91+
9092
def __init__(self, maxusage=0, setsession=None, *args, **kwargs):
9193
"""Create a "tough" PostgreSQL connection.
9294
@@ -102,9 +104,10 @@ def __init__(self, maxusage=0, setsession=None, *args, **kwargs):
102104
self._maxusage = maxusage
103105
self._setsession_sql = setsession
104106
self._closeable = 1
105-
self._usage = 0
106107
self._con = PgConnection(*args, **kwargs)
108+
self._closed = False
107109
self._setsession()
110+
self._usage = 0
108111

109112
def _setsession(self):
110113
"""Execute the SQL commands for session preparation."""
@@ -119,11 +122,12 @@ def _close(self):
119122
and it will not complain if you close it more than once.
120123
121124
"""
122-
try:
123-
self._con.close()
124-
self._usage = 0
125-
except Exception:
126-
pass
125+
if not self._closed:
126+
try:
127+
self._con.close()
128+
except Exception:
129+
pass
130+
self._closed = True
127131

128132
def close(self):
129133
"""Close the tough connection.
@@ -142,13 +146,17 @@ def close(self):
142146
def reopen(self):
143147
"""Reopen the tough connection.
144148
145-
It will not complain if the connection cannot be reopened."""
149+
It will not complain if the connection cannot be reopened.
150+
151+
"""
146152
try:
147153
self._con.reopen()
148-
self._setsession()
149-
self._usage = 0
150154
except Exception:
151155
pass
156+
else:
157+
self._closed = False
158+
self._setsession()
159+
self._usage = 0
152160

153161
def reset(self):
154162
"""Reset the tough connection.
@@ -207,5 +215,4 @@ def __getattr__(self, name):
207215

208216
def __del__(self):
209217
"""Delete the steady connection."""
210-
if hasattr(self, '_con'):
211-
del self._con
218+
self._close() # make sure the connection is closed

DBUtils/Testing/TestPersistentDB.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def runQueries(i):
138138
except TypeError:
139139
queryQueue[1].put('close', 1)
140140
r = resultQueue[1].get(1)
141-
self.assertEqual(r, '1(0): ok - connection closed')
141+
self.assertEqual(r, '1(3): ok - connection closed')
142142
for j in range(2):
143143
try:
144144
queryQueue[1].put('select test%d' % j, 1, 0.1)

DBUtils/Testing/TestSteadyDB.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525

2626
class Error(StandardError): pass
2727
class DatabaseError(Error): pass
28-
class InternalError(DatabaseError): pass
2928
class OperationalError(DatabaseError): pass
29+
class InternalError(DatabaseError): pass
30+
class ProgrammingError(DatabaseError): pass
3031

3132
def connect(database=None, user=None):
3233
return Connection(database, user)
@@ -40,6 +41,9 @@ def __init__(self, database=None, user=None):
4041
self.num_uses = 0
4142
self.num_queries = 0
4243
self.session = []
44+
if database == 'error':
45+
self.valid = 0
46+
raise OperationalError
4347
self.valid = 1
4448

4549
def close(self):
@@ -57,18 +61,21 @@ def commit(self):
5761
def rollback(self):
5862
self.session.append('rollback')
5963

60-
def cursor(self):
64+
def cursor(self, name=None):
6165
if not self.valid:
6266
raise InternalError
63-
return Cursor(self)
67+
return Cursor(self, name)
6468

6569
class Cursor:
6670

67-
def __init__(self, con):
71+
def __init__(self, con, name=None):
6872
self.con = con
73+
self.result = None
74+
if name == 'error':
75+
self.valid = 0
76+
raise OperationalError
6977
con.open_cursors += 1
7078
self.valid = 1
71-
self.result = None
7279

7380
def close(self):
7481
if not self.valid:
@@ -77,7 +84,7 @@ def close(self):
7784
self.valid = 0
7885

7986
def execute(self, operation):
80-
if not self.valid:
87+
if not self.valid or not self.con.valid:
8188
raise InternalError
8289
self.con.num_uses += 1
8390
if operation.startswith('select '):
@@ -87,15 +94,17 @@ def execute(self, operation):
8794
self.con.session.append(operation[4:])
8895
self.result = None
8996
else:
90-
raise DatabaseError
97+
raise ProgrammingError
9198

9299
def fetchone(self):
100+
if not self.valid:
101+
raise InternalError
93102
result = self.result
94103
self.result = None
95104
return result
96105

97106
def callproc(self, procname):
98-
if not self.valid:
107+
if not self.valid or not self.con.valid:
99108
raise InternalError
100109
self.con.num_uses += 1
101110

@@ -175,7 +184,23 @@ def test1_MockedDBConnection(self):
175184
self.assertRaises(InternalError, db.close)
176185
self.assertRaises(InternalError, db.cursor)
177186

178-
def test2_SteadyDBClose(self):
187+
def test2_BrokenDBConnection(self):
188+
db = SteadyDBconnect(dbapi, database='ok')
189+
for i in range(3):
190+
db.close()
191+
del db
192+
self.assertRaises(OperationalError, SteadyDBconnect,
193+
dbapi, database='error')
194+
db = SteadyDBconnect(dbapi, database='ok')
195+
cursor = db.cursor()
196+
for i in range(3):
197+
cursor.close()
198+
cursor = db.cursor('ok')
199+
for i in range(3):
200+
cursor.close()
201+
self.assertRaises(OperationalError, db.cursor, 'error')
202+
203+
def test3_SteadyDBClose(self):
179204
for closeable in (0, 1):
180205
db = SteadyDBconnect(dbapi)
181206
db._closeable = closeable
@@ -189,7 +214,7 @@ def test2_SteadyDBClose(self):
189214
db._close()
190215
self.assert_(not db._con.valid)
191216

192-
def test3_SteadyDBConnection(self):
217+
def test4_SteadyDBConnection(self):
193218
db = SteadyDBconnect(dbapi, 0, None,
194219
'SteadyDBTestDB', user='SteadyDBTestUser')
195220
self.assert_(hasattr(db, '_con'))
@@ -257,7 +282,7 @@ def test3_SteadyDBConnection(self):
257282
db.close()
258283
self.assert_(not db._con.valid)
259284
self.assertEqual(db._con.open_cursors, 0)
260-
self.assertEqual(db._usage, 0)
285+
self.assertEqual(db._usage, 8)
261286
self.assertEqual(db._con.num_uses, 0)
262287
self.assertEqual(db._con.num_queries, 0)
263288
self.assertRaises(InternalError, db._con.close)
@@ -304,7 +329,7 @@ def test3_SteadyDBConnection(self):
304329
self.assertEqual(db._con.session,
305330
['doit', 'commit', 'dont', 'rollback'])
306331

307-
def test4_SteadyDBConnectionCreatorFunction(self):
332+
def test5_SteadyDBConnectionCreatorFunction(self):
308333
db1 = SteadyDBconnect(dbapi, 0, None,
309334
'SteadyDBTestDB', user='SteadyDBTestUser')
310335
db2 = SteadyDBconnect(connect, 0, None,
@@ -317,7 +342,7 @@ def test4_SteadyDBConnectionCreatorFunction(self):
317342
db2.close()
318343
db1.close()
319344

320-
def test5_SteadyDBConnectionMaxUsage(self):
345+
def test6_SteadyDBConnectionMaxUsage(self):
321346
db = SteadyDBconnect(dbapi, 10)
322347
cursor = db.cursor()
323348
for i in range(100):
@@ -363,7 +388,7 @@ def test5_SteadyDBConnectionMaxUsage(self):
363388
self.assertEqual(db._con.num_uses, 1)
364389
self.assertEqual(db._con.num_queries, 1)
365390

366-
def test6_SteadyDBConnectionSetSession(self):
391+
def test7_SteadyDBConnectionSetSession(self):
367392
db = SteadyDBconnect(dbapi, 3, ('set time zone', 'set datestyle'))
368393
self.assert_(hasattr(db, '_usage'))
369394
self.assertEqual(db._usage, 0)

0 commit comments

Comments
 (0)