Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/pool/base.py: 32%
406 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# sqlalchemy/pool.py
2# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
9"""Base constructs for connection pools.
11"""
13from collections import deque
14import time
15import weakref
17from .. import event
18from .. import exc
19from .. import log
20from .. import util
23reset_rollback = util.symbol("reset_rollback")
24reset_commit = util.symbol("reset_commit")
25reset_none = util.symbol("reset_none")
28class _ConnDialect(object):
29 """partial implementation of :class:`.Dialect`
30 which provides DBAPI connection methods.
32 When a :class:`_pool.Pool` is combined with an :class:`_engine.Engine`,
33 the :class:`_engine.Engine` replaces this with its own
34 :class:`.Dialect`.
36 """
38 is_async = False
39 has_terminate = False
41 def do_rollback(self, dbapi_connection):
42 dbapi_connection.rollback()
44 def do_commit(self, dbapi_connection):
45 dbapi_connection.commit()
47 def do_terminate(self, dbapi_connection):
48 dbapi_connection.close()
50 def do_close(self, dbapi_connection):
51 dbapi_connection.close()
53 def do_ping(self, dbapi_connection):
54 raise NotImplementedError(
55 "The ping feature requires that a dialect is "
56 "passed to the connection pool."
57 )
59 def get_driver_connection(self, connection):
60 return connection
63class _AsyncConnDialect(_ConnDialect):
64 is_async = True
67class Pool(log.Identified):
69 """Abstract base class for connection pools."""
71 _dialect = _ConnDialect()
73 def __init__(
74 self,
75 creator,
76 recycle=-1,
77 echo=None,
78 logging_name=None,
79 reset_on_return=True,
80 events=None,
81 dialect=None,
82 pre_ping=False,
83 _dispatch=None,
84 ):
85 """
86 Construct a Pool.
88 :param creator: a callable function that returns a DB-API
89 connection object. The function will be called with
90 parameters.
92 :param recycle: If set to a value other than -1, number of
93 seconds between connection recycling, which means upon
94 checkout, if this timeout is surpassed the connection will be
95 closed and replaced with a newly opened connection. Defaults to -1.
97 :param logging_name: String identifier which will be used within
98 the "name" field of logging records generated within the
99 "sqlalchemy.pool" logger. Defaults to a hexstring of the object's
100 id.
102 :param echo: if True, the connection pool will log
103 informational output such as when connections are invalidated
104 as well as when connections are recycled to the default log handler,
105 which defaults to ``sys.stdout`` for output.. If set to the string
106 ``"debug"``, the logging will include pool checkouts and checkins.
108 The :paramref:`_pool.Pool.echo` parameter can also be set from the
109 :func:`_sa.create_engine` call by using the
110 :paramref:`_sa.create_engine.echo_pool` parameter.
112 .. seealso::
114 :ref:`dbengine_logging` - further detail on how to configure
115 logging.
117 :param reset_on_return: Determine steps to take on
118 connections as they are returned to the pool, which were
119 not otherwise handled by a :class:`_engine.Connection`.
120 Available from :func:`_sa.create_engine` via the
121 :paramref:`_sa.create_engine.pool_reset_on_return` parameter.
123 :paramref:`_pool.Pool.reset_on_return` can have any of these values:
125 * ``"rollback"`` - call rollback() on the connection,
126 to release locks and transaction resources.
127 This is the default value. The vast majority
128 of use cases should leave this value set.
129 * ``"commit"`` - call commit() on the connection,
130 to release locks and transaction resources.
131 A commit here may be desirable for databases that
132 cache query plans if a commit is emitted,
133 such as Microsoft SQL Server. However, this
134 value is more dangerous than 'rollback' because
135 any data changes present on the transaction
136 are committed unconditionally.
137 * ``None`` - don't do anything on the connection.
138 This setting may be appropriate if the database / DBAPI
139 works in pure "autocommit" mode at all times, or if
140 a custom reset handler is established using the
141 :meth:`.PoolEvents.reset` event handler.
142 * ``True`` - same as 'rollback', this is here for
143 backwards compatibility.
144 * ``False`` - same as None, this is here for
145 backwards compatibility.
147 For further customization of reset on return, the
148 :meth:`.PoolEvents.reset` event hook may be used which can perform
149 any connection activity desired on reset. (requires version 1.4.43
150 or greater)
152 .. seealso::
154 :ref:`pool_reset_on_return`
156 :param events: a list of 2-tuples, each of the form
157 ``(callable, target)`` which will be passed to :func:`.event.listen`
158 upon construction. Provided here so that event listeners
159 can be assigned via :func:`_sa.create_engine` before dialect-level
160 listeners are applied.
162 :param dialect: a :class:`.Dialect` that will handle the job
163 of calling rollback(), close(), or commit() on DBAPI connections.
164 If omitted, a built-in "stub" dialect is used. Applications that
165 make use of :func:`_sa.create_engine` should not use this parameter
166 as it is handled by the engine creation strategy.
168 .. versionadded:: 1.1 - ``dialect`` is now a public parameter
169 to the :class:`_pool.Pool`.
171 :param pre_ping: if True, the pool will emit a "ping" (typically
172 "SELECT 1", but is dialect-specific) on the connection
173 upon checkout, to test if the connection is alive or not. If not,
174 the connection is transparently re-connected and upon success, all
175 other pooled connections established prior to that timestamp are
176 invalidated. Requires that a dialect is passed as well to
177 interpret the disconnection error.
179 .. versionadded:: 1.2
181 """
182 if logging_name:
183 self.logging_name = self._orig_logging_name = logging_name
184 else:
185 self._orig_logging_name = None
187 log.instance_logger(self, echoflag=echo)
188 self._creator = creator
189 self._recycle = recycle
190 self._invalidate_time = 0
191 self._pre_ping = pre_ping
192 self._reset_on_return = util.symbol.parse_user_argument(
193 reset_on_return,
194 {
195 reset_rollback: ["rollback", True],
196 reset_none: ["none", None, False],
197 reset_commit: ["commit"],
198 },
199 "reset_on_return",
200 resolve_symbol_names=False,
201 )
203 self.echo = echo
205 if _dispatch:
206 self.dispatch._update(_dispatch, only_propagate=False)
207 if dialect:
208 self._dialect = dialect
209 if events:
210 for fn, target in events:
211 event.listen(self, target, fn)
213 @util.hybridproperty
214 def _is_asyncio(self):
215 return self._dialect.is_async
217 @property
218 def _creator(self):
219 return self.__dict__["_creator"]
221 @_creator.setter
222 def _creator(self, creator):
223 self.__dict__["_creator"] = creator
224 self._invoke_creator = self._should_wrap_creator(creator)
226 def _should_wrap_creator(self, creator):
227 """Detect if creator accepts a single argument, or is sent
228 as a legacy style no-arg function.
230 """
232 try:
233 argspec = util.get_callable_argspec(self._creator, no_self=True)
234 except TypeError:
235 return lambda crec: creator()
237 defaulted = argspec[3] is not None and len(argspec[3]) or 0
238 positionals = len(argspec[0]) - defaulted
240 # look for the exact arg signature that DefaultStrategy
241 # sends us
242 if (argspec[0], argspec[3]) == (["connection_record"], (None,)):
243 return creator
244 # or just a single positional
245 elif positionals == 1:
246 return creator
247 # all other cases, just wrap and assume legacy "creator" callable
248 # thing
249 else:
250 return lambda crec: creator()
252 def _close_connection(self, connection, terminate=False):
253 self.logger.debug(
254 "%s connection %r",
255 "Hard-closing" if terminate else "Closing",
256 connection,
257 )
258 try:
259 if terminate:
260 self._dialect.do_terminate(connection)
261 else:
262 self._dialect.do_close(connection)
263 except BaseException as e:
264 self.logger.error(
265 "Exception closing connection %r", connection, exc_info=True
266 )
267 if not isinstance(e, Exception):
268 raise
270 def _create_connection(self):
271 """Called by subclasses to create a new ConnectionRecord."""
273 return _ConnectionRecord(self)
275 def _invalidate(self, connection, exception=None, _checkin=True):
276 """Mark all connections established within the generation
277 of the given connection as invalidated.
279 If this pool's last invalidate time is before when the given
280 connection was created, update the timestamp til now. Otherwise,
281 no action is performed.
283 Connections with a start time prior to this pool's invalidation
284 time will be recycled upon next checkout.
285 """
286 rec = getattr(connection, "_connection_record", None)
287 if not rec or self._invalidate_time < rec.starttime:
288 self._invalidate_time = time.time()
289 if _checkin and getattr(connection, "is_valid", False):
290 connection.invalidate(exception)
292 def recreate(self):
293 """Return a new :class:`_pool.Pool`, of the same class as this one
294 and configured with identical creation arguments.
296 This method is used in conjunction with :meth:`dispose`
297 to close out an entire :class:`_pool.Pool` and create a new one in
298 its place.
300 """
302 raise NotImplementedError()
304 def dispose(self):
305 """Dispose of this pool.
307 This method leaves the possibility of checked-out connections
308 remaining open, as it only affects connections that are
309 idle in the pool.
311 .. seealso::
313 :meth:`Pool.recreate`
315 """
317 raise NotImplementedError()
319 def connect(self):
320 """Return a DBAPI connection from the pool.
322 The connection is instrumented such that when its
323 ``close()`` method is called, the connection will be returned to
324 the pool.
326 """
327 return _ConnectionFairy._checkout(self)
329 def _return_conn(self, record):
330 """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`.
332 This method is called when an instrumented DBAPI connection
333 has its ``close()`` method called.
335 """
336 self._do_return_conn(record)
338 def _do_get(self):
339 """Implementation for :meth:`get`, supplied by subclasses."""
341 raise NotImplementedError()
343 def _do_return_conn(self, conn):
344 """Implementation for :meth:`return_conn`, supplied by subclasses."""
346 raise NotImplementedError()
348 def status(self):
349 raise NotImplementedError()
352class _ConnectionRecord(object):
354 """Internal object which maintains an individual DBAPI connection
355 referenced by a :class:`_pool.Pool`.
357 The :class:`._ConnectionRecord` object always exists for any particular
358 DBAPI connection whether or not that DBAPI connection has been
359 "checked out". This is in contrast to the :class:`._ConnectionFairy`
360 which is only a public facade to the DBAPI connection while it is checked
361 out.
363 A :class:`._ConnectionRecord` may exist for a span longer than that
364 of a single DBAPI connection. For example, if the
365 :meth:`._ConnectionRecord.invalidate`
366 method is called, the DBAPI connection associated with this
367 :class:`._ConnectionRecord`
368 will be discarded, but the :class:`._ConnectionRecord` may be used again,
369 in which case a new DBAPI connection is produced when the
370 :class:`_pool.Pool`
371 next uses this record.
373 The :class:`._ConnectionRecord` is delivered along with connection
374 pool events, including :meth:`_events.PoolEvents.connect` and
375 :meth:`_events.PoolEvents.checkout`, however :class:`._ConnectionRecord`
376 still
377 remains an internal object whose API and internals may change.
379 .. seealso::
381 :class:`._ConnectionFairy`
383 """
385 def __init__(self, pool, connect=True):
386 self.__pool = pool
387 if connect:
388 self.__connect()
389 self.finalize_callback = deque()
391 fresh = False
393 fairy_ref = None
395 starttime = None
397 dbapi_connection = None
398 """A reference to the actual DBAPI connection being tracked.
400 May be ``None`` if this :class:`._ConnectionRecord` has been marked
401 as invalidated; a new DBAPI connection may replace it if the owning
402 pool calls upon this :class:`._ConnectionRecord` to reconnect.
404 For adapted drivers, like the Asyncio implementations, this is a
405 :class:`.AdaptedConnection` that adapts the driver connection
406 to the DBAPI protocol.
407 Use :attr:`._ConnectionRecord.driver_connection` to obtain the
408 connection objected returned by the driver.
410 .. versionadded:: 1.4.24
412 """
414 @property
415 def driver_connection(self):
416 """The connection object as returned by the driver after a connect.
418 For normal sync drivers that support the DBAPI protocol, this object
419 is the same as the one referenced by
420 :attr:`._ConnectionRecord.dbapi_connection`.
422 For adapted drivers, like the Asyncio ones, this is the actual object
423 that was returned by the driver ``connect`` call.
425 As :attr:`._ConnectionRecord.dbapi_connection` it may be ``None``
426 if this :class:`._ConnectionRecord` has been marked as invalidated.
428 .. versionadded:: 1.4.24
430 """
432 if self.dbapi_connection is None:
433 return None
434 else:
435 return self.__pool._dialect.get_driver_connection(
436 self.dbapi_connection
437 )
439 @property
440 def connection(self):
441 """An alias to :attr:`._ConnectionRecord.dbapi_connection`.
443 This alias is deprecated, please use the new name.
445 .. deprecated:: 1.4.24
447 """
448 return self.dbapi_connection
450 @connection.setter
451 def connection(self, value):
452 self.dbapi_connection = value
454 _soft_invalidate_time = 0
456 @util.memoized_property
457 def info(self):
458 """The ``.info`` dictionary associated with the DBAPI connection.
460 This dictionary is shared among the :attr:`._ConnectionFairy.info`
461 and :attr:`_engine.Connection.info` accessors.
463 .. note::
465 The lifespan of this dictionary is linked to the
466 DBAPI connection itself, meaning that it is **discarded** each time
467 the DBAPI connection is closed and/or invalidated. The
468 :attr:`._ConnectionRecord.record_info` dictionary remains
469 persistent throughout the lifespan of the
470 :class:`._ConnectionRecord` container.
472 """
473 return {}
475 @util.memoized_property
476 def record_info(self):
477 """An "info' dictionary associated with the connection record
478 itself.
480 Unlike the :attr:`._ConnectionRecord.info` dictionary, which is linked
481 to the lifespan of the DBAPI connection, this dictionary is linked
482 to the lifespan of the :class:`._ConnectionRecord` container itself
483 and will remain persistent throughout the life of the
484 :class:`._ConnectionRecord`.
486 .. versionadded:: 1.1
488 """
489 return {}
491 @classmethod
492 def checkout(cls, pool):
493 rec = pool._do_get()
494 try:
495 dbapi_connection = rec.get_connection()
496 except BaseException as err:
497 with util.safe_reraise():
498 rec._checkin_failed(err, _fairy_was_created=False)
500 # never called, this is for code linters
501 raise
503 echo = pool._should_log_debug()
504 fairy = _ConnectionFairy(dbapi_connection, rec, echo)
506 rec.fairy_ref = ref = weakref.ref(
507 fairy,
508 lambda ref: _finalize_fairy
509 and _finalize_fairy(
510 None, rec, pool, ref, echo, transaction_was_reset=False
511 ),
512 )
513 _strong_ref_connection_records[ref] = rec
514 if echo:
515 pool.logger.debug(
516 "Connection %r checked out from pool", dbapi_connection
517 )
518 return fairy
520 def _checkin_failed(self, err, _fairy_was_created=True):
521 self.invalidate(e=err)
522 self.checkin(
523 _fairy_was_created=_fairy_was_created,
524 )
526 def checkin(self, _fairy_was_created=True):
527 if self.fairy_ref is None and _fairy_was_created:
528 # _fairy_was_created is False for the initial get connection phase;
529 # meaning there was no _ConnectionFairy and we must unconditionally
530 # do a checkin.
531 #
532 # otherwise, if fairy_was_created==True, if fairy_ref is None here
533 # that means we were checked in already, so this looks like
534 # a double checkin.
535 util.warn("Double checkin attempted on %s" % self)
536 return
537 self.fairy_ref = None
538 connection = self.dbapi_connection
539 pool = self.__pool
540 while self.finalize_callback:
541 finalizer = self.finalize_callback.pop()
542 finalizer(connection)
543 if pool.dispatch.checkin:
544 pool.dispatch.checkin(connection, self)
546 pool._return_conn(self)
548 @property
549 def in_use(self):
550 return self.fairy_ref is not None
552 @property
553 def last_connect_time(self):
554 return self.starttime
556 def close(self):
557 if self.dbapi_connection is not None:
558 self.__close()
560 def invalidate(self, e=None, soft=False):
561 """Invalidate the DBAPI connection held by this
562 :class:`._ConnectionRecord`.
564 This method is called for all connection invalidations, including
565 when the :meth:`._ConnectionFairy.invalidate` or
566 :meth:`_engine.Connection.invalidate` methods are called,
567 as well as when any
568 so-called "automatic invalidation" condition occurs.
570 :param e: an exception object indicating a reason for the
571 invalidation.
573 :param soft: if True, the connection isn't closed; instead, this
574 connection will be recycled on next checkout.
576 .. versionadded:: 1.0.3
578 .. seealso::
580 :ref:`pool_connection_invalidation`
582 """
583 # already invalidated
584 if self.dbapi_connection is None:
585 return
586 if soft:
587 self.__pool.dispatch.soft_invalidate(
588 self.dbapi_connection, self, e
589 )
590 else:
591 self.__pool.dispatch.invalidate(self.dbapi_connection, self, e)
592 if e is not None:
593 self.__pool.logger.info(
594 "%sInvalidate connection %r (reason: %s:%s)",
595 "Soft " if soft else "",
596 self.dbapi_connection,
597 e.__class__.__name__,
598 e,
599 )
600 else:
601 self.__pool.logger.info(
602 "%sInvalidate connection %r",
603 "Soft " if soft else "",
604 self.dbapi_connection,
605 )
607 if soft:
608 self._soft_invalidate_time = time.time()
609 else:
610 self.__close(terminate=True)
611 self.dbapi_connection = None
613 def get_connection(self):
614 recycle = False
616 # NOTE: the various comparisons here are assuming that measurable time
617 # passes between these state changes. however, time.time() is not
618 # guaranteed to have sub-second precision. comparisons of
619 # "invalidation time" to "starttime" should perhaps use >= so that the
620 # state change can take place assuming no measurable time has passed,
621 # however this does not guarantee correct behavior here as if time
622 # continues to not pass, it will try to reconnect repeatedly until
623 # these timestamps diverge, so in that sense using > is safer. Per
624 # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be
625 # within 16 milliseconds accuracy, so unit tests for connection
626 # invalidation need a sleep of at least this long between initial start
627 # time and invalidation for the logic below to work reliably.
628 if self.dbapi_connection is None:
629 self.info.clear()
630 self.__connect()
631 elif (
632 self.__pool._recycle > -1
633 and time.time() - self.starttime > self.__pool._recycle
634 ):
635 self.__pool.logger.info(
636 "Connection %r exceeded timeout; recycling",
637 self.dbapi_connection,
638 )
639 recycle = True
640 elif self.__pool._invalidate_time > self.starttime:
641 self.__pool.logger.info(
642 "Connection %r invalidated due to pool invalidation; "
643 + "recycling",
644 self.dbapi_connection,
645 )
646 recycle = True
647 elif self._soft_invalidate_time > self.starttime:
648 self.__pool.logger.info(
649 "Connection %r invalidated due to local soft invalidation; "
650 + "recycling",
651 self.dbapi_connection,
652 )
653 recycle = True
655 if recycle:
656 self.__close(terminate=True)
657 self.info.clear()
659 self.__connect()
660 return self.dbapi_connection
662 def _is_hard_or_soft_invalidated(self):
663 return (
664 self.dbapi_connection is None
665 or self.__pool._invalidate_time > self.starttime
666 or (self._soft_invalidate_time > self.starttime)
667 )
669 def __close(self, terminate=False):
670 self.finalize_callback.clear()
671 if self.__pool.dispatch.close:
672 self.__pool.dispatch.close(self.dbapi_connection, self)
673 self.__pool._close_connection(
674 self.dbapi_connection, terminate=terminate
675 )
676 self.dbapi_connection = None
678 def __connect(self):
679 pool = self.__pool
681 # ensure any existing connection is removed, so that if
682 # creator fails, this attribute stays None
683 self.dbapi_connection = None
684 try:
685 self.starttime = time.time()
686 self.dbapi_connection = connection = pool._invoke_creator(self)
687 pool.logger.debug("Created new connection %r", connection)
688 self.fresh = True
689 except BaseException as e:
690 with util.safe_reraise():
691 pool.logger.debug("Error on connect(): %s", e)
692 else:
693 # in SQLAlchemy 1.4 the first_connect event is not used by
694 # the engine, so this will usually not be set
695 if pool.dispatch.first_connect:
696 pool.dispatch.first_connect.for_modify(
697 pool.dispatch
698 ).exec_once_unless_exception(self.dbapi_connection, self)
700 # init of the dialect now takes place within the connect
701 # event, so ensure a mutex is used on the first run
702 pool.dispatch.connect.for_modify(
703 pool.dispatch
704 )._exec_w_sync_on_first_run(self.dbapi_connection, self)
707def _finalize_fairy(
708 dbapi_connection,
709 connection_record,
710 pool,
711 ref, # this is None when called directly, not by the gc
712 echo,
713 transaction_was_reset=False,
714 fairy=None,
715):
716 """Cleanup for a :class:`._ConnectionFairy` whether or not it's already
717 been garbage collected.
719 When using an async dialect no IO can happen here (without using
720 a dedicated thread), since this is called outside the greenlet
721 context and with an already running loop. In this case function
722 will only log a message and raise a warning.
723 """
725 if ref:
726 _strong_ref_connection_records.pop(ref, None)
727 elif fairy:
728 _strong_ref_connection_records.pop(weakref.ref(fairy), None)
730 if ref is not None:
731 if connection_record.fairy_ref is not ref:
732 return
733 assert dbapi_connection is None
734 dbapi_connection = connection_record.dbapi_connection
736 # null pool is not _is_asyncio but can be used also with async dialects
737 dont_restore_gced = (
738 pool._dialect.is_async and not pool._dialect.has_terminate
739 )
741 if dont_restore_gced:
742 detach = not connection_record or ref
743 can_manipulate_connection = not ref
744 else:
745 detach = not connection_record
746 can_manipulate_connection = True
748 if dbapi_connection is not None:
749 if connection_record and echo:
750 pool.logger.debug(
751 "Connection %r being returned to pool",
752 dbapi_connection,
753 )
755 try:
756 fairy = fairy or _ConnectionFairy(
757 dbapi_connection,
758 connection_record,
759 echo,
760 )
761 assert fairy.dbapi_connection is dbapi_connection
762 if can_manipulate_connection:
763 fairy._reset(pool, transaction_was_reset)
765 if detach:
766 if connection_record:
767 fairy._pool = pool
768 fairy.detach()
770 if can_manipulate_connection:
771 if pool.dispatch.close_detached:
772 pool.dispatch.close_detached(dbapi_connection)
774 pool._close_connection(dbapi_connection)
775 else:
776 message = (
777 "The garbage collector is trying to clean up "
778 "connection %r. This feature is unsupported on "
779 "unsupported on asyncio "
780 'dbapis that lack a "terminate" feature, '
781 "since no IO can be performed at this stage to "
782 "reset the connection. Please close out all "
783 "connections when they are no longer used, calling "
784 "``close()`` or using a context manager to "
785 "manage their lifetime."
786 ) % dbapi_connection
787 pool.logger.error(message)
788 util.warn(message)
790 except BaseException as e:
791 pool.logger.error(
792 "Exception during reset or similar", exc_info=True
793 )
794 if connection_record:
795 connection_record.invalidate(e=e)
796 if not isinstance(e, Exception):
797 raise
799 if connection_record and connection_record.fairy_ref is not None:
800 connection_record.checkin()
803# a dictionary of the _ConnectionFairy weakrefs to _ConnectionRecord, so that
804# GC under pypy will call ConnectionFairy finalizers. linked directly to the
805# weakref that will empty itself when collected so that it should not create
806# any unmanaged memory references.
807_strong_ref_connection_records = {}
810class _ConnectionFairy(object):
812 """Proxies a DBAPI connection and provides return-on-dereference
813 support.
815 This is an internal object used by the :class:`_pool.Pool` implementation
816 to provide context management to a DBAPI connection delivered by
817 that :class:`_pool.Pool`.
819 The name "fairy" is inspired by the fact that the
820 :class:`._ConnectionFairy` object's lifespan is transitory, as it lasts
821 only for the length of a specific DBAPI connection being checked out from
822 the pool, and additionally that as a transparent proxy, it is mostly
823 invisible.
825 .. seealso::
827 :class:`._ConnectionRecord`
829 """
831 def __init__(self, dbapi_connection, connection_record, echo):
832 self.dbapi_connection = dbapi_connection
833 self._connection_record = connection_record
834 self._echo = echo
836 dbapi_connection = None
837 """A reference to the actual DBAPI connection being tracked.
839 .. versionadded:: 1.4.24
841 .. seealso::
843 :attr:`._ConnectionFairy.driver_connection`
845 :attr:`._ConnectionRecord.dbapi_connection`
847 :ref:`faq_dbapi_connection`
849 """
851 _connection_record = None
852 """A reference to the :class:`._ConnectionRecord` object associated
853 with the DBAPI connection.
855 This is currently an internal accessor which is subject to change.
857 """
859 @property
860 def driver_connection(self):
861 """The connection object as returned by the driver after a connect.
863 .. versionadded:: 1.4.24
865 .. seealso::
867 :attr:`._ConnectionFairy.dbapi_connection`
869 :attr:`._ConnectionRecord.driver_connection`
871 :ref:`faq_dbapi_connection`
873 """
874 return self._connection_record.driver_connection
876 @property
877 def connection(self):
878 """An alias to :attr:`._ConnectionFairy.dbapi_connection`.
880 This alias is deprecated, please use the new name.
882 .. deprecated:: 1.4.24
884 """
885 return self.dbapi_connection
887 @connection.setter
888 def connection(self, value):
889 self.dbapi_connection = value
891 @classmethod
892 def _checkout(cls, pool, threadconns=None, fairy=None):
893 if not fairy:
894 fairy = _ConnectionRecord.checkout(pool)
896 fairy._pool = pool
897 fairy._counter = 0
899 if threadconns is not None:
900 threadconns.current = weakref.ref(fairy)
902 if fairy.dbapi_connection is None:
903 raise exc.InvalidRequestError("This connection is closed")
904 fairy._counter += 1
905 if (
906 not pool.dispatch.checkout and not pool._pre_ping
907 ) or fairy._counter != 1:
908 return fairy
910 # Pool listeners can trigger a reconnection on checkout, as well
911 # as the pre-pinger.
912 # there are three attempts made here, but note that if the database
913 # is not accessible from a connection standpoint, those won't proceed
914 # here.
915 attempts = 2
917 while attempts > 0:
918 connection_is_fresh = fairy._connection_record.fresh
919 fairy._connection_record.fresh = False
920 try:
921 if pool._pre_ping:
922 if not connection_is_fresh:
923 if fairy._echo:
924 pool.logger.debug(
925 "Pool pre-ping on connection %s",
926 fairy.dbapi_connection,
927 )
928 result = pool._dialect.do_ping(fairy.dbapi_connection)
929 if not result:
930 if fairy._echo:
931 pool.logger.debug(
932 "Pool pre-ping on connection %s failed, "
933 "will invalidate pool",
934 fairy.dbapi_connection,
935 )
936 raise exc.InvalidatePoolError()
937 elif fairy._echo:
938 pool.logger.debug(
939 "Connection %s is fresh, skipping pre-ping",
940 fairy.dbapi_connection,
941 )
943 pool.dispatch.checkout(
944 fairy.dbapi_connection, fairy._connection_record, fairy
945 )
946 return fairy
947 except exc.DisconnectionError as e:
948 if e.invalidate_pool:
949 pool.logger.info(
950 "Disconnection detected on checkout, "
951 "invalidating all pooled connections prior to "
952 "current timestamp (reason: %r)",
953 e,
954 )
955 fairy._connection_record.invalidate(e)
956 pool._invalidate(fairy, e, _checkin=False)
957 else:
958 pool.logger.info(
959 "Disconnection detected on checkout, "
960 "invalidating individual connection %s (reason: %r)",
961 fairy.dbapi_connection,
962 e,
963 )
964 fairy._connection_record.invalidate(e)
965 try:
966 fairy.dbapi_connection = (
967 fairy._connection_record.get_connection()
968 )
969 except BaseException as err:
970 with util.safe_reraise():
971 fairy._connection_record._checkin_failed(
972 err,
973 _fairy_was_created=True,
974 )
976 # prevent _ConnectionFairy from being carried
977 # in the stack trace. Do this after the
978 # connection record has been checked in, so that
979 # if the del triggers a finalize fairy, it won't
980 # try to checkin a second time.
981 del fairy
983 attempts -= 1
984 except BaseException as be_outer:
985 with util.safe_reraise():
986 rec = fairy._connection_record
987 if rec is not None:
988 rec._checkin_failed(
989 be_outer,
990 _fairy_was_created=True,
991 )
993 # prevent _ConnectionFairy from being carried
994 # in the stack trace, see above
995 del fairy
997 # never called, this is for code linters
998 raise
1000 pool.logger.info("Reconnection attempts exhausted on checkout")
1001 fairy.invalidate()
1002 raise exc.InvalidRequestError("This connection is closed")
1004 def _checkout_existing(self):
1005 return _ConnectionFairy._checkout(self._pool, fairy=self)
1007 def _checkin(self, transaction_was_reset=False):
1008 _finalize_fairy(
1009 self.dbapi_connection,
1010 self._connection_record,
1011 self._pool,
1012 None,
1013 self._echo,
1014 transaction_was_reset=transaction_was_reset,
1015 fairy=self,
1016 )
1017 self.dbapi_connection = None
1018 self._connection_record = None
1020 _close = _checkin
1022 def _reset(self, pool, transaction_was_reset=False):
1023 if pool.dispatch.reset:
1024 pool.dispatch.reset(self, self._connection_record)
1025 if pool._reset_on_return is reset_rollback:
1026 if transaction_was_reset:
1027 if self._echo:
1028 pool.logger.debug(
1029 "Connection %s reset, transaction already reset",
1030 self.dbapi_connection,
1031 )
1032 else:
1033 if self._echo:
1034 pool.logger.debug(
1035 "Connection %s rollback-on-return",
1036 self.dbapi_connection,
1037 )
1038 pool._dialect.do_rollback(self)
1039 elif pool._reset_on_return is reset_commit:
1040 if self._echo:
1041 pool.logger.debug(
1042 "Connection %s commit-on-return",
1043 self.dbapi_connection,
1044 )
1045 pool._dialect.do_commit(self)
1047 @property
1048 def _logger(self):
1049 return self._pool.logger
1051 @property
1052 def is_valid(self):
1053 """Return True if this :class:`._ConnectionFairy` still refers
1054 to an active DBAPI connection."""
1056 return self.dbapi_connection is not None
1058 @util.memoized_property
1059 def info(self):
1060 """Info dictionary associated with the underlying DBAPI connection
1061 referred to by this :class:`.ConnectionFairy`, allowing user-defined
1062 data to be associated with the connection.
1064 The data here will follow along with the DBAPI connection including
1065 after it is returned to the connection pool and used again
1066 in subsequent instances of :class:`._ConnectionFairy`. It is shared
1067 with the :attr:`._ConnectionRecord.info` and
1068 :attr:`_engine.Connection.info`
1069 accessors.
1071 The dictionary associated with a particular DBAPI connection is
1072 discarded when the connection itself is discarded.
1074 """
1075 return self._connection_record.info
1077 @property
1078 def record_info(self):
1079 """Info dictionary associated with the :class:`._ConnectionRecord
1080 container referred to by this :class:`.ConnectionFairy`.
1082 Unlike the :attr:`._ConnectionFairy.info` dictionary, the lifespan
1083 of this dictionary is persistent across connections that are
1084 disconnected and/or invalidated within the lifespan of a
1085 :class:`._ConnectionRecord`.
1087 .. versionadded:: 1.1
1089 """
1090 if self._connection_record:
1091 return self._connection_record.record_info
1092 else:
1093 return None
1095 def invalidate(self, e=None, soft=False):
1096 """Mark this connection as invalidated.
1098 This method can be called directly, and is also called as a result
1099 of the :meth:`_engine.Connection.invalidate` method. When invoked,
1100 the DBAPI connection is immediately closed and discarded from
1101 further use by the pool. The invalidation mechanism proceeds
1102 via the :meth:`._ConnectionRecord.invalidate` internal method.
1104 :param e: an exception object indicating a reason for the invalidation.
1106 :param soft: if True, the connection isn't closed; instead, this
1107 connection will be recycled on next checkout.
1109 .. versionadded:: 1.0.3
1111 .. seealso::
1113 :ref:`pool_connection_invalidation`
1115 """
1117 if self.dbapi_connection is None:
1118 util.warn("Can't invalidate an already-closed connection.")
1119 return
1120 if self._connection_record:
1121 self._connection_record.invalidate(e=e, soft=soft)
1122 if not soft:
1123 self.dbapi_connection = None
1124 self._checkin()
1126 def cursor(self, *args, **kwargs):
1127 """Return a new DBAPI cursor for the underlying connection.
1129 This method is a proxy for the ``connection.cursor()`` DBAPI
1130 method.
1132 """
1133 return self.dbapi_connection.cursor(*args, **kwargs)
1135 def __getattr__(self, key):
1136 return getattr(self.dbapi_connection, key)
1138 def detach(self):
1139 """Separate this connection from its Pool.
1141 This means that the connection will no longer be returned to the
1142 pool when closed, and will instead be literally closed. The
1143 containing ConnectionRecord is separated from the DB-API connection,
1144 and will create a new connection when next used.
1146 Note that any overall connection limiting constraints imposed by a
1147 Pool implementation may be violated after a detach, as the detached
1148 connection is removed from the pool's knowledge and control.
1149 """
1151 if self._connection_record is not None:
1152 rec = self._connection_record
1153 rec.fairy_ref = None
1154 rec.dbapi_connection = None
1155 # TODO: should this be _return_conn?
1156 self._pool._do_return_conn(self._connection_record)
1157 self.info = self.info.copy()
1158 self._connection_record = None
1160 if self._pool.dispatch.detach:
1161 self._pool.dispatch.detach(self.dbapi_connection, rec)
1163 def close(self):
1164 self._counter -= 1
1165 if self._counter == 0:
1166 self._checkin()
1168 def _close_special(self, transaction_reset=False):
1169 self._counter -= 1
1170 if self._counter == 0:
1171 self._checkin(transaction_was_reset=transaction_reset)