Aestate
steady_pg.py
Go to the documentation of this file.
1 """SteadyPg - hardened classic PyGreSQL connections.
2 
3 Implements steady connections to a PostgreSQL database
4 using the classic (not DB-API 2 compliant) PyGreSQL API.
5 
6 The connections are transparently reopened when they are
7 closed or the database connection has been lost or when
8 they are used more often than an optional usage limit.
9 Only connections which have been marked as being in a database
10 transaction with a begin() call will not be silently replaced.
11 
12 A typical situation where database connections are lost
13 is when the database server or an intervening firewall is
14 shutdown and restarted for maintenance reasons. In such a
15 case, all database connections would become unusable, even
16 though the database service may be already available again.
17 
18 The "hardened" connections provided by this module will
19 make the database connections immediately available again.
20 
21 This results in a steady PostgreSQL connection that can be used
22 by PooledPg or PersistentPg to create pooled or persistent
23 connections to a PostgreSQL database in a threaded environment
24 such as the application server of "Webware for Python."
25 Note, however, that the connections themselves are not thread-safe.
26 
27 For more information on PostgreSQL, see:
28  https://www.postgresql.org/
29 For more information on PyGreSQL, see:
30  http://www.pygresql.org
31 For more information on Webware for Python, see:
32  https://webwareforpython.github.io/w4py/
33 
34 
35 Usage:
36 
37 You can use the class SteadyPgConnection in the same way as you
38 would use the class DB from the classic PyGreSQL API module db.
39 The only difference is that you may specify a usage limit as the
40 first parameter when you open a connection (set it to None
41 if you prefer unlimited usage), and an optional list of commands
42 that may serve to prepare the session as the second parameter,
43 and you can specify whether is is allowed to close the connection
44 (by default this is true). When the connection to the PostgreSQL
45 database is lost or has been used too often, it will be automatically
46 reset, without further notice.
47 
48  from dbutils.steady_pg import SteadyPgConnection
49  db = SteadyPgConnection(10000, ["set datestyle to german"],
50  host=..., dbname=..., user=..., ...)
51  ...
52  result = db.query('...')
53  ...
54  db.close()
55 
56 
57 Ideas for improvement:
58 
59 * Alternatively to the maximum number of uses,
60  implement a maximum time to live for connections.
61 * Optionally log usage and loss of connection.
62 
63 
64 Copyright, credits and license:
65 
66 * Contributed as supplement for Webware for Python and PyGreSQL
67  by Christoph Zwerschke in September 2005
68 
69 Licensed under the MIT license.
70 """
71 
72 from pg import DB as PgConnection
73 
74 from . import __version__
75 
76 try:
77  baseint = (int, long)
78 except NameError: # Python 3
79  baseint = int
80 
81 
82 class SteadyPgError(Exception):
83  """General SteadyPg error."""
84 
85 
87  """Database connection is invalid."""
88 
89 
91  """Class representing steady connections to a PostgreSQL database.
92 
93  Underlying the connection is a classic PyGreSQL pg API database
94  connection which is reset if the connection is lost or used too often.
95  Thus the resulting connection is steadier ("tough and self-healing").
96 
97  If you want the connection to be persistent in a threaded environment,
98  then you should not deal with this class directly, but use either the
99  PooledPg module or the PersistentPg module to get the connections.
100  """
101 
102  version = __version__
103 
104  def __init__(
105  self, maxusage=None, setsession=None, closeable=True,
106  *args, **kwargs):
107  """Create a "tough" PostgreSQL connection.
108 
109  maxusage: maximum usage limit for the underlying PyGreSQL connection
110  (number of uses, 0 or None means unlimited usage)
111  When this limit is reached, the connection is automatically reset.
112  setsession: optional list of SQL commands that may serve to prepare
113  the session, e.g. ["set datestyle to ...", "set time zone ..."]
114  closeable: if this is set to false, then closing the connection will
115  be silently ignored, but by default the connection can be closed
116  args, kwargs: the parameters that shall be used to establish
117  the PostgreSQL connections with PyGreSQL using pg.DB()
118  """
119  # basic initialization to make finalizer work
120  self._con = None
121  self._closed = True
122  # proper initialization of the connection
123  if maxusage is None:
124  maxusage = 0
125  if not isinstance(maxusage, baseint):
126  raise TypeError("'maxusage' must be an integer value.")
127  self._maxusage = maxusage
128  self._setsession_sql = setsession
129  self._closeable = closeable
130  self._con = PgConnection(*args, **kwargs)
131  self._transaction = False
132  self._closed = False
133  self._setsession()
134  self._usage = 0
135 
136  def __enter__(self):
137  """Enter the runtime context. This will start a transaction."""
138  self.begin()
139  return self
140 
141  def __exit__(self, *exc):
142  """Exit the runtime context. This will end the transaction."""
143  if exc[0] is None and exc[1] is None and exc[2] is None:
144  self.commit()
145  else:
146  self.rollback()
147 
148  def _setsession(self):
149  """Execute the SQL commands for session preparation."""
150  if self._setsession_sql:
151  for sql in self._setsession_sql:
152  self._con.query(sql)
153 
154  def _close(self):
155  """Close the tough connection.
156 
157  You can always close a tough connection with this method
158  and it will not complain if you close it more than once.
159  """
160  if not self._closed:
161  try:
162  self._con.close()
163  except Exception:
164  pass
165  self._transaction = False
166  self._closed = True
167 
168  def close(self):
169  """Close the tough connection.
170 
171  You are allowed to close a tough connection by default
172  and it will not complain if you close it more than once.
173 
174  You can disallow closing connections by setting
175  the closeable parameter to something false. In this case,
176  closing tough connections will be silently ignored.
177  """
178  if self._closeable:
179  self._close()
180  elif self._transaction:
181  self.reset()
182 
183  def reopen(self):
184  """Reopen the tough connection.
185 
186  It will not complain if the connection cannot be reopened.
187  """
188  try:
189  self._con.reopen()
190  except Exception:
191  if self._transcation:
192  self._transaction = False
193  try:
194  self._con.query('rollback')
195  except Exception:
196  pass
197  else:
198  self._transaction = False
199  self._closed = False
200  self._setsession()
201  self._usage = 0
202 
203  def reset(self):
204  """Reset the tough connection.
205 
206  If a reset is not possible, tries to reopen the connection.
207  It will not complain if the connection is already closed.
208  """
209  try:
210  self._con.reset()
211  self._transaction = False
212  self._setsession()
213  self._usage = 0
214  except Exception:
215  try:
216  self.reopen()
217  except Exception:
218  try:
219  self.rollback()
220  except Exception:
221  pass
222 
223  def begin(self, sql=None):
224  """Begin a transaction."""
225  self._transaction = True
226  try:
227  begin = self._con.begin
228  except AttributeError:
229  return self._con.query(sql or 'begin')
230  else:
231  # use existing method if available
232  if sql:
233  return begin(sql=sql)
234  else:
235  return begin()
236 
237  def end(self, sql=None):
238  """Commit the current transaction."""
239  self._transaction = False
240  try:
241  end = self._con.end
242  except AttributeError:
243  return self._con.query(sql or 'end')
244  else:
245  if sql:
246  return end(sql=sql)
247  else:
248  return end()
249 
250  def commit(self, sql=None):
251  """Commit the current transaction."""
252  self._transaction = False
253  try:
254  commit = self._con.commit
255  except AttributeError:
256  return self._con.query(sql or 'commit')
257  else:
258  if sql:
259  return commit(sql=sql)
260  else:
261  return commit()
262 
263  def rollback(self, sql=None):
264  """Rollback the current transaction."""
265  self._transaction = False
266  try:
267  rollback = self._con.rollback
268  except AttributeError:
269  return self._con.query(sql or 'rollback')
270  else:
271  if sql:
272  return rollback(sql=sql)
273  else:
274  return rollback()
275 
276  def _get_tough_method(self, method):
277  """Return a "tough" version of a connection class method.
278 
279  The tough version checks whether the connection is bad (lost)
280  and automatically and transparently tries to reset the connection
281  if this is the case (for instance, the database has been restarted).
282  """
283 
284  def tough_method(*args, **kwargs):
285  transaction = self._transaction
286  if not transaction:
287  try:
288  # check whether connection status is bad
289  # or the connection has been used too often
290  if not self._con.db.status or (
291  self._maxusage and self._usage >= self._maxusage):
292  raise AttributeError
293  except Exception:
294  self.reset() # then reset the connection
295  try:
296  result = method(*args, **kwargs) # try connection method
297  except Exception: # error in query
298  if transaction: # inside a transaction
299  self._transaction = False
300  raise # propagate the error
301  elif self._con.db.status: # if it was not a connection problem
302  raise # then propagate the error
303  else: # otherwise
304  self.reset() # reset the connection
305  result = method(*args, **kwargs) # and try one more time
306  self._usage += 1
307  return result
308 
309  return tough_method
310 
311  def __getattr__(self, name):
312  """Inherit the members of the standard connection class.
313 
314  Some methods are made "tougher" than in the standard version.
315  """
316  if self._con:
317  attr = getattr(self._con, name)
318  if (name in ('query', 'get', 'insert', 'update', 'delete')
319  or name.startswith('get_')):
320  attr = self._get_tough_method(attr)
321  return attr
322  else:
323  raise InvalidConnection
324 
325  def __del__(self):
326  """Delete the steady connection."""
327  try:
328  self._close() # make sure the connection is closed
329  except: # builtin Exceptions might not exist any more
330  pass
aestate.opera.DBPool.steady_pg.SteadyPgConnection.begin
def begin(self, sql=None)
Definition: steady_pg.py:223
aestate.opera.DBPool.steady_pg.SteadyPgConnection._maxusage
_maxusage
Definition: steady_pg.py:125
aestate.opera.DBPool.steady_pg.SteadyPgConnection.__exit__
def __exit__(self, *exc)
Definition: steady_pg.py:141
aestate.opera.DBPool.steady_pg.SteadyPgConnection._closeable
_closeable
Definition: steady_pg.py:127
aestate.opera.DBPool.steady_pg.SteadyPgConnection.__enter__
def __enter__(self)
Definition: steady_pg.py:136
aestate.opera.DBPool.steady_pg.SteadyPgConnection._con
_con
Definition: steady_pg.py:118
aestate.opera.DBPool.steady_pg.SteadyPgConnection.__getattr__
def __getattr__(self, name)
Definition: steady_pg.py:311
aestate.opera.DBPool.steady_pg.SteadyPgConnection.__del__
def __del__(self)
Definition: steady_pg.py:325
aestate.opera.DBPool.steady_pg.SteadyPgConnection.reset
def reset(self)
Definition: steady_pg.py:203
aestate.opera.DBPool.steady_pg.SteadyPgError
Definition: steady_pg.py:82
aestate.opera.DBPool.steady_pg.SteadyPgConnection.__init__
def __init__(self, maxusage=None, setsession=None, closeable=True, *args, **kwargs)
Definition: steady_pg.py:104
aestate.opera.DBPool.steady_pg.SteadyPgConnection._setsession
def _setsession(self)
Definition: steady_pg.py:148
aestate.opera.DBPool.steady_pg.SteadyPgConnection.rollback
def rollback(self, sql=None)
Definition: steady_pg.py:263
aestate.opera.DBPool.steady_pg.SteadyPgConnection._usage
_usage
Definition: steady_pg.py:132
aestate.opera.DBPool.steady_pg.SteadyPgConnection.commit
def commit(self, sql=None)
Definition: steady_pg.py:250
aestate.opera.DBPool.steady_pg.InvalidConnection
Definition: steady_pg.py:86
aestate.opera.DBPool.steady_pg.SteadyPgConnection._transaction
_transaction
Definition: steady_pg.py:129
aestate.opera.DBPool.steady_pg.SteadyPgConnection._setsession_sql
_setsession_sql
Definition: steady_pg.py:126
aestate.opera.DBPool.steady_pg.SteadyPgConnection
Definition: steady_pg.py:90
aestate.opera.DBPool.steady_pg.SteadyPgConnection._close
def _close(self)
Definition: steady_pg.py:154
aestate.opera.DBPool.steady_pg.SteadyPgConnection._closed
_closed
Definition: steady_pg.py:119
aestate.opera.DBPool.steady_pg.SteadyPgConnection.close
def close(self)
Definition: steady_pg.py:168
aestate.opera.DBPool.steady_pg.SteadyPgConnection.end
def end(self, sql=None)
Definition: steady_pg.py:237
aestate.opera.DBPool.steady_pg.SteadyPgConnection._get_tough_method
def _get_tough_method(self, method)
Definition: steady_pg.py:276
aestate.opera.DBPool.steady_pg.SteadyPgConnection.reopen
def reopen(self)
Definition: steady_pg.py:183