Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/pool/base.py: 33%
396 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# sqlalchemy/pool.py
2# Copyright (C) 2005-2022 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 Exception:
264 self.logger.error(
265 "Exception closing connection %r", connection, exc_info=True
266 )
268 def _create_connection(self):
269 """Called by subclasses to create a new ConnectionRecord."""
271 return _ConnectionRecord(self)
273 def _invalidate(self, connection, exception=None, _checkin=True):
274 """Mark all connections established within the generation
275 of the given connection as invalidated.
277 If this pool's last invalidate time is before when the given
278 connection was created, update the timestamp til now. Otherwise,
279 no action is performed.
281 Connections with a start time prior to this pool's invalidation
282 time will be recycled upon next checkout.
283 """
284 rec = getattr(connection, "_connection_record", None)
285 if not rec or self._invalidate_time < rec.starttime:
286 self._invalidate_time = time.time()
287 if _checkin and getattr(connection, "is_valid", False):
288 connection.invalidate(exception)
290 def recreate(self):
291 """Return a new :class:`_pool.Pool`, of the same class as this one
292 and configured with identical creation arguments.
294 This method is used in conjunction with :meth:`dispose`
295 to close out an entire :class:`_pool.Pool` and create a new one in
296 its place.
298 """
300 raise NotImplementedError()
302 def dispose(self):
303 """Dispose of this pool.
305 This method leaves the possibility of checked-out connections
306 remaining open, as it only affects connections that are
307 idle in the pool.
309 .. seealso::
311 :meth:`Pool.recreate`
313 """
315 raise NotImplementedError()
317 def connect(self):
318 """Return a DBAPI connection from the pool.
320 The connection is instrumented such that when its
321 ``close()`` method is called, the connection will be returned to
322 the pool.
324 """
325 return _ConnectionFairy._checkout(self)
327 def _return_conn(self, record):
328 """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`.
330 This method is called when an instrumented DBAPI connection
331 has its ``close()`` method called.
333 """
334 self._do_return_conn(record)
336 def _do_get(self):
337 """Implementation for :meth:`get`, supplied by subclasses."""
339 raise NotImplementedError()
341 def _do_return_conn(self, conn):
342 """Implementation for :meth:`return_conn`, supplied by subclasses."""
344 raise NotImplementedError()
346 def status(self):
347 raise NotImplementedError()
350class _ConnectionRecord(object):
352 """Internal object which maintains an individual DBAPI connection
353 referenced by a :class:`_pool.Pool`.
355 The :class:`._ConnectionRecord` object always exists for any particular
356 DBAPI connection whether or not that DBAPI connection has been
357 "checked out". This is in contrast to the :class:`._ConnectionFairy`
358 which is only a public facade to the DBAPI connection while it is checked
359 out.
361 A :class:`._ConnectionRecord` may exist for a span longer than that
362 of a single DBAPI connection. For example, if the
363 :meth:`._ConnectionRecord.invalidate`
364 method is called, the DBAPI connection associated with this
365 :class:`._ConnectionRecord`
366 will be discarded, but the :class:`._ConnectionRecord` may be used again,
367 in which case a new DBAPI connection is produced when the
368 :class:`_pool.Pool`
369 next uses this record.
371 The :class:`._ConnectionRecord` is delivered along with connection
372 pool events, including :meth:`_events.PoolEvents.connect` and
373 :meth:`_events.PoolEvents.checkout`, however :class:`._ConnectionRecord`
374 still
375 remains an internal object whose API and internals may change.
377 .. seealso::
379 :class:`._ConnectionFairy`
381 """
383 def __init__(self, pool, connect=True):
384 self.__pool = pool
385 if connect:
386 self.__connect()
387 self.finalize_callback = deque()
389 fresh = False
391 fairy_ref = None
393 starttime = None
395 dbapi_connection = None
396 """A reference to the actual DBAPI connection being tracked.
398 May be ``None`` if this :class:`._ConnectionRecord` has been marked
399 as invalidated; a new DBAPI connection may replace it if the owning
400 pool calls upon this :class:`._ConnectionRecord` to reconnect.
402 For adapted drivers, like the Asyncio implementations, this is a
403 :class:`.AdaptedConnection` that adapts the driver connection
404 to the DBAPI protocol.
405 Use :attr:`._ConnectionRecord.driver_connection` to obtain the
406 connection objected returned by the driver.
408 .. versionadded:: 1.4.24
410 """
412 @property
413 def driver_connection(self):
414 """The connection object as returned by the driver after a connect.
416 For normal sync drivers that support the DBAPI protocol, this object
417 is the same as the one referenced by
418 :attr:`._ConnectionRecord.dbapi_connection`.
420 For adapted drivers, like the Asyncio ones, this is the actual object
421 that was returned by the driver ``connect`` call.
423 As :attr:`._ConnectionRecord.dbapi_connection` it may be ``None``
424 if this :class:`._ConnectionRecord` has been marked as invalidated.
426 .. versionadded:: 1.4.24
428 """
430 if self.dbapi_connection is None:
431 return None
432 else:
433 return self.__pool._dialect.get_driver_connection(
434 self.dbapi_connection
435 )
437 @property
438 def connection(self):
439 """An alias to :attr:`._ConnectionRecord.dbapi_connection`.
441 This alias is deprecated, please use the new name.
443 .. deprecated:: 1.4.24
445 """
446 return self.dbapi_connection
448 @connection.setter
449 def connection(self, value):
450 self.dbapi_connection = value
452 _soft_invalidate_time = 0
454 @util.memoized_property
455 def info(self):
456 """The ``.info`` dictionary associated with the DBAPI connection.
458 This dictionary is shared among the :attr:`._ConnectionFairy.info`
459 and :attr:`_engine.Connection.info` accessors.
461 .. note::
463 The lifespan of this dictionary is linked to the
464 DBAPI connection itself, meaning that it is **discarded** each time
465 the DBAPI connection is closed and/or invalidated. The
466 :attr:`._ConnectionRecord.record_info` dictionary remains
467 persistent throughout the lifespan of the
468 :class:`._ConnectionRecord` container.
470 """
471 return {}
473 @util.memoized_property
474 def record_info(self):
475 """An "info' dictionary associated with the connection record
476 itself.
478 Unlike the :attr:`._ConnectionRecord.info` dictionary, which is linked
479 to the lifespan of the DBAPI connection, this dictionary is linked
480 to the lifespan of the :class:`._ConnectionRecord` container itself
481 and will remain persistent throughout the life of the
482 :class:`._ConnectionRecord`.
484 .. versionadded:: 1.1
486 """
487 return {}
489 @classmethod
490 def checkout(cls, pool):
491 rec = pool._do_get()
492 try:
493 dbapi_connection = rec.get_connection()
494 except Exception as err:
495 with util.safe_reraise():
496 rec._checkin_failed(err, _fairy_was_created=False)
497 echo = pool._should_log_debug()
498 fairy = _ConnectionFairy(dbapi_connection, rec, echo)
500 rec.fairy_ref = ref = weakref.ref(
501 fairy,
502 lambda ref: _finalize_fairy
503 and _finalize_fairy(
504 None, rec, pool, ref, echo, transaction_was_reset=False
505 ),
506 )
507 _strong_ref_connection_records[ref] = rec
508 if echo:
509 pool.logger.debug(
510 "Connection %r checked out from pool", dbapi_connection
511 )
512 return fairy
514 def _checkin_failed(self, err, _fairy_was_created=True):
515 self.invalidate(e=err)
516 self.checkin(
517 _fairy_was_created=_fairy_was_created,
518 )
520 def checkin(self, _fairy_was_created=True):
521 if self.fairy_ref is None and _fairy_was_created:
522 # _fairy_was_created is False for the initial get connection phase;
523 # meaning there was no _ConnectionFairy and we must unconditionally
524 # do a checkin.
525 #
526 # otherwise, if fairy_was_created==True, if fairy_ref is None here
527 # that means we were checked in already, so this looks like
528 # a double checkin.
529 util.warn("Double checkin attempted on %s" % self)
530 return
531 self.fairy_ref = None
532 connection = self.dbapi_connection
533 pool = self.__pool
534 while self.finalize_callback:
535 finalizer = self.finalize_callback.pop()
536 finalizer(connection)
537 if pool.dispatch.checkin:
538 pool.dispatch.checkin(connection, self)
540 pool._return_conn(self)
542 @property
543 def in_use(self):
544 return self.fairy_ref is not None
546 @property
547 def last_connect_time(self):
548 return self.starttime
550 def close(self):
551 if self.dbapi_connection is not None:
552 self.__close()
554 def invalidate(self, e=None, soft=False):
555 """Invalidate the DBAPI connection held by this
556 :class:`._ConnectionRecord`.
558 This method is called for all connection invalidations, including
559 when the :meth:`._ConnectionFairy.invalidate` or
560 :meth:`_engine.Connection.invalidate` methods are called,
561 as well as when any
562 so-called "automatic invalidation" condition occurs.
564 :param e: an exception object indicating a reason for the
565 invalidation.
567 :param soft: if True, the connection isn't closed; instead, this
568 connection will be recycled on next checkout.
570 .. versionadded:: 1.0.3
572 .. seealso::
574 :ref:`pool_connection_invalidation`
576 """
577 # already invalidated
578 if self.dbapi_connection is None:
579 return
580 if soft:
581 self.__pool.dispatch.soft_invalidate(
582 self.dbapi_connection, self, e
583 )
584 else:
585 self.__pool.dispatch.invalidate(self.dbapi_connection, self, e)
586 if e is not None:
587 self.__pool.logger.info(
588 "%sInvalidate connection %r (reason: %s:%s)",
589 "Soft " if soft else "",
590 self.dbapi_connection,
591 e.__class__.__name__,
592 e,
593 )
594 else:
595 self.__pool.logger.info(
596 "%sInvalidate connection %r",
597 "Soft " if soft else "",
598 self.dbapi_connection,
599 )
601 if soft:
602 self._soft_invalidate_time = time.time()
603 else:
604 self.__close(terminate=True)
605 self.dbapi_connection = None
607 def get_connection(self):
608 recycle = False
610 # NOTE: the various comparisons here are assuming that measurable time
611 # passes between these state changes. however, time.time() is not
612 # guaranteed to have sub-second precision. comparisons of
613 # "invalidation time" to "starttime" should perhaps use >= so that the
614 # state change can take place assuming no measurable time has passed,
615 # however this does not guarantee correct behavior here as if time
616 # continues to not pass, it will try to reconnect repeatedly until
617 # these timestamps diverge, so in that sense using > is safer. Per
618 # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be
619 # within 16 milliseconds accuracy, so unit tests for connection
620 # invalidation need a sleep of at least this long between initial start
621 # time and invalidation for the logic below to work reliably.
622 if self.dbapi_connection is None:
623 self.info.clear()
624 self.__connect()
625 elif (
626 self.__pool._recycle > -1
627 and time.time() - self.starttime > self.__pool._recycle
628 ):
629 self.__pool.logger.info(
630 "Connection %r exceeded timeout; recycling",
631 self.dbapi_connection,
632 )
633 recycle = True
634 elif self.__pool._invalidate_time > self.starttime:
635 self.__pool.logger.info(
636 "Connection %r invalidated due to pool invalidation; "
637 + "recycling",
638 self.dbapi_connection,
639 )
640 recycle = True
641 elif self._soft_invalidate_time > self.starttime:
642 self.__pool.logger.info(
643 "Connection %r invalidated due to local soft invalidation; "
644 + "recycling",
645 self.dbapi_connection,
646 )
647 recycle = True
649 if recycle:
650 self.__close(terminate=True)
651 self.info.clear()
653 self.__connect()
654 return self.dbapi_connection
656 def _is_hard_or_soft_invalidated(self):
657 return (
658 self.dbapi_connection is None
659 or self.__pool._invalidate_time > self.starttime
660 or (self._soft_invalidate_time > self.starttime)
661 )
663 def __close(self, terminate=False):
664 self.finalize_callback.clear()
665 if self.__pool.dispatch.close:
666 self.__pool.dispatch.close(self.dbapi_connection, self)
667 self.__pool._close_connection(
668 self.dbapi_connection, terminate=terminate
669 )
670 self.dbapi_connection = None
672 def __connect(self):
673 pool = self.__pool
675 # ensure any existing connection is removed, so that if
676 # creator fails, this attribute stays None
677 self.dbapi_connection = None
678 try:
679 self.starttime = time.time()
680 self.dbapi_connection = connection = pool._invoke_creator(self)
681 pool.logger.debug("Created new connection %r", connection)
682 self.fresh = True
683 except Exception as e:
684 with util.safe_reraise():
685 pool.logger.debug("Error on connect(): %s", e)
686 else:
687 # in SQLAlchemy 1.4 the first_connect event is not used by
688 # the engine, so this will usually not be set
689 if pool.dispatch.first_connect:
690 pool.dispatch.first_connect.for_modify(
691 pool.dispatch
692 ).exec_once_unless_exception(self.dbapi_connection, self)
694 # init of the dialect now takes place within the connect
695 # event, so ensure a mutex is used on the first run
696 pool.dispatch.connect.for_modify(
697 pool.dispatch
698 )._exec_w_sync_on_first_run(self.dbapi_connection, self)
701def _finalize_fairy(
702 dbapi_connection,
703 connection_record,
704 pool,
705 ref, # this is None when called directly, not by the gc
706 echo,
707 transaction_was_reset=False,
708 fairy=None,
709):
710 """Cleanup for a :class:`._ConnectionFairy` whether or not it's already
711 been garbage collected.
713 When using an async dialect no IO can happen here (without using
714 a dedicated thread), since this is called outside the greenlet
715 context and with an already running loop. In this case function
716 will only log a message and raise a warning.
717 """
719 if ref:
720 _strong_ref_connection_records.pop(ref, None)
721 elif fairy:
722 _strong_ref_connection_records.pop(weakref.ref(fairy), None)
724 if ref is not None:
725 if connection_record.fairy_ref is not ref:
726 return
727 assert dbapi_connection is None
728 dbapi_connection = connection_record.dbapi_connection
730 # null pool is not _is_asyncio but can be used also with async dialects
731 dont_restore_gced = (
732 pool._dialect.is_async and not pool._dialect.has_terminate
733 )
735 if dont_restore_gced:
736 detach = not connection_record or ref
737 can_manipulate_connection = not ref
738 else:
739 detach = not connection_record
740 can_manipulate_connection = True
742 if dbapi_connection is not None:
743 if connection_record and echo:
744 pool.logger.debug(
745 "Connection %r being returned to pool",
746 dbapi_connection,
747 )
749 try:
750 fairy = fairy or _ConnectionFairy(
751 dbapi_connection,
752 connection_record,
753 echo,
754 )
755 assert fairy.dbapi_connection is dbapi_connection
756 if can_manipulate_connection:
757 fairy._reset(pool, transaction_was_reset)
759 if detach:
760 if connection_record:
761 fairy._pool = pool
762 fairy.detach()
764 if can_manipulate_connection:
765 if pool.dispatch.close_detached:
766 pool.dispatch.close_detached(dbapi_connection)
768 pool._close_connection(dbapi_connection)
769 else:
770 message = (
771 "The garbage collector is trying to clean up "
772 "connection %r. This feature is unsupported on "
773 "unsupported on asyncio "
774 'dbapis that lack a "terminate" feature, '
775 "since no IO can be performed at this stage to "
776 "reset the connection. Please close out all "
777 "connections when they are no longer used, calling "
778 "``close()`` or using a context manager to "
779 "manage their lifetime."
780 ) % dbapi_connection
781 pool.logger.error(message)
782 util.warn(message)
784 except BaseException as e:
785 pool.logger.error(
786 "Exception during reset or similar", exc_info=True
787 )
788 if connection_record:
789 connection_record.invalidate(e=e)
790 if not isinstance(e, Exception):
791 raise
793 if connection_record and connection_record.fairy_ref is not None:
794 connection_record.checkin()
797# a dictionary of the _ConnectionFairy weakrefs to _ConnectionRecord, so that
798# GC under pypy will call ConnectionFairy finalizers. linked directly to the
799# weakref that will empty itself when collected so that it should not create
800# any unmanaged memory references.
801_strong_ref_connection_records = {}
804class _ConnectionFairy(object):
806 """Proxies a DBAPI connection and provides return-on-dereference
807 support.
809 This is an internal object used by the :class:`_pool.Pool` implementation
810 to provide context management to a DBAPI connection delivered by
811 that :class:`_pool.Pool`.
813 The name "fairy" is inspired by the fact that the
814 :class:`._ConnectionFairy` object's lifespan is transitory, as it lasts
815 only for the length of a specific DBAPI connection being checked out from
816 the pool, and additionally that as a transparent proxy, it is mostly
817 invisible.
819 .. seealso::
821 :class:`._ConnectionRecord`
823 """
825 def __init__(self, dbapi_connection, connection_record, echo):
826 self.dbapi_connection = dbapi_connection
827 self._connection_record = connection_record
828 self._echo = echo
830 dbapi_connection = None
831 """A reference to the actual DBAPI connection being tracked.
833 .. versionadded:: 1.4.24
835 .. seealso::
837 :attr:`._ConnectionFairy.driver_connection`
839 :attr:`._ConnectionRecord.dbapi_connection`
841 :ref:`faq_dbapi_connection`
843 """
845 _connection_record = None
846 """A reference to the :class:`._ConnectionRecord` object associated
847 with the DBAPI connection.
849 This is currently an internal accessor which is subject to change.
851 """
853 @property
854 def driver_connection(self):
855 """The connection object as returned by the driver after a connect.
857 .. versionadded:: 1.4.24
859 .. seealso::
861 :attr:`._ConnectionFairy.dbapi_connection`
863 :attr:`._ConnectionRecord.driver_connection`
865 :ref:`faq_dbapi_connection`
867 """
868 return self._connection_record.driver_connection
870 @property
871 def connection(self):
872 """An alias to :attr:`._ConnectionFairy.dbapi_connection`.
874 This alias is deprecated, please use the new name.
876 .. deprecated:: 1.4.24
878 """
879 return self.dbapi_connection
881 @connection.setter
882 def connection(self, value):
883 self.dbapi_connection = value
885 @classmethod
886 def _checkout(cls, pool, threadconns=None, fairy=None):
887 if not fairy:
888 fairy = _ConnectionRecord.checkout(pool)
890 fairy._pool = pool
891 fairy._counter = 0
893 if threadconns is not None:
894 threadconns.current = weakref.ref(fairy)
896 if fairy.dbapi_connection is None:
897 raise exc.InvalidRequestError("This connection is closed")
898 fairy._counter += 1
899 if (
900 not pool.dispatch.checkout and not pool._pre_ping
901 ) or fairy._counter != 1:
902 return fairy
904 # Pool listeners can trigger a reconnection on checkout, as well
905 # as the pre-pinger.
906 # there are three attempts made here, but note that if the database
907 # is not accessible from a connection standpoint, those won't proceed
908 # here.
909 attempts = 2
910 while attempts > 0:
911 connection_is_fresh = fairy._connection_record.fresh
912 fairy._connection_record.fresh = False
913 try:
914 if pool._pre_ping:
915 if not connection_is_fresh:
916 if fairy._echo:
917 pool.logger.debug(
918 "Pool pre-ping on connection %s",
919 fairy.dbapi_connection,
920 )
921 result = pool._dialect.do_ping(fairy.dbapi_connection)
922 if not result:
923 if fairy._echo:
924 pool.logger.debug(
925 "Pool pre-ping on connection %s failed, "
926 "will invalidate pool",
927 fairy.dbapi_connection,
928 )
929 raise exc.InvalidatePoolError()
930 elif fairy._echo:
931 pool.logger.debug(
932 "Connection %s is fresh, skipping pre-ping",
933 fairy.dbapi_connection,
934 )
936 pool.dispatch.checkout(
937 fairy.dbapi_connection, fairy._connection_record, fairy
938 )
939 return fairy
940 except exc.DisconnectionError as e:
941 if e.invalidate_pool:
942 pool.logger.info(
943 "Disconnection detected on checkout, "
944 "invalidating all pooled connections prior to "
945 "current timestamp (reason: %r)",
946 e,
947 )
948 fairy._connection_record.invalidate(e)
949 pool._invalidate(fairy, e, _checkin=False)
950 else:
951 pool.logger.info(
952 "Disconnection detected on checkout, "
953 "invalidating individual connection %s (reason: %r)",
954 fairy.dbapi_connection,
955 e,
956 )
957 fairy._connection_record.invalidate(e)
958 try:
959 fairy.dbapi_connection = (
960 fairy._connection_record.get_connection()
961 )
962 except Exception as err:
963 with util.safe_reraise():
964 fairy._connection_record._checkin_failed(
965 err,
966 _fairy_was_created=True,
967 )
969 # prevent _ConnectionFairy from being carried
970 # in the stack trace. Do this after the
971 # connection record has been checked in, so that
972 # if the del triggers a finalize fairy, it won't
973 # try to checkin a second time.
974 del fairy
976 attempts -= 1
978 pool.logger.info("Reconnection attempts exhausted on checkout")
979 fairy.invalidate()
980 raise exc.InvalidRequestError("This connection is closed")
982 def _checkout_existing(self):
983 return _ConnectionFairy._checkout(self._pool, fairy=self)
985 def _checkin(self, transaction_was_reset=False):
986 _finalize_fairy(
987 self.dbapi_connection,
988 self._connection_record,
989 self._pool,
990 None,
991 self._echo,
992 transaction_was_reset=transaction_was_reset,
993 fairy=self,
994 )
995 self.dbapi_connection = None
996 self._connection_record = None
998 _close = _checkin
1000 def _reset(self, pool, transaction_was_reset=False):
1001 if pool.dispatch.reset:
1002 pool.dispatch.reset(self, self._connection_record)
1003 if pool._reset_on_return is reset_rollback:
1004 if transaction_was_reset:
1005 if self._echo:
1006 pool.logger.debug(
1007 "Connection %s reset, transaction already reset",
1008 self.dbapi_connection,
1009 )
1010 else:
1011 if self._echo:
1012 pool.logger.debug(
1013 "Connection %s rollback-on-return",
1014 self.dbapi_connection,
1015 )
1016 pool._dialect.do_rollback(self)
1017 elif pool._reset_on_return is reset_commit:
1018 if self._echo:
1019 pool.logger.debug(
1020 "Connection %s commit-on-return",
1021 self.dbapi_connection,
1022 )
1023 pool._dialect.do_commit(self)
1025 @property
1026 def _logger(self):
1027 return self._pool.logger
1029 @property
1030 def is_valid(self):
1031 """Return True if this :class:`._ConnectionFairy` still refers
1032 to an active DBAPI connection."""
1034 return self.dbapi_connection is not None
1036 @util.memoized_property
1037 def info(self):
1038 """Info dictionary associated with the underlying DBAPI connection
1039 referred to by this :class:`.ConnectionFairy`, allowing user-defined
1040 data to be associated with the connection.
1042 The data here will follow along with the DBAPI connection including
1043 after it is returned to the connection pool and used again
1044 in subsequent instances of :class:`._ConnectionFairy`. It is shared
1045 with the :attr:`._ConnectionRecord.info` and
1046 :attr:`_engine.Connection.info`
1047 accessors.
1049 The dictionary associated with a particular DBAPI connection is
1050 discarded when the connection itself is discarded.
1052 """
1053 return self._connection_record.info
1055 @property
1056 def record_info(self):
1057 """Info dictionary associated with the :class:`._ConnectionRecord
1058 container referred to by this :class:`.ConnectionFairy`.
1060 Unlike the :attr:`._ConnectionFairy.info` dictionary, the lifespan
1061 of this dictionary is persistent across connections that are
1062 disconnected and/or invalidated within the lifespan of a
1063 :class:`._ConnectionRecord`.
1065 .. versionadded:: 1.1
1067 """
1068 if self._connection_record:
1069 return self._connection_record.record_info
1070 else:
1071 return None
1073 def invalidate(self, e=None, soft=False):
1074 """Mark this connection as invalidated.
1076 This method can be called directly, and is also called as a result
1077 of the :meth:`_engine.Connection.invalidate` method. When invoked,
1078 the DBAPI connection is immediately closed and discarded from
1079 further use by the pool. The invalidation mechanism proceeds
1080 via the :meth:`._ConnectionRecord.invalidate` internal method.
1082 :param e: an exception object indicating a reason for the invalidation.
1084 :param soft: if True, the connection isn't closed; instead, this
1085 connection will be recycled on next checkout.
1087 .. versionadded:: 1.0.3
1089 .. seealso::
1091 :ref:`pool_connection_invalidation`
1093 """
1095 if self.dbapi_connection is None:
1096 util.warn("Can't invalidate an already-closed connection.")
1097 return
1098 if self._connection_record:
1099 self._connection_record.invalidate(e=e, soft=soft)
1100 if not soft:
1101 self.dbapi_connection = None
1102 self._checkin()
1104 def cursor(self, *args, **kwargs):
1105 """Return a new DBAPI cursor for the underlying connection.
1107 This method is a proxy for the ``connection.cursor()`` DBAPI
1108 method.
1110 """
1111 return self.dbapi_connection.cursor(*args, **kwargs)
1113 def __getattr__(self, key):
1114 return getattr(self.dbapi_connection, key)
1116 def detach(self):
1117 """Separate this connection from its Pool.
1119 This means that the connection will no longer be returned to the
1120 pool when closed, and will instead be literally closed. The
1121 containing ConnectionRecord is separated from the DB-API connection,
1122 and will create a new connection when next used.
1124 Note that any overall connection limiting constraints imposed by a
1125 Pool implementation may be violated after a detach, as the detached
1126 connection is removed from the pool's knowledge and control.
1127 """
1129 if self._connection_record is not None:
1130 rec = self._connection_record
1131 rec.fairy_ref = None
1132 rec.dbapi_connection = None
1133 # TODO: should this be _return_conn?
1134 self._pool._do_return_conn(self._connection_record)
1135 self.info = self.info.copy()
1136 self._connection_record = None
1138 if self._pool.dispatch.detach:
1139 self._pool.dispatch.detach(self.dbapi_connection, rec)
1141 def close(self):
1142 self._counter -= 1
1143 if self._counter == 0:
1144 self._checkin()
1146 def _close_special(self, transaction_reset=False):
1147 self._counter -= 1
1148 if self._counter == 0:
1149 self._checkin(transaction_was_reset=transaction_reset)