Aestate
steady_db.py
Go to the documentation of this file.
1 import sys
2 
3 from . import __version__
4 
5 baseint = int
6 
7 
8 class SteadyDBError(Exception):
9  """General SteadyDB error."""
10 
11 
13  """Database cursor is invalid."""
14 
15 
16 def connect(
17  creator, maxusage=None, setsession=None,
18  failures=None, ping=1, closeable=True, *args, **kwargs):
19  """A tough version of the connection constructor of a DB-API 2 module.
20 
21  creator: either an arbitrary function returning new DB-API 2 compliant
22  connection objects or a DB-API 2 compliant database module
23  maxusage: maximum usage limit for the underlying DB-API 2 connection
24  (number of database operations, 0 or None means unlimited usage)
25  callproc(), execute() and executemany() count as one operation.
26  When the limit is reached, the connection is automatically reset.
27  setsession: an optional list of SQL commands that may serve to prepare
28  the session, e.g. ["set datestyle to german", "set time zone mez"]
29  failures: an optional exception class or a tuple of exception classes
30  for which the failover mechanism shall be applied, if the default
31  (OperationalError, InternalError) is not adequate
32  ping: determines when the connection should be checked with ping()
33  (0 = None = never, 1 = default = when _ping_check() is called,
34  2 = whenever a cursor is created, 4 = when a query is executed,
35  7 = always, and all other bit combinations of these values)
36  closeable: if this is set to false, then closing the connection will
37  be silently ignored, but by default the connection can be closed
38  args, kwargs: the parameters that shall be passed to the creator
39  function or the connection constructor of the DB-API 2 module
40  """
41  return SteadyDBConnection(
42  creator, maxusage, setsession,
43  failures, ping, closeable, *args, **kwargs)
44 
45 
47  """A "tough" version of DB-API 2 connections."""
48 
49  version = __version__
50 
51  def __init__(
52  self, creator, maxusage=None, setsession=None,
53  failures=None, ping=1, closeable=True, *args, **kwargs):
54  """Create a "tough" DB-API 2 connection."""
55  # basic initialization to make finalizer work
56  self._con = None
57  self._closed = True
58  # proper initialization of the connection
59  try:
60  self._creator = creator.connect
61  self._dbapi = creator
62  except AttributeError:
63  # try finding the DB-API 2 module via the connection creator
64  self._creator = creator
65  try:
66  self._dbapi = creator.dbapi
67  except AttributeError:
68  try:
69  self._dbapi = sys.modules[creator.__module__]
70  if self._dbapi.connect != creator:
71  raise AttributeError
72  except (AttributeError, KeyError):
73  self._dbapi = None
74  try:
75  self._threadsafety = creator.threadsafety
76  except AttributeError:
77  try:
78  self._threadsafety = self._dbapi.threadsafety
79  except AttributeError:
80  self._threadsafety = None
81  if not callable(self._creator):
82  raise TypeError("%r is not a connection provider." % (creator,))
83  if maxusage is None:
84  maxusage = 0
85  if not isinstance(maxusage, baseint):
86  raise TypeError("'maxusage' must be an integer value.")
87  self._maxusage = maxusage
88  self._setsession_sql = setsession
89  if failures is not None and not isinstance(
90  failures, tuple) and not issubclass(failures, Exception):
91  raise TypeError("'failures' must be a tuple of exceptions.")
92  self._failures = failures
93  self._ping = ping if isinstance(ping, int) else 0
94  self._closeable = closeable
95  self._args, self._kwargs = args, kwargs
96  self._store(self._create())
97 
98  def __enter__(self):
99  """Enter the runtime context for the connection object."""
100  return self
101 
102  def __exit__(self, *exc):
103  """Exit the runtime context for the connection object.
104 
105  This does not close the connection, but it ends a transaction.
106  """
107  if exc[0] is None and exc[1] is None and exc[2] is None:
108  self.commit()
109  else:
110  self.rollback()
111 
112  def _create(self):
113  """Create a new connection using the creator function."""
114  con = self._creator(*self._args, **self._kwargs)
115  try:
116  try:
117  if self._dbapi.connect != self._creator:
118  raise AttributeError
119  except AttributeError:
120  # try finding the DB-API 2 module via the connection itself
121  try:
122  mod = con.__module__
123  except AttributeError:
124  mod = None
125  while mod:
126  try:
127  self._dbapi = sys.modules[mod]
128  if not callable(self._dbapi.connect):
129  raise AttributeError
130  except (AttributeError, KeyError):
131  pass
132  else:
133  break
134  i = mod.rfind('.')
135  if i < 0:
136  mod = None
137  else:
138  mod = mod[:i]
139  else:
140  try:
141  mod = con.OperationalError.__module__
142  except AttributeError:
143  mod = None
144  while mod:
145  try:
146  self._dbapi = sys.modules[mod]
147  if not callable(self._dbapi.connect):
148  raise AttributeError
149  except (AttributeError, KeyError):
150  pass
151  else:
152  break
153  i = mod.rfind('.')
154  if i < 0:
155  mod = None
156  else:
157  mod = mod[:i]
158  else:
159  self._dbapi = None
160  if self._threadsafety is None:
161  try:
162  self._threadsafety = self._dbapi.threadsafety
163  except AttributeError:
164  try:
165  self._threadsafety = con.threadsafety
166  except AttributeError:
167  pass
168  if self._failures is None:
169  try:
170  self._failures = (
171  self._dbapi.OperationalError,
172  self._dbapi.InternalError)
173  except AttributeError:
174  try:
175  self._failures = (
176  self._creator.OperationalError,
177  self._creator.InternalError)
178  except AttributeError:
179  try:
180  self._failures = (
181  con.OperationalError, con.InternalError)
182  except AttributeError:
183  raise AttributeError(
184  "Could not determine failure exceptions"
185  " (please set failures or creator.dbapi).")
186  if isinstance(self._failures, tuple):
187  self._failure = self._failures[0]
188  else:
189  self._failure = self._failures
190  self._setsession(con)
191  except Exception as error:
192  # the database module could not be determined
193  # or the session could not be prepared
194  try: # close the connection first
195  con.close()
196  except Exception:
197  pass
198  raise error # re-raise the original error again
199  return con
200 
201  def _setsession(self, con=None):
202  """Execute the SQL commands for session preparation."""
203  if con is None:
204  con = self._con
205  if self._setsession_sql:
206  cursor = con.cursor()
207  for sql in self._setsession_sql:
208  cursor.execute(sql)
209  cursor.close()
210 
211  def _store(self, con):
212  """Store a database connection for subsequent use."""
213  self._con = con
214  self._transaction = False
215  self._closed = False
216  self._usage = 0
217 
218  def _close(self):
219  """Close the tough connection.
220 
221  You can always close a tough connection with this method
222  and it will not complain if you close it more than once.
223  """
224  if not self._closed:
225  try:
226  self._con.close()
227  except Exception:
228  pass
229  self._transaction = False
230  self._closed = True
231 
232  def _reset(self, force=False):
233  """Reset a tough connection.
234 
235  Rollback if forced or the connection was in a transaction.
236  """
237  if not self._closed and (force or self._transaction):
238  try:
239  self.rollback()
240  except Exception:
241  pass
242 
243  def _ping_check(self, ping=1, reconnect=True):
244  """Check whether the connection is still alive using ping().
245 
246  If the the underlying connection is not active and the ping
247  parameter is set accordingly, the connection will be recreated
248  unless the connection is currently inside a transaction.
249  """
250  if ping & self._ping:
251  try: # if possible, ping the connection
252  try: # pass a reconnect=False flag if this is supported
253  alive = self._con.ping(False)
254  except TypeError: # the reconnect flag is not supported
255  alive = self._con.ping()
256  except (AttributeError, IndexError, TypeError, ValueError):
257  self._ping = 0 # ping() is not available
258  alive = None
259  reconnect = False
260  except Exception:
261  alive = False
262  else:
263  if alive is None:
264  alive = True
265  if alive:
266  reconnect = False
267  if reconnect and not self._transaction:
268  try: # try to reopen the connection
269  con = self._create()
270  except Exception:
271  pass
272  else:
273  self._close()
274  self._store(con)
275  alive = True
276  return alive
277 
278  def dbapi(self):
279  """Return the underlying DB-API 2 module of the connection."""
280  if self._dbapi is None:
281  raise AttributeError(
282  "Could not determine DB-API 2 module"
283  " (please set creator.dbapi).")
284  return self._dbapi
285 
286  def threadsafety(self):
287  """Return the thread safety level of the connection."""
288  if self._threadsafety is None:
289  if self._dbapi is None:
290  raise AttributeError(
291  "Could not determine threadsafety"
292  " (please set creator.dbapi or creator.threadsafety).")
293  return 0
294  return self._threadsafety
295 
296  def close(self):
297  """Close the tough connection.
298 
299  You are allowed to close a tough connection by default
300  and it will not complain if you close it more than once.
301 
302  You can disallow closing connections by setting
303  the closeable parameter to something false. In this case,
304  closing tough connections will be silently ignored.
305  """
306  if self._closeable:
307  self._close()
308  elif self._transaction:
309  self._reset()
310 
311  def begin(self, *args, **kwargs):
312  """Indicate the beginning of a transaction.
313 
314  During a transaction, connections won't be transparently
315  replaced, and all errors will be raised to the application.
316 
317  If the underlying driver supports this method, it will be called
318  with the given parameters (e.g. for distributed transactions).
319  """
320  self._transaction = True
321  try:
322  begin = self._con.begin
323  except AttributeError:
324  pass
325  else:
326  begin(*args, **kwargs)
327 
328  def commit(self):
329  """Commit any pending transaction."""
330  self._transaction = False
331  try:
332  self._con.commit()
333  except self._failures as error: # cannot commit
334  try: # try to reopen the connection
335  con = self._create()
336  except Exception:
337  pass
338  else:
339  self._close()
340  self._store(con)
341  raise error # re-raise the original error
342 
343  def rollback(self):
344  """Rollback pending transaction."""
345  self._transaction = False
346  try:
347  self._con.rollback()
348  except self._failures as error: # cannot rollback
349  try: # try to reopen the connection
350  con = self._create()
351  except Exception:
352  pass
353  else:
354  self._close()
355  self._store(con)
356  raise error # re-raise the original error
357 
358  def cancel(self):
359  """Cancel a long-running transaction.
360 
361  If the underlying driver supports this method, it will be called.
362  """
363  self._transaction = False
364  try:
365  cancel = self._con.cancel
366  except AttributeError:
367  pass
368  else:
369  cancel()
370 
371  def ping(self, *args, **kwargs):
372  """Ping connection."""
373  return self._con.ping(*args, **kwargs)
374 
375  def _cursor(self, *args, **kwargs):
376  """A "tough" version of the method cursor()."""
377  # The args and kwargs are not part of the standard,
378  # but some database modules seem to use these.
379  transaction = self._transaction
380  if not transaction:
381  self._ping_check(2)
382  try:
383  # check whether the connection has been used too often
384  if (self._maxusage and self._usage >= self._maxusage
385  and not transaction):
386  raise self._failure
387  cursor = self._con.cursor(*args, **kwargs) # try to get a cursor
388  except self._failures as error: # error in getting cursor
389  try: # try to reopen the connection
390  con = self._create()
391  except Exception:
392  pass
393  else:
394  try: # and try one more time to get a cursor
395  cursor = con.cursor(*args, **kwargs)
396  except Exception:
397  pass
398  else:
399  self._close()
400  self._store(con)
401  if transaction:
402  raise error # re-raise the original error again
403  return cursor
404  try:
405  con.close()
406  except Exception:
407  pass
408  if transaction:
409  self._transaction = False
410  raise error # re-raise the original error again
411  return cursor
412 
413  def cursor(self, *args, **kwargs):
414  """Return a new Cursor Object using the connection."""
415  return SteadyDBCursor(self, *args, **kwargs)
416 
417  def __del__(self):
418  """Delete the steady connection."""
419  try:
420  self._close() # make sure the connection is closed
421  except: # builtin Exceptions might not exist any more
422  pass
423 
424 
426  """A "tough" version of DB-API 2 cursors."""
427 
428  def __init__(self, con, *args, **kwargs):
429  """Create a "tough" DB-API 2 cursor."""
430  # basic initialization to make finalizer work
431  self._cursor = None
432  self._closed = True
433  # proper initialization of the cursor
434  self._con = con
435  self._args, self._kwargs = args, kwargs
436  self._clearsizes()
437  try:
438  self._cursor = con._cursor(*args, **kwargs)
439  except AttributeError:
440  raise TypeError("%r is not a SteadyDBConnection." % (con,))
441  self._closed = False
442 
443  def __enter__(self):
444  """Enter the runtime context for the cursor object."""
445  return self
446 
447  def __exit__(self, *exc):
448  """Exit the runtime context for the cursor object."""
449  self.close()
450 
451  def setinputsizes(self, sizes):
452  """Store input sizes in case cursor needs to be reopened."""
453  self._inputsizes = sizes
454 
455  def setoutputsize(self, size, column=None):
456  """Store output sizes in case cursor needs to be reopened."""
457  self._outputsizes[column] = size
458 
459  def _clearsizes(self):
460  """Clear stored input and output sizes."""
461  self._inputsizes = []
462  self._outputsizes = {}
463 
464  def _setsizes(self, cursor=None):
465  """Set stored input and output sizes for cursor execution."""
466  if cursor is None:
467  cursor = self._cursor
468  if self._inputsizes:
469  cursor.setinputsizes(self._inputsizes)
470  for column, size in self._outputsizes.items():
471  if column is None:
472  cursor.setoutputsize(size)
473  else:
474  cursor.setoutputsize(size, column)
475 
476  def close(self):
477  """Close the tough cursor.
478 
479  It will not complain if you close it more than once.
480  """
481  if not self._closed:
482  try:
483  self._cursor.close()
484  except Exception:
485  pass
486  self._closed = True
487 
488  def _get_tough_method(self, name):
489  """Return a "tough" version of the given cursor method."""
490 
491  def tough_method(*args, **kwargs):
492  execute = name.startswith('execute')
493  con = self._con
494  transaction = con._transaction
495  if not transaction:
496  con._ping_check(4)
497  try:
498  # 检查连接是否使用过频繁
499  if (con._maxusage and con._usage >= con._maxusage
500  and not transaction):
501  raise con._failure
502  if execute:
503  self._setsizes()
504  method = getattr(self._cursor, name)
505  result = method(*args, **kwargs) # try to execute
506  if execute:
507  self._clearsizes()
508  except con._failures as error: # execution error
509  if not transaction:
510  try:
511  cursor2 = con._cursor(
512  *self._args, **self._kwargs) # open new cursor
513  except Exception:
514  pass
515  else:
516  try: # and try one more time to execute
517  if execute:
518  self._setsizes(cursor2)
519  method = getattr(cursor2, name)
520  result = method(*args, **kwargs)
521  if execute:
522  self._clearsizes()
523  except Exception:
524  pass
525  else:
526  self.close()
527  self._cursor = cursor2
528  con._usage += 1
529  return result
530  try:
531  cursor2.close()
532  except Exception:
533  pass
534  try: # try to reopen the connection
535  con2 = con._create()
536  except Exception:
537  pass
538  else:
539  try:
540  cursor2 = con2.cursor(
541  *self._args, **self._kwargs) # open new cursor
542  except Exception:
543  pass
544  else:
545  if transaction:
546  self.close()
547  con._close()
548  con._store(con2)
549  self._cursor = cursor2
550  raise error # raise the original error again
551  error2 = None
552  try: # try one more time to execute
553  if execute:
554  self._setsizes(cursor2)
555  method2 = getattr(cursor2, name)
556  result = method2(*args, **kwargs)
557  if execute:
558  self._clearsizes()
559  except error.__class__: # same execution error
560  use2 = False
561  error2 = error
562  except Exception as error: # other execution errors
563  use2 = True
564  error2 = error
565  else:
566  use2 = True
567  if use2:
568  self.close()
569  con._close()
570  con._store(con2)
571  self._cursor = cursor2
572  con._usage += 1
573  if error2:
574  raise error2 # raise the other error
575  return result
576  try:
577  cursor2.close()
578  except Exception:
579  pass
580  try:
581  con2.close()
582  except Exception:
583  pass
584  if transaction:
585  self._transaction = False
586  raise error # re-raise the original error again
587  else:
588  con._usage += 1
589  return result
590 
591  return tough_method
592 
593  def __getattr__(self, name):
594  """Inherit methods and attributes of underlying cursor."""
595  if self._cursor:
596  if name.startswith(('execute', 'call')):
597  # make execution methods "tough"
598  return self._get_tough_method(name)
599  else:
600  return getattr(self._cursor, name)
601  else:
602  raise InvalidCursor
603 
604  def __del__(self):
605  """Delete the steady cursor."""
606  try:
607  self.close() # make sure the cursor is closed
608  except: # builtin Exceptions might not exist any more
609  pass
aestate.opera.DBPool.steady_db.SteadyDBConnection._closeable
_closeable
Definition: steady_db.py:92
aestate.opera.DBPool.steady_db.SteadyDBCursor.close
def close(self)
Definition: steady_db.py:476
aestate.opera.DBPool.steady_db.SteadyDBError
Definition: steady_db.py:8
aestate.opera.DBPool.steady_db.SteadyDBCursor._inputsizes
_inputsizes
Definition: steady_db.py:453
aestate.opera.DBPool.steady_db.SteadyDBConnection._failure
_failure
Definition: steady_db.py:187
aestate.opera.DBPool.steady_db.SteadyDBCursor._kwargs
_kwargs
Definition: steady_db.py:435
aestate.opera.DBPool.steady_db.SteadyDBConnection._create
def _create(self)
Definition: steady_db.py:112
aestate.opera.DBPool.steady_db.SteadyDBCursor.__del__
def __del__(self)
Definition: steady_db.py:604
aestate.opera.DBPool.steady_db.SteadyDBCursor.__init__
def __init__(self, con, *args, **kwargs)
Definition: steady_db.py:428
aestate.opera.DBPool.steady_db.SteadyDBConnection.commit
def commit(self)
Definition: steady_db.py:328
aestate.opera.DBPool.steady_db.InvalidCursor
Definition: steady_db.py:12
aestate.opera.DBPool.steady_db.SteadyDBCursor
Definition: steady_db.py:425
aestate.opera.DBPool.steady_db.SteadyDBConnection._maxusage
_maxusage
Definition: steady_db.py:85
aestate.opera.DBPool.steady_db.SteadyDBConnection.__del__
def __del__(self)
Definition: steady_db.py:417
aestate.opera.DBPool.steady_db.SteadyDBConnection.cancel
def cancel(self)
Definition: steady_db.py:358
aestate.opera.DBPool.steady_db.SteadyDBConnection._kwargs
_kwargs
Definition: steady_db.py:93
aestate.opera.DBPool.steady_db.SteadyDBConnection._cursor
def _cursor(self, *args, **kwargs)
Definition: steady_db.py:375
aestate.opera.DBPool.steady_db.SteadyDBConnection._creator
_creator
Definition: steady_db.py:58
aestate.opera.DBPool.steady_db.SteadyDBConnection._setsession
def _setsession(self, con=None)
Definition: steady_db.py:201
aestate.opera.DBPool.steady_db.SteadyDBConnection._store
def _store(self, con)
Definition: steady_db.py:211
aestate.opera.DBPool.steady_db.SteadyDBCursor._closed
_closed
Definition: steady_db.py:432
aestate.opera.DBPool.steady_db.SteadyDBConnection.close
def close(self)
Definition: steady_db.py:296
aestate.opera.DBPool.steady_db.SteadyDBConnection._transaction
_transaction
Definition: steady_db.py:214
aestate.opera.DBPool.steady_db.SteadyDBCursor._cursor
_cursor
Definition: steady_db.py:431
aestate.opera.DBPool.steady_db.SteadyDBCursor.__exit__
def __exit__(self, *exc)
Definition: steady_db.py:447
aestate.opera.DBPool.steady_db.SteadyDBConnection.rollback
def rollback(self)
Definition: steady_db.py:343
aestate.opera.DBPool.steady_db.SteadyDBCursor.__getattr__
def __getattr__(self, name)
Definition: steady_db.py:593
aestate.opera.DBPool.steady_db.SteadyDBCursor.__enter__
def __enter__(self)
Definition: steady_db.py:443
aestate.opera.DBPool.steady_db.SteadyDBConnection.__exit__
def __exit__(self, *exc)
Definition: steady_db.py:102
aestate.opera.DBPool.steady_db.SteadyDBConnection.__init__
def __init__(self, creator, maxusage=None, setsession=None, failures=None, ping=1, closeable=True, *args, **kwargs)
Definition: steady_db.py:51
aestate.opera.DBPool.steady_db.SteadyDBCursor._transaction
_transaction
Definition: steady_db.py:585
aestate.opera.DBPool.steady_db.SteadyDBConnection._usage
_usage
Definition: steady_db.py:216
aestate.opera.DBPool.steady_db.SteadyDBConnection._failures
_failures
Definition: steady_db.py:90
aestate.opera.DBPool.steady_db.SteadyDBConnection._setsession_sql
_setsession_sql
Definition: steady_db.py:86
aestate.opera.DBPool.steady_db.SteadyDBConnection.cursor
def cursor(self, *args, **kwargs)
Definition: steady_db.py:413
aestate.opera.DBPool.steady_db.SteadyDBConnection.__enter__
def __enter__(self)
Definition: steady_db.py:98
aestate.opera.DBPool.steady_db.SteadyDBCursor._setsizes
def _setsizes(self, cursor=None)
Definition: steady_db.py:464
aestate.opera.DBPool.steady_db.connect
def connect(creator, maxusage=None, setsession=None, failures=None, ping=1, closeable=True, *args, **kwargs)
Definition: steady_db.py:16
aestate.opera.DBPool.steady_db.SteadyDBConnection._close
def _close(self)
Definition: steady_db.py:218
aestate.opera.DBPool.steady_db.SteadyDBConnection.dbapi
def dbapi(self)
Definition: steady_db.py:278
aestate.opera.DBPool.steady_db.SteadyDBCursor.setoutputsize
def setoutputsize(self, size, column=None)
Definition: steady_db.py:455
aestate.opera.DBPool.steady_db.SteadyDBConnection._closed
_closed
Definition: steady_db.py:55
aestate.opera.DBPool.steady_db.SteadyDBConnection._ping_check
def _ping_check(self, ping=1, reconnect=True)
Definition: steady_db.py:243
aestate.opera.DBPool.steady_db.SteadyDBConnection.threadsafety
def threadsafety(self)
Definition: steady_db.py:286
aestate.opera.DBPool.steady_db.SteadyDBConnection
Definition: steady_db.py:46
aestate.opera.DBPool.steady_db.SteadyDBConnection._reset
def _reset(self, force=False)
Definition: steady_db.py:232
aestate.opera.DBPool.steady_db.SteadyDBCursor.setinputsizes
def setinputsizes(self, sizes)
Definition: steady_db.py:451
aestate.opera.DBPool.steady_db.SteadyDBConnection._con
_con
Definition: steady_db.py:54
aestate.opera.DBPool.steady_db.SteadyDBCursor._con
_con
Definition: steady_db.py:434
aestate.opera.DBPool.steady_db.SteadyDBCursor._outputsizes
_outputsizes
Definition: steady_db.py:462
aestate.opera.DBPool.steady_db.SteadyDBConnection.ping
def ping(self, *args, **kwargs)
Definition: steady_db.py:371
aestate.opera.DBPool.steady_db.SteadyDBCursor._get_tough_method
def _get_tough_method(self, name)
Definition: steady_db.py:488
aestate.opera.DBPool.steady_db.SteadyDBConnection._threadsafety
_threadsafety
Definition: steady_db.py:73
aestate.opera.DBPool.steady_db.SteadyDBConnection._ping
_ping
Definition: steady_db.py:91
aestate.opera.DBPool.steady_db.SteadyDBConnection._dbapi
_dbapi
Definition: steady_db.py:59
aestate.opera.DBPool.steady_db.SteadyDBCursor._clearsizes
def _clearsizes(self)
Definition: steady_db.py:459
aestate.opera.DBPool.steady_db.SteadyDBConnection.begin
def begin(self, *args, **kwargs)
Definition: steady_db.py:311