collection of Sequence objects, list
of schema names. [ticket:2104]
+- pool
+ - AssertionPool now stores the traceback indicating
+ where the currently checked out connection was
+ acquired; this traceback is reported within
+ the assertion raised upon a second concurrent
+ checkout; courtesy Gunnlaugur Briem
+ [ticket:2103]
+
- oracle
- Using column names that would require quotes
for the column itself or for a name-generated
SQLAlchemy connection pool.
"""
-import weakref, time, threading
+import weakref, time, traceback
from sqlalchemy import exc, log, event, events, interfaces, util
from sqlalchemy.util import queue as sqla_queue
-from sqlalchemy.util import threading, pickle, memoized_property
+from sqlalchemy.util import threading, pickle, memoized_property, \
+ chop_traceback
proxies = {}
return self.connection
class AssertionPool(Pool):
- """A Pool that allows at most one checked out connection at any given
+ """A :class:`.Pool` that allows at most one checked out connection at any given
time.
This will raise an exception if more than one connection is checked out
at a time. Useful for debugging code that is using more connections
than desired.
+
+ :class:`.AssertionPool` also logs a traceback of where
+ the original connection was checked out, and reports
+ this in the assertion error raised (new in 0.7).
"""
def __init__(self, *args, **kw):
self._conn = None
self._checked_out = False
+ self._store_traceback = kw.pop('store_traceback', True)
+ self._checkout_traceback = None
Pool.__init__(self, *args, **kw)
def status(self):
def _do_get(self):
if self._checked_out:
- raise AssertionError("connection is already checked out")
+ if self._checkout_traceback:
+ suffix = ' at:\n%s' % ''.join(
+ chop_traceback(self._checkout_traceback))
+ else:
+ suffix = ''
+ raise AssertionError("connection is already checked out" + suffix)
if not self._conn:
self._conn = self._create_connection()
self._checked_out = True
+ if self._store_traceback:
+ self._checkout_traceback = traceback.format_stack()
return self._conn
class _DBProxy(object):
monkeypatch_proxied_specials, asbool, bool_or_str, coerce_kw_type,\
duck_type_collection, assert_arg_type, symbol, dictlike_iteritems,\
classproperty, set_creation_order, warn_exception, warn, NoneType,\
- constructor_copy, methods_equivalent
+ constructor_copy, methods_equivalent, chop_traceback
from deprecations import warn_deprecated, warn_pending_deprecation, \
deprecated, pending_deprecation
import itertools
import inspect
import operator
+import re
import sys
import types
import warnings
else:
warnings.warn(msg, stacklevel=stacklevel)
+_SQLA_RE = re.compile(r'sqlalchemy/([a-z_]+/){0,2}[a-z_]+\.py')
+_UNITTEST_RE = re.compile(r'unit(?:2|test2?/)')
+def chop_traceback(tb, exclude_prefix=_UNITTEST_RE, exclude_suffix=_SQLA_RE):
+ """Chop extraneous lines off beginning and end of a traceback.
+
+ :param tb:
+ a list of traceback lines as returned by ``traceback.format_stack()``
+
+ :param exclude_prefix:
+ a regular expression object matching lines to skip at beginning of ``tb``
+
+ :param exclude_suffix:
+ a regular expression object matching lines to skip at end of ``tb``
+ """
+ start = 0
+ end = len(tb) - 1
+ while start <= end and exclude_prefix.search(tb[start]):
+ start += 1
+ while start <= end and exclude_suffix.search(tb[end]):
+ end -= 1
+ return tb[start:end+1]
+
NoneType = type(None)