Skip to content

Commit 8345373

Browse files
committed
Allow overriding defaults via the creator.dbapi and creator.threadsafety attributes. In some cases, finalizers could throw exceptions or create infinite recursion via getattr when init failed; solved by adequate pre-initialization in the init method.
1 parent 2557747 commit 8345373

6 files changed

Lines changed: 155 additions & 39 deletions

File tree

DBUtils/PersistentDB.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,12 @@ def __init__(self, creator,
139139
threadsafety = creator.threadsafety
140140
except AttributeError:
141141
try:
142-
threadsafety = callable(creator.connect) and 0 or 1
142+
if not callable(creator.connect):
143+
raise AttributeError
143144
except AttributeError:
144145
threadsafety = 1
146+
else:
147+
threadsafety = 0
145148
if not threadsafety:
146149
raise NotSupportedError("Database module is not thread-safe.")
147150
self._creator = creator

DBUtils/PooledDB.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,12 @@ def __init__(self, creator,
190190
threadsafety = creator.threadsafety
191191
except AttributeError:
192192
try:
193-
threadsafety = callable(creator.connect) and 0 or 2
193+
if not callable(creator.connect):
194+
raise AttributeError
194195
except AttributeError:
195196
threadsafety = 2
197+
else:
198+
threadsafety = 0
196199
if not threadsafety:
197200
raise NotSupportedError("Database module is not thread-safe.")
198201
self._creator = creator
@@ -368,6 +371,9 @@ def __init__(self, pool, con):
368371
con: the underlying SteadyDB connection
369372
370373
"""
374+
# basic initialization to make finalizer work
375+
self._con = None
376+
# proper initialization of the connection
371377
if not con.threadsafety():
372378
raise NotSupportedError("Database module is not thread-safe.")
373379
self._pool = pool
@@ -431,6 +437,9 @@ def __init__(self, pool, shared_con):
431437
con: the underlying SharedDBConnection
432438
433439
"""
440+
# basic initialization to make finalizer work
441+
self._con = None
442+
# proper initialization of the connection
434443
con = shared_con.con
435444
if not con.threadsafety() > 1:
436445
raise NotSupportedError("Database connection is not thread-safe.")

DBUtils/SteadyDB.py

Lines changed: 116 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@
9393
import sys
9494

9595

96+
class SteadyDBError(Exception):
97+
"""General SteadyDB error."""
98+
99+
class InvalidCursor(SteadyDBError):
100+
"""Database cursor is invalid."""
101+
102+
96103
def connect(creator, maxusage=None, setsession=None, failures=None,
97104
closeable=True, *args, **kwargs):
98105
"""A tough version of the connection constructor of a DB-API 2 module.
@@ -123,22 +130,35 @@ class SteadyDBConnection:
123130

124131
version = __version__
125132

126-
_closed = True
127-
128133
def __init__(self, creator, maxusage=None, setsession=None, failures=None,
129134
closeable=True, *args, **kwargs):
130135
""""Create a "tough" DB-API 2 connection."""
136+
# basic initialization to make finalizer work
137+
self._con = None
138+
self._closed = True
139+
# proper initialization of the connection
131140
try:
132141
self._creator = creator.connect
133142
self._dbapi = creator
134143
except AttributeError:
144+
# try finding the DB-API 2 module via the connection creator
135145
self._creator = creator
136146
try:
137-
self._dbapi = sys.modules[creator.__module__]
138-
if self._dbapi.connect != creator:
139-
raise AttributeError
140-
except (AttributeError, KeyError):
141-
self._dbapi = None
147+
self._dbapi = creator.dbapi
148+
except AttributeError:
149+
try:
150+
self._dbapi = sys.modules[creator.__module__]
151+
if self._dbapi.connect != creator:
152+
raise AttributeError
153+
except (AttributeError, KeyError):
154+
self._dbapi = None
155+
try:
156+
self._threadsafety = creator.threadsafety
157+
except AttributeError:
158+
try:
159+
self._threadsafety = self._dbapi.threadsafety
160+
except AttributeError:
161+
self._threadsafety = None
142162
if not callable(self._creator):
143163
raise TypeError("%r is not a connection provider." % (creator,))
144164
if maxusage is not None and not isinstance(maxusage, (int, long)):
@@ -161,15 +181,74 @@ def _create(self):
161181
if self._dbapi.connect != self._creator:
162182
raise AttributeError
163183
except AttributeError:
184+
# try finding the DB-API 2 module via the connection itself
164185
try:
165-
self._dbapi = sys.modules[con.__module__]
166-
if not callable(self._dbapi.connect):
167-
raise AttributeError
168-
except (AttributeError, KeyError):
169-
raise TypeError("Cannot determine DB-API 2 module.")
186+
mod = con.__module__
187+
except AttributeError:
188+
mod = None
189+
while mod:
190+
try:
191+
self._dbapi = sys.modules[mod]
192+
if not callable(self._dbapi.connect):
193+
raise AttributeError
194+
except (AttributeError, KeyError):
195+
pass
196+
else:
197+
break
198+
i = mod.rfind('.')
199+
if i < 0:
200+
mod = None
201+
else:
202+
mod = mod[:i]
203+
else:
204+
try:
205+
mod = con.OperationalError.__module__
206+
except AttributeError:
207+
mod = None
208+
while mod:
209+
try:
210+
self._dbapi = sys.modules[mod]
211+
if not callable(self._dbapi.connect):
212+
raise AttributeError
213+
except (AttributeError, KeyError):
214+
pass
215+
else:
216+
break
217+
i = mod.rfind('.')
218+
if i < 0:
219+
mod = None
220+
else:
221+
mod = mod[:i]
222+
else:
223+
self._dbapi = None
224+
if self._threadsafety is None:
225+
try:
226+
self._threadsafety = self._dbapi.threadsafety
227+
except AttributeError:
228+
try:
229+
self._threadsafety = con.threadsafety
230+
except AttributeError:
231+
pass
170232
if self._failures is None:
171-
self._failures = (self._dbapi.OperationalError,
172-
self._dbapi.InternalError)
233+
try:
234+
self._failures = (self._dbapi.OperationalError,
235+
self._dbapi.InternalError)
236+
except AttributeError:
237+
try:
238+
self._failures = (self._creator.OperationalError,
239+
self._creator.InternalError)
240+
except AttributeError:
241+
try:
242+
self._failures = (con.OperationalError,
243+
con.InternalError)
244+
except AttributeError:
245+
raise AttributeError(
246+
"Could not determine failure exceptions"
247+
" (please set failures or creator.dbapi).")
248+
if isinstance(self._failures, tuple):
249+
self._failure = self._failures[0]
250+
else:
251+
self._failure = self._failures
173252
self._setsession(con)
174253
except Exception, error:
175254
# the database module could not be determined
@@ -213,14 +292,19 @@ def _close(self):
213292

214293
def dbapi(self):
215294
"""Return the underlying DB-API 2 module of the connection."""
295+
if self._dbapi is None:
296+
raise AttributeError("Could not determine DB-API 2 module"
297+
" (please set creator.dbapi).")
216298
return self._dbapi
217299

218300
def threadsafety(self):
219301
"""Return the thread safety level of the connection."""
220-
try:
221-
return self._dbapi.threadsafety
222-
except AttributeError:
302+
if self._threadsafety is None:
303+
if self._dbapi is None:
304+
raise AttributeError("Could not determine threadsafety"
305+
" (please set creator.dbapi or creator.threadsafety).")
223306
return 0
307+
return self._threadsafety
224308

225309
def close(self):
226310
"""Close the tough connection.
@@ -290,10 +374,12 @@ def __del__(self):
290374
class SteadyDBCursor:
291375
"""A "tough" version of DB-API 2 cursors."""
292376

293-
_closed = True
294-
295377
def __init__(self, con, *args, **kwargs):
296378
""""Create a "tough" DB-API 2 cursor."""
379+
# basic initialization to make finalizer work
380+
self._cursor = None
381+
self._closed = True
382+
# proper initialization of the cursor
297383
self._con = con
298384
self._args, self._kwargs = args, kwargs
299385
self._clearsizes()
@@ -424,12 +510,18 @@ def tough_method(*args, **kwargs):
424510

425511
def __getattr__(self, name):
426512
"""Inherit methods and attributes of underlying cursor."""
427-
if name.startswith('execute') or name.startswith('call'):
428-
# make execution methods "tough"
429-
return self._get_tough_method(name)
513+
if self._cursor:
514+
if name.startswith('execute') or name.startswith('call'):
515+
# make execution methods "tough"
516+
return self._get_tough_method(name)
517+
else:
518+
return getattr(self._cursor, name)
430519
else:
431-
return getattr(self._cursor, name)
520+
raise InvalidCursor
432521

433522
def __del__(self):
434523
"""Delete the steady cursor."""
435-
self.close() # make sure the cursor is closed
524+
try:
525+
self.close() # make sure the cursor is closed
526+
except Exception:
527+
pass

DBUtils/SteadyPg.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@
7474
from pg import DB as PgConnection
7575

7676

77+
class SteadyPgError(Exception):
78+
"""General SteadyPg error."""
79+
80+
class InvalidConnection(SteadyPgError):
81+
"""Database connection is invalid."""
82+
83+
7784
class SteadyPgConnection:
7885
"""Class representing steady connections to a PostgreSQL database.
7986
@@ -89,8 +96,6 @@ class SteadyPgConnection:
8996

9097
version = __version__
9198

92-
_closed = True
93-
9499
def __init__(self, maxusage=None, setsession=None, closeable=True,
95100
*args, **kwargs):
96101
"""Create a "tough" PostgreSQL connection.
@@ -106,6 +111,10 @@ def __init__(self, maxusage=None, setsession=None, closeable=True,
106111
the PostgreSQL connections with PyGreSQL using pg.DB()
107112
108113
"""
114+
# basic initialization to make finalizer work
115+
self._con = None
116+
self._closed = True
117+
# proper initialization of the connection
109118
if maxusage is not None and not isinstance(maxusage, (int, long)):
110119
raise TypeError("'maxusage' must be an integer value.")
111120
self._maxusage = maxusage
@@ -214,11 +223,14 @@ def __getattr__(self, name):
214223
Some methods are made "tougher" than in the standard version.
215224
216225
"""
217-
attr = getattr(self._con, name)
218-
if name in ('query', 'get', 'insert', 'update', 'delete') \
219-
or name.startswith('get_'):
220-
attr = self._get_tough_method(attr)
221-
return attr
226+
if self._con:
227+
attr = getattr(self._con, name)
228+
if (name in ('query', 'get', 'insert', 'update', 'delete')
229+
or name.startswith('get_')):
230+
attr = self._get_tough_method(attr)
231+
return attr
232+
else:
233+
raise InvalidConnection
222234

223235
def __del__(self):
224236
"""Delete the steady connection."""

DBUtils/Testing/TestSteadyDB.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ class Connection:
3939
def __init__(self, database=None, user=None):
4040
self.database = database
4141
self.user = user
42+
self.valid = False
43+
if database == 'error':
44+
raise OperationalError
4245
self.open_cursors = 0
4346
self.num_uses = 0
4447
self.num_queries = 0
4548
self.session = []
46-
if database == 'error':
47-
self.valid = False
48-
raise OperationalError
4949
self.valid = True
5050

5151
def close(self):
@@ -73,10 +73,10 @@ class Cursor:
7373

7474
def __init__(self, con, name=None):
7575
self.con = con
76-
self.result = None
76+
self.valid = False
7777
if name == 'error':
78-
self.valid = False
7978
raise OperationalError
79+
self.result = None
8080
con.open_cursors += 1
8181
self.valid = True
8282

DBUtils/Testing/TestSteadyPg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def __getattr__(self, name):
8787
if self.db:
8888
return getattr(self.db, name)
8989
else:
90-
raise InternalError
90+
raise AttributeError
9191

9292
def close(self):
9393
if self.db:

0 commit comments

Comments
 (0)