update sqlalchemy

This commit is contained in:
Jan Gerber 2016-02-22 13:17:39 +01:00
commit a4267212e4
192 changed files with 17429 additions and 9601 deletions

View file

@ -1,5 +1,5 @@
# sqlalchemy/pool.py
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@ -186,6 +186,10 @@ class Pool(log.Identified):
database that supports transactions,
as it will lead to deadlocks and stale
state.
* ``"none"`` - same as ``None``
.. versionadded:: 0.9.10
* ``False`` - same as None, this is here for
backwards compatibility.
@ -220,7 +224,7 @@ class Pool(log.Identified):
self._use_threadlocal = use_threadlocal
if reset_on_return in ('rollback', True, reset_rollback):
self._reset_on_return = reset_rollback
elif reset_on_return in (None, False, reset_none):
elif reset_on_return in ('none', None, False, reset_none):
self._reset_on_return = reset_none
elif reset_on_return in ('commit', reset_commit):
self._reset_on_return = reset_commit
@ -230,6 +234,7 @@ class Pool(log.Identified):
% reset_on_return)
self.echo = echo
if _dispatch:
self.dispatch._update(_dispatch, only_propagate=False)
if _dialect:
@ -244,13 +249,46 @@ class Pool(log.Identified):
for l in listeners:
self.add_listener(l)
@property
def _creator(self):
return self.__dict__['_creator']
@_creator.setter
def _creator(self, creator):
self.__dict__['_creator'] = creator
self._invoke_creator = self._should_wrap_creator(creator)
def _should_wrap_creator(self, creator):
"""Detect if creator accepts a single argument, or is sent
as a legacy style no-arg function.
"""
try:
argspec = util.get_callable_argspec(self._creator, no_self=True)
except TypeError:
return lambda crec: creator()
defaulted = argspec[3] is not None and len(argspec[3]) or 0
positionals = len(argspec[0]) - defaulted
# look for the exact arg signature that DefaultStrategy
# sends us
if (argspec[0], argspec[3]) == (['connection_record'], (None,)):
return creator
# or just a single positional
elif positionals == 1:
return creator
# all other cases, just wrap and assume legacy "creator" callable
# thing
else:
return lambda crec: creator()
def _close_connection(self, connection):
self.logger.debug("Closing connection %r", connection)
try:
self._dialect.do_close(connection)
except (SystemExit, KeyboardInterrupt):
raise
except:
except Exception:
self.logger.error("Exception closing connection %r",
connection, exc_info=True)
@ -305,7 +343,7 @@ class Pool(log.Identified):
"""Return a new :class:`.Pool`, of the same class as this one
and configured with identical creation arguments.
This method is used in conjunection with :meth:`dispose`
This method is used in conjunction with :meth:`dispose`
to close out an entire :class:`.Pool` and create a new one in
its place.
@ -425,6 +463,8 @@ class _ConnectionRecord(object):
"""
_soft_invalidate_time = 0
@util.memoized_property
def info(self):
"""The ``.info`` dictionary associated with the DBAPI connection.
@ -441,18 +481,19 @@ class _ConnectionRecord(object):
try:
dbapi_connection = rec.get_connection()
except:
rec.checkin()
raise
fairy = _ConnectionFairy(dbapi_connection, rec)
with util.safe_reraise():
rec.checkin()
echo = pool._should_log_debug()
fairy = _ConnectionFairy(dbapi_connection, rec, echo)
rec.fairy_ref = weakref.ref(
fairy,
lambda ref: _finalize_fairy and
_finalize_fairy(
dbapi_connection,
rec, pool, ref, pool._echo)
rec, pool, ref, echo)
)
_refs.add(rec)
if pool._echo:
if echo:
pool.logger.debug("Connection %r checked out from pool",
dbapi_connection)
return fairy
@ -472,7 +513,7 @@ class _ConnectionRecord(object):
if self.connection is not None:
self.__close()
def invalidate(self, e=None):
def invalidate(self, e=None, soft=False):
"""Invalidate the DBAPI connection held by this :class:`._ConnectionRecord`.
This method is called for all connection invalidations, including
@ -480,6 +521,13 @@ class _ConnectionRecord(object):
:meth:`.Connection.invalidate` methods are called, as well as when any
so-called "automatic invalidation" condition occurs.
:param e: an exception object indicating a reason for the invalidation.
:param soft: if True, the connection isn't closed; instead, this
connection will be recycled on next checkout.
.. versionadded:: 1.0.3
.. seealso::
:ref:`pool_connection_invalidation`
@ -488,22 +536,31 @@ class _ConnectionRecord(object):
# already invalidated
if self.connection is None:
return
self.__pool.dispatch.invalidate(self.connection, self, e)
if soft:
self.__pool.dispatch.soft_invalidate(self.connection, self, e)
else:
self.__pool.dispatch.invalidate(self.connection, self, e)
if e is not None:
self.__pool.logger.info(
"Invalidate connection %r (reason: %s:%s)",
"%sInvalidate connection %r (reason: %s:%s)",
"Soft " if soft else "",
self.connection, e.__class__.__name__, e)
else:
self.__pool.logger.info(
"Invalidate connection %r", self.connection)
self.__close()
self.connection = None
"%sInvalidate connection %r",
"Soft " if soft else "",
self.connection)
if soft:
self._soft_invalidate_time = time.time()
else:
self.__close()
self.connection = None
def get_connection(self):
recycle = False
if self.connection is None:
self.connection = self.__connect()
self.info.clear()
self.connection = self.__connect()
if self.__pool.dispatch.connect:
self.__pool.dispatch.connect(self.connection, self)
elif self.__pool._recycle > -1 and \
@ -519,22 +576,35 @@ class _ConnectionRecord(object):
self.connection
)
recycle = True
elif self._soft_invalidate_time > self.starttime:
self.__pool.logger.info(
"Connection %r invalidated due to local soft invalidation; " +
"recycling",
self.connection
)
recycle = True
if recycle:
self.__close()
self.connection = self.__connect()
self.info.clear()
# ensure that if self.__connect() fails,
# we are not referring to the previous stale connection here
self.connection = None
self.connection = self.__connect()
if self.__pool.dispatch.connect:
self.__pool.dispatch.connect(self.connection, self)
return self.connection
def __close(self):
self.finalize_callback.clear()
self.__pool._close_connection(self.connection)
def __connect(self):
try:
self.starttime = time.time()
connection = self.__pool._creator()
connection = self.__pool._invoke_creator(self)
self.__pool.logger.debug("Created new connection %r", connection)
return connection
except Exception as e:
@ -560,19 +630,20 @@ def _finalize_fairy(connection, connection_record,
connection)
try:
fairy = fairy or _ConnectionFairy(connection, connection_record)
fairy = fairy or _ConnectionFairy(
connection, connection_record, echo)
assert fairy.connection is connection
fairy._reset(pool, echo)
fairy._reset(pool)
# Immediately close detached instances
if not connection_record:
pool._close_connection(connection)
except Exception as e:
except BaseException as e:
pool.logger.error(
"Exception during reset or similar", exc_info=True)
if connection_record:
connection_record.invalidate(e=e)
if isinstance(e, (SystemExit, KeyboardInterrupt)):
if not isinstance(e, Exception):
raise
if connection_record:
@ -603,9 +674,10 @@ class _ConnectionFairy(object):
"""
def __init__(self, dbapi_connection, connection_record):
def __init__(self, dbapi_connection, connection_record, echo):
self.connection = dbapi_connection
self._connection_record = connection_record
self._echo = echo
connection = None
"""A reference to the actual DBAPI connection being tracked."""
@ -642,7 +714,6 @@ class _ConnectionFairy(object):
fairy._pool = pool
fairy._counter = 0
fairy._echo = pool._should_log_debug()
if threadconns is not None:
threadconns.current = weakref.ref(fairy)
@ -666,7 +737,13 @@ class _ConnectionFairy(object):
pool.logger.info(
"Disconnection detected on checkout: %s", e)
fairy._connection_record.invalidate(e)
fairy.connection = fairy._connection_record.get_connection()
try:
fairy.connection = \
fairy._connection_record.get_connection()
except:
with util.safe_reraise():
fairy._connection_record.checkin()
attempts -= 1
pool.logger.info("Reconnection attempts exhausted on checkout")
@ -684,11 +761,11 @@ class _ConnectionFairy(object):
_close = _checkin
def _reset(self, pool, echo):
def _reset(self, pool):
if pool.dispatch.reset:
pool.dispatch.reset(self, self._connection_record)
if pool._reset_on_return is reset_rollback:
if echo:
if self._echo:
pool.logger.debug("Connection %s rollback-on-return%s",
self.connection,
", via agent"
@ -698,7 +775,7 @@ class _ConnectionFairy(object):
else:
pool._dialect.do_rollback(self)
elif pool._reset_on_return is reset_commit:
if echo:
if self._echo:
pool.logger.debug("Connection %s commit-on-return%s",
self.connection,
", via agent"
@ -734,7 +811,7 @@ class _ConnectionFairy(object):
"""
return self._connection_record.info
def invalidate(self, e=None):
def invalidate(self, e=None, soft=False):
"""Mark this connection as invalidated.
This method can be called directly, and is also called as a result
@ -743,6 +820,13 @@ class _ConnectionFairy(object):
further use by the pool. The invalidation mechanism proceeds
via the :meth:`._ConnectionRecord.invalidate` internal method.
:param e: an exception object indicating a reason for the invalidation.
:param soft: if True, the connection isn't closed; instead, this
connection will be recycled on next checkout.
.. versionadded:: 1.0.3
.. seealso::
:ref:`pool_connection_invalidation`
@ -753,9 +837,10 @@ class _ConnectionFairy(object):
util.warn("Can't invalidate an already-closed connection.")
return
if self._connection_record:
self._connection_record.invalidate(e=e)
self.connection = None
self._checkin()
self._connection_record.invalidate(e=e, soft=soft)
if not soft:
self.connection = None
self._checkin()
def cursor(self, *args, **kwargs):
"""Return a new DBAPI cursor for the underlying connection.
@ -804,6 +889,19 @@ class SingletonThreadPool(Pool):
Maintains one connection per each thread, never moving a connection to a
thread other than the one which it was created in.
.. warning:: the :class:`.SingletonThreadPool` will call ``.close()``
on arbitrary connections that exist beyond the size setting of
``pool_size``, e.g. if more unique **thread identities**
than what ``pool_size`` states are used. This cleanup is
non-deterministic and not sensitive to whether or not the connections
linked to those thread identities are currently in use.
:class:`.SingletonThreadPool` may be improved in a future release,
however in its current status it is generally used only for test
scenarios using a SQLite ``:memory:`` database and is not recommended
for production use.
Options are the same as those of :class:`.Pool`, as well as:
:param pool_size: The number of threads in which to maintain connections
@ -840,9 +938,7 @@ class SingletonThreadPool(Pool):
for conn in self._all_conns:
try:
conn.close()
except (SystemExit, KeyboardInterrupt):
raise
except:
except Exception:
# pysqlite won't even let you close a conn from a thread
# that didn't create it
pass
@ -919,9 +1015,9 @@ class QueuePool(Pool):
on returning a connection. Defaults to 30.
:param \**kw: Other keyword arguments including
:paramref:`.Pool.recycle`, :paramref:`.Pool.echo`,
:paramref:`.Pool.reset_on_return` and others are passed to the
:class:`.Pool` constructor.
:paramref:`.Pool.recycle`, :paramref:`.Pool.echo`,
:paramref:`.Pool.reset_on_return` and others are passed to the
:class:`.Pool` constructor.
"""
Pool.__init__(self, creator, **kw)
@ -960,8 +1056,8 @@ class QueuePool(Pool):
try:
return self._create_connection()
except:
self._dec_overflow()
raise
with util.safe_reraise():
self._dec_overflow()
else:
return self._do_get()