base.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524
  1. # pool/base.py
  2. # Copyright (C) 2005-2026 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
  7. """Base constructs for connection pools."""
  8. from __future__ import annotations
  9. from collections import deque
  10. import dataclasses
  11. from enum import Enum
  12. import threading
  13. import time
  14. import typing
  15. from typing import Any
  16. from typing import Callable
  17. from typing import cast
  18. from typing import Deque
  19. from typing import Dict
  20. from typing import List
  21. from typing import Optional
  22. from typing import Tuple
  23. from typing import TYPE_CHECKING
  24. from typing import Union
  25. import weakref
  26. from .. import event
  27. from .. import exc
  28. from .. import log
  29. from .. import util
  30. from ..util.typing import Literal
  31. from ..util.typing import Protocol
  32. from ..util.typing import Self
  33. if TYPE_CHECKING:
  34. from ..engine.interfaces import DBAPIConnection
  35. from ..engine.interfaces import DBAPICursor
  36. from ..engine.interfaces import Dialect
  37. from ..event import _DispatchCommon
  38. from ..event import _ListenerFnType
  39. from ..event import dispatcher
  40. from ..sql._typing import _InfoType
  41. @dataclasses.dataclass(frozen=True)
  42. class PoolResetState:
  43. """describes the state of a DBAPI connection as it is being passed to
  44. the :meth:`.PoolEvents.reset` connection pool event.
  45. .. versionadded:: 2.0.0b3
  46. """
  47. __slots__ = ("transaction_was_reset", "terminate_only", "asyncio_safe")
  48. transaction_was_reset: bool
  49. """Indicates if the transaction on the DBAPI connection was already
  50. essentially "reset" back by the :class:`.Connection` object.
  51. This boolean is True if the :class:`.Connection` had transactional
  52. state present upon it, which was then not closed using the
  53. :meth:`.Connection.rollback` or :meth:`.Connection.commit` method;
  54. instead, the transaction was closed inline within the
  55. :meth:`.Connection.close` method so is guaranteed to remain non-present
  56. when this event is reached.
  57. """
  58. terminate_only: bool
  59. """indicates if the connection is to be immediately terminated and
  60. not checked in to the pool.
  61. This occurs for connections that were invalidated, as well as asyncio
  62. connections that were not cleanly handled by the calling code that
  63. are instead being garbage collected. In the latter case,
  64. operations can't be safely run on asyncio connections within garbage
  65. collection as there is not necessarily an event loop present.
  66. """
  67. asyncio_safe: bool
  68. """Indicates if the reset operation is occurring within a scope where
  69. an enclosing event loop is expected to be present for asyncio applications.
  70. Will be False in the case that the connection is being garbage collected.
  71. """
  72. class ResetStyle(Enum):
  73. """Describe options for "reset on return" behaviors."""
  74. reset_rollback = 0
  75. reset_commit = 1
  76. reset_none = 2
  77. _ResetStyleArgType = Union[
  78. ResetStyle,
  79. Literal[True, None, False, "commit", "rollback"],
  80. ]
  81. reset_rollback, reset_commit, reset_none = list(ResetStyle)
  82. class _ConnDialect:
  83. """partial implementation of :class:`.Dialect`
  84. which provides DBAPI connection methods.
  85. When a :class:`_pool.Pool` is combined with an :class:`_engine.Engine`,
  86. the :class:`_engine.Engine` replaces this with its own
  87. :class:`.Dialect`.
  88. """
  89. is_async = False
  90. has_terminate = False
  91. def do_rollback(self, dbapi_connection: PoolProxiedConnection) -> None:
  92. dbapi_connection.rollback()
  93. def do_commit(self, dbapi_connection: PoolProxiedConnection) -> None:
  94. dbapi_connection.commit()
  95. def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
  96. dbapi_connection.close()
  97. def do_close(self, dbapi_connection: DBAPIConnection) -> None:
  98. dbapi_connection.close()
  99. def _do_ping_w_event(self, dbapi_connection: DBAPIConnection) -> bool:
  100. raise NotImplementedError(
  101. "The ping feature requires that a dialect is "
  102. "passed to the connection pool."
  103. )
  104. def get_driver_connection(self, connection: DBAPIConnection) -> Any:
  105. return connection
  106. class _AsyncConnDialect(_ConnDialect):
  107. is_async = True
  108. class _CreatorFnType(Protocol):
  109. def __call__(self) -> DBAPIConnection: ...
  110. class _CreatorWRecFnType(Protocol):
  111. def __call__(self, rec: ConnectionPoolEntry) -> DBAPIConnection: ...
  112. class Pool(log.Identified, event.EventTarget):
  113. """Abstract base class for connection pools."""
  114. dispatch: dispatcher[Pool]
  115. echo: log._EchoFlagType
  116. _orig_logging_name: Optional[str]
  117. _dialect: Union[_ConnDialect, Dialect] = _ConnDialect()
  118. _creator_arg: Union[_CreatorFnType, _CreatorWRecFnType]
  119. _invoke_creator: _CreatorWRecFnType
  120. _invalidate_time: float
  121. def __init__(
  122. self,
  123. creator: Union[_CreatorFnType, _CreatorWRecFnType],
  124. recycle: int = -1,
  125. echo: log._EchoFlagType = None,
  126. logging_name: Optional[str] = None,
  127. reset_on_return: _ResetStyleArgType = True,
  128. events: Optional[List[Tuple[_ListenerFnType, str]]] = None,
  129. dialect: Optional[Union[_ConnDialect, Dialect]] = None,
  130. pre_ping: bool = False,
  131. _dispatch: Optional[_DispatchCommon[Pool]] = None,
  132. ):
  133. """
  134. Construct a Pool.
  135. :param creator: a callable function that returns a DB-API
  136. connection object. The function will be called with
  137. parameters.
  138. :param recycle: If set to a value other than -1, number of
  139. seconds between connection recycling, which means upon
  140. checkout, if this timeout is surpassed the connection will be
  141. closed and replaced with a newly opened connection. Defaults to -1.
  142. :param logging_name: String identifier which will be used within
  143. the "name" field of logging records generated within the
  144. "sqlalchemy.pool" logger. Defaults to a hexstring of the object's
  145. id.
  146. :param echo: if True, the connection pool will log
  147. informational output such as when connections are invalidated
  148. as well as when connections are recycled to the default log handler,
  149. which defaults to ``sys.stdout`` for output.. If set to the string
  150. ``"debug"``, the logging will include pool checkouts and checkins.
  151. The :paramref:`_pool.Pool.echo` parameter can also be set from the
  152. :func:`_sa.create_engine` call by using the
  153. :paramref:`_sa.create_engine.echo_pool` parameter.
  154. .. seealso::
  155. :ref:`dbengine_logging` - further detail on how to configure
  156. logging.
  157. :param reset_on_return: Determine steps to take on
  158. connections as they are returned to the pool, which were
  159. not otherwise handled by a :class:`_engine.Connection`.
  160. Available from :func:`_sa.create_engine` via the
  161. :paramref:`_sa.create_engine.pool_reset_on_return` parameter.
  162. :paramref:`_pool.Pool.reset_on_return` can have any of these values:
  163. * ``"rollback"`` - call rollback() on the connection,
  164. to release locks and transaction resources.
  165. This is the default value. The vast majority
  166. of use cases should leave this value set.
  167. * ``"commit"`` - call commit() on the connection,
  168. to release locks and transaction resources.
  169. A commit here may be desirable for databases that
  170. cache query plans if a commit is emitted,
  171. such as Microsoft SQL Server. However, this
  172. value is more dangerous than 'rollback' because
  173. any data changes present on the transaction
  174. are committed unconditionally.
  175. * ``None`` - don't do anything on the connection.
  176. This setting may be appropriate if the database / DBAPI
  177. works in pure "autocommit" mode at all times, or if
  178. a custom reset handler is established using the
  179. :meth:`.PoolEvents.reset` event handler.
  180. * ``True`` - same as 'rollback', this is here for
  181. backwards compatibility.
  182. * ``False`` - same as None, this is here for
  183. backwards compatibility.
  184. For further customization of reset on return, the
  185. :meth:`.PoolEvents.reset` event hook may be used which can perform
  186. any connection activity desired on reset.
  187. .. seealso::
  188. :ref:`pool_reset_on_return`
  189. :meth:`.PoolEvents.reset`
  190. :param events: a list of 2-tuples, each of the form
  191. ``(callable, target)`` which will be passed to :func:`.event.listen`
  192. upon construction. Provided here so that event listeners
  193. can be assigned via :func:`_sa.create_engine` before dialect-level
  194. listeners are applied.
  195. :param dialect: a :class:`.Dialect` that will handle the job
  196. of calling rollback(), close(), or commit() on DBAPI connections.
  197. If omitted, a built-in "stub" dialect is used. Applications that
  198. make use of :func:`_sa.create_engine` should not use this parameter
  199. as it is handled by the engine creation strategy.
  200. :param pre_ping: if True, the pool will emit a "ping" (typically
  201. "SELECT 1", but is dialect-specific) on the connection
  202. upon checkout, to test if the connection is alive or not. If not,
  203. the connection is transparently re-connected and upon success, all
  204. other pooled connections established prior to that timestamp are
  205. invalidated. Requires that a dialect is passed as well to
  206. interpret the disconnection error.
  207. .. versionadded:: 1.2
  208. """
  209. if logging_name:
  210. self.logging_name = self._orig_logging_name = logging_name
  211. else:
  212. self._orig_logging_name = None
  213. log.instance_logger(self, echoflag=echo)
  214. self._creator = creator
  215. self._recycle = recycle
  216. self._invalidate_time = 0
  217. self._pre_ping = pre_ping
  218. self._reset_on_return = util.parse_user_argument_for_enum(
  219. reset_on_return,
  220. {
  221. ResetStyle.reset_rollback: ["rollback", True],
  222. ResetStyle.reset_none: ["none", None, False],
  223. ResetStyle.reset_commit: ["commit"],
  224. },
  225. "reset_on_return",
  226. )
  227. self.echo = echo
  228. if _dispatch:
  229. self.dispatch._update(_dispatch, only_propagate=False)
  230. if dialect:
  231. self._dialect = dialect
  232. if events:
  233. for fn, target in events:
  234. event.listen(self, target, fn)
  235. @util.hybridproperty
  236. def _is_asyncio(self) -> bool:
  237. return self._dialect.is_async
  238. @property
  239. def _creator(self) -> Union[_CreatorFnType, _CreatorWRecFnType]:
  240. return self._creator_arg
  241. @_creator.setter
  242. def _creator(
  243. self, creator: Union[_CreatorFnType, _CreatorWRecFnType]
  244. ) -> None:
  245. self._creator_arg = creator
  246. # mypy seems to get super confused assigning functions to
  247. # attributes
  248. self._invoke_creator = self._should_wrap_creator(creator)
  249. @_creator.deleter
  250. def _creator(self) -> None:
  251. # needed for mock testing
  252. del self._creator_arg
  253. del self._invoke_creator
  254. def _should_wrap_creator(
  255. self, creator: Union[_CreatorFnType, _CreatorWRecFnType]
  256. ) -> _CreatorWRecFnType:
  257. """Detect if creator accepts a single argument, or is sent
  258. as a legacy style no-arg function.
  259. """
  260. try:
  261. argspec = util.get_callable_argspec(self._creator, no_self=True)
  262. except TypeError:
  263. creator_fn = cast(_CreatorFnType, creator)
  264. return lambda rec: creator_fn()
  265. if argspec.defaults is not None:
  266. defaulted = len(argspec.defaults)
  267. else:
  268. defaulted = 0
  269. positionals = len(argspec[0]) - defaulted
  270. # look for the exact arg signature that DefaultStrategy
  271. # sends us
  272. if (argspec[0], argspec[3]) == (["connection_record"], (None,)):
  273. return cast(_CreatorWRecFnType, creator)
  274. # or just a single positional
  275. elif positionals == 1:
  276. return cast(_CreatorWRecFnType, creator)
  277. # all other cases, just wrap and assume legacy "creator" callable
  278. # thing
  279. else:
  280. creator_fn = cast(_CreatorFnType, creator)
  281. return lambda rec: creator_fn()
  282. def _close_connection(
  283. self, connection: DBAPIConnection, *, terminate: bool = False
  284. ) -> None:
  285. self.logger.debug(
  286. "%s connection %r",
  287. "Hard-closing" if terminate else "Closing",
  288. connection,
  289. )
  290. try:
  291. if terminate:
  292. self._dialect.do_terminate(connection)
  293. else:
  294. self._dialect.do_close(connection)
  295. except BaseException as e:
  296. self.logger.error(
  297. f"Exception {'terminating' if terminate else 'closing'} "
  298. f"connection %r",
  299. connection,
  300. exc_info=True,
  301. )
  302. if not isinstance(e, Exception):
  303. raise
  304. def _create_connection(self) -> ConnectionPoolEntry:
  305. """Called by subclasses to create a new ConnectionRecord."""
  306. return _ConnectionRecord(self)
  307. def _invalidate(
  308. self,
  309. connection: PoolProxiedConnection,
  310. exception: Optional[BaseException] = None,
  311. _checkin: bool = True,
  312. ) -> None:
  313. """Mark all connections established within the generation
  314. of the given connection as invalidated.
  315. If this pool's last invalidate time is before when the given
  316. connection was created, update the timestamp til now. Otherwise,
  317. no action is performed.
  318. Connections with a start time prior to this pool's invalidation
  319. time will be recycled upon next checkout.
  320. """
  321. rec = getattr(connection, "_connection_record", None)
  322. if not rec or self._invalidate_time < rec.starttime:
  323. self._invalidate_time = time.time()
  324. if _checkin and getattr(connection, "is_valid", False):
  325. connection.invalidate(exception)
  326. def recreate(self) -> Pool:
  327. """Return a new :class:`_pool.Pool`, of the same class as this one
  328. and configured with identical creation arguments.
  329. This method is used in conjunction with :meth:`dispose`
  330. to close out an entire :class:`_pool.Pool` and create a new one in
  331. its place.
  332. """
  333. raise NotImplementedError()
  334. def dispose(self) -> None:
  335. """Dispose of this pool.
  336. This method leaves the possibility of checked-out connections
  337. remaining open, as it only affects connections that are
  338. idle in the pool.
  339. .. seealso::
  340. :meth:`Pool.recreate`
  341. """
  342. raise NotImplementedError()
  343. def connect(self) -> PoolProxiedConnection:
  344. """Return a DBAPI connection from the pool.
  345. The connection is instrumented such that when its
  346. ``close()`` method is called, the connection will be returned to
  347. the pool.
  348. """
  349. return _ConnectionFairy._checkout(self)
  350. def _return_conn(self, record: ConnectionPoolEntry) -> None:
  351. """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`.
  352. This method is called when an instrumented DBAPI connection
  353. has its ``close()`` method called.
  354. """
  355. self._do_return_conn(record)
  356. def _do_get(self) -> ConnectionPoolEntry:
  357. """Implementation for :meth:`get`, supplied by subclasses."""
  358. raise NotImplementedError()
  359. def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
  360. """Implementation for :meth:`return_conn`, supplied by subclasses."""
  361. raise NotImplementedError()
  362. def status(self) -> str:
  363. """Returns a brief description of the state of this pool."""
  364. raise NotImplementedError()
  365. class ManagesConnection:
  366. """Common base for the two connection-management interfaces
  367. :class:`.PoolProxiedConnection` and :class:`.ConnectionPoolEntry`.
  368. These two objects are typically exposed in the public facing API
  369. via the connection pool event hooks, documented at :class:`.PoolEvents`.
  370. .. versionadded:: 2.0
  371. """
  372. __slots__ = ()
  373. dbapi_connection: Optional[DBAPIConnection]
  374. """A reference to the actual DBAPI connection being tracked.
  375. This is a :pep:`249`-compliant object that for traditional sync-style
  376. dialects is provided by the third-party
  377. DBAPI implementation in use. For asyncio dialects, the implementation
  378. is typically an adapter object provided by the SQLAlchemy dialect
  379. itself; the underlying asyncio object is available via the
  380. :attr:`.ManagesConnection.driver_connection` attribute.
  381. SQLAlchemy's interface for the DBAPI connection is based on the
  382. :class:`.DBAPIConnection` protocol object
  383. .. seealso::
  384. :attr:`.ManagesConnection.driver_connection`
  385. :ref:`faq_dbapi_connection`
  386. """
  387. driver_connection: Optional[Any]
  388. """The "driver level" connection object as used by the Python
  389. DBAPI or database driver.
  390. For traditional :pep:`249` DBAPI implementations, this object will
  391. be the same object as that of
  392. :attr:`.ManagesConnection.dbapi_connection`. For an asyncio database
  393. driver, this will be the ultimate "connection" object used by that
  394. driver, such as the ``asyncpg.Connection`` object which will not have
  395. standard pep-249 methods.
  396. .. versionadded:: 1.4.24
  397. .. seealso::
  398. :attr:`.ManagesConnection.dbapi_connection`
  399. :ref:`faq_dbapi_connection`
  400. """
  401. @util.ro_memoized_property
  402. def info(self) -> _InfoType:
  403. """Info dictionary associated with the underlying DBAPI connection
  404. referred to by this :class:`.ManagesConnection` instance, allowing
  405. user-defined data to be associated with the connection.
  406. The data in this dictionary is persistent for the lifespan
  407. of the DBAPI connection itself, including across pool checkins
  408. and checkouts. When the connection is invalidated
  409. and replaced with a new one, this dictionary is cleared.
  410. For a :class:`.PoolProxiedConnection` instance that's not associated
  411. with a :class:`.ConnectionPoolEntry`, such as if it were detached, the
  412. attribute returns a dictionary that is local to that
  413. :class:`.ConnectionPoolEntry`. Therefore the
  414. :attr:`.ManagesConnection.info` attribute will always provide a Python
  415. dictionary.
  416. .. seealso::
  417. :attr:`.ManagesConnection.record_info`
  418. """
  419. raise NotImplementedError()
  420. @util.ro_memoized_property
  421. def record_info(self) -> Optional[_InfoType]:
  422. """Persistent info dictionary associated with this
  423. :class:`.ManagesConnection`.
  424. Unlike the :attr:`.ManagesConnection.info` dictionary, the lifespan
  425. of this dictionary is that of the :class:`.ConnectionPoolEntry`
  426. which owns it; therefore this dictionary will persist across
  427. reconnects and connection invalidation for a particular entry
  428. in the connection pool.
  429. For a :class:`.PoolProxiedConnection` instance that's not associated
  430. with a :class:`.ConnectionPoolEntry`, such as if it were detached, the
  431. attribute returns None. Contrast to the :attr:`.ManagesConnection.info`
  432. dictionary which is never None.
  433. .. seealso::
  434. :attr:`.ManagesConnection.info`
  435. """
  436. raise NotImplementedError()
  437. def invalidate(
  438. self, e: Optional[BaseException] = None, soft: bool = False
  439. ) -> None:
  440. """Mark the managed connection as invalidated.
  441. :param e: an exception object indicating a reason for the invalidation.
  442. :param soft: if True, the connection isn't closed; instead, this
  443. connection will be recycled on next checkout.
  444. .. seealso::
  445. :ref:`pool_connection_invalidation`
  446. """
  447. raise NotImplementedError()
  448. class ConnectionPoolEntry(ManagesConnection):
  449. """Interface for the object that maintains an individual database
  450. connection on behalf of a :class:`_pool.Pool` instance.
  451. The :class:`.ConnectionPoolEntry` object represents the long term
  452. maintenance of a particular connection for a pool, including expiring or
  453. invalidating that connection to have it replaced with a new one, which will
  454. continue to be maintained by that same :class:`.ConnectionPoolEntry`
  455. instance. Compared to :class:`.PoolProxiedConnection`, which is the
  456. short-term, per-checkout connection manager, this object lasts for the
  457. lifespan of a particular "slot" within a connection pool.
  458. The :class:`.ConnectionPoolEntry` object is mostly visible to public-facing
  459. API code when it is delivered to connection pool event hooks, such as
  460. :meth:`_events.PoolEvents.connect` and :meth:`_events.PoolEvents.checkout`.
  461. .. versionadded:: 2.0 :class:`.ConnectionPoolEntry` provides the public
  462. facing interface for the :class:`._ConnectionRecord` internal class.
  463. """
  464. __slots__ = ()
  465. @property
  466. def in_use(self) -> bool:
  467. """Return True the connection is currently checked out"""
  468. raise NotImplementedError()
  469. def close(self) -> None:
  470. """Close the DBAPI connection managed by this connection pool entry."""
  471. raise NotImplementedError()
  472. class _ConnectionRecord(ConnectionPoolEntry):
  473. """Maintains a position in a connection pool which references a pooled
  474. connection.
  475. This is an internal object used by the :class:`_pool.Pool` implementation
  476. to provide context management to a DBAPI connection maintained by
  477. that :class:`_pool.Pool`. The public facing interface for this class
  478. is described by the :class:`.ConnectionPoolEntry` class. See that
  479. class for public API details.
  480. .. seealso::
  481. :class:`.ConnectionPoolEntry`
  482. :class:`.PoolProxiedConnection`
  483. """
  484. __slots__ = (
  485. "__pool",
  486. "fairy_ref",
  487. "finalize_callback",
  488. "fresh",
  489. "starttime",
  490. "dbapi_connection",
  491. "__weakref__",
  492. "__dict__",
  493. )
  494. finalize_callback: Deque[Callable[[DBAPIConnection], None]]
  495. fresh: bool
  496. fairy_ref: Optional[weakref.ref[_ConnectionFairy]]
  497. starttime: float
  498. def __init__(self, pool: Pool, connect: bool = True):
  499. self.fresh = False
  500. self.fairy_ref = None
  501. self.starttime = 0
  502. self.dbapi_connection = None
  503. self.__pool = pool
  504. if connect:
  505. self.__connect()
  506. self.finalize_callback = deque()
  507. dbapi_connection: Optional[DBAPIConnection]
  508. @property
  509. def driver_connection(self) -> Optional[Any]: # type: ignore[override] # mypy#4125 # noqa: E501
  510. if self.dbapi_connection is None:
  511. return None
  512. else:
  513. return self.__pool._dialect.get_driver_connection(
  514. self.dbapi_connection
  515. )
  516. @property
  517. @util.deprecated(
  518. "2.0",
  519. "The _ConnectionRecord.connection attribute is deprecated; "
  520. "please use 'driver_connection'",
  521. )
  522. def connection(self) -> Optional[DBAPIConnection]:
  523. return self.dbapi_connection
  524. _soft_invalidate_time: float = 0
  525. @util.ro_memoized_property
  526. def info(self) -> _InfoType:
  527. return {}
  528. @util.ro_memoized_property
  529. def record_info(self) -> Optional[_InfoType]:
  530. return {}
  531. @classmethod
  532. def checkout(cls, pool: Pool) -> _ConnectionFairy:
  533. if TYPE_CHECKING:
  534. rec = cast(_ConnectionRecord, pool._do_get())
  535. else:
  536. rec = pool._do_get()
  537. try:
  538. dbapi_connection = rec.get_connection()
  539. except BaseException as err:
  540. with util.safe_reraise():
  541. rec._checkin_failed(err, _fairy_was_created=False)
  542. # not reached, for code linters only
  543. raise
  544. echo = pool._should_log_debug()
  545. fairy = _ConnectionFairy(pool, dbapi_connection, rec, echo)
  546. rec.fairy_ref = ref = weakref.ref(
  547. fairy,
  548. lambda ref: (
  549. _finalize_fairy(
  550. None, rec, pool, ref, echo, transaction_was_reset=False
  551. )
  552. if _finalize_fairy is not None
  553. else None
  554. ),
  555. )
  556. _strong_ref_connection_records[ref] = rec
  557. if echo:
  558. pool.logger.debug(
  559. "Connection %r checked out from pool", dbapi_connection
  560. )
  561. return fairy
  562. def _checkin_failed(
  563. self, err: BaseException, _fairy_was_created: bool = True
  564. ) -> None:
  565. self.invalidate(e=err)
  566. self.checkin(
  567. _fairy_was_created=_fairy_was_created,
  568. )
  569. def checkin(self, _fairy_was_created: bool = True) -> None:
  570. if self.fairy_ref is None and _fairy_was_created:
  571. # _fairy_was_created is False for the initial get connection phase;
  572. # meaning there was no _ConnectionFairy and we must unconditionally
  573. # do a checkin.
  574. #
  575. # otherwise, if fairy_was_created==True, if fairy_ref is None here
  576. # that means we were checked in already, so this looks like
  577. # a double checkin.
  578. util.warn("Double checkin attempted on %s" % self)
  579. return
  580. self.fairy_ref = None
  581. connection = self.dbapi_connection
  582. pool = self.__pool
  583. while self.finalize_callback:
  584. finalizer = self.finalize_callback.pop()
  585. if connection is not None:
  586. finalizer(connection)
  587. if pool.dispatch.checkin:
  588. pool.dispatch.checkin(connection, self)
  589. pool._return_conn(self)
  590. @property
  591. def in_use(self) -> bool:
  592. return self.fairy_ref is not None
  593. @property
  594. def last_connect_time(self) -> float:
  595. return self.starttime
  596. def close(self) -> None:
  597. if self.dbapi_connection is not None:
  598. self.__close()
  599. def invalidate(
  600. self, e: Optional[BaseException] = None, soft: bool = False
  601. ) -> None:
  602. # already invalidated
  603. if self.dbapi_connection is None:
  604. return
  605. if soft:
  606. self.__pool.dispatch.soft_invalidate(
  607. self.dbapi_connection, self, e
  608. )
  609. else:
  610. self.__pool.dispatch.invalidate(self.dbapi_connection, self, e)
  611. if e is not None:
  612. self.__pool.logger.info(
  613. "%sInvalidate connection %r (reason: %s:%s)",
  614. "Soft " if soft else "",
  615. self.dbapi_connection,
  616. e.__class__.__name__,
  617. e,
  618. )
  619. else:
  620. self.__pool.logger.info(
  621. "%sInvalidate connection %r",
  622. "Soft " if soft else "",
  623. self.dbapi_connection,
  624. )
  625. if soft:
  626. self._soft_invalidate_time = time.time()
  627. else:
  628. self.__close(terminate=True)
  629. self.dbapi_connection = None
  630. def get_connection(self) -> DBAPIConnection:
  631. recycle = False
  632. # NOTE: the various comparisons here are assuming that measurable time
  633. # passes between these state changes. however, time.time() is not
  634. # guaranteed to have sub-second precision. comparisons of
  635. # "invalidation time" to "starttime" should perhaps use >= so that the
  636. # state change can take place assuming no measurable time has passed,
  637. # however this does not guarantee correct behavior here as if time
  638. # continues to not pass, it will try to reconnect repeatedly until
  639. # these timestamps diverge, so in that sense using > is safer. Per
  640. # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be
  641. # within 16 milliseconds accuracy, so unit tests for connection
  642. # invalidation need a sleep of at least this long between initial start
  643. # time and invalidation for the logic below to work reliably.
  644. if self.dbapi_connection is None:
  645. self.info.clear()
  646. self.__connect()
  647. elif (
  648. self.__pool._recycle > -1
  649. and time.time() - self.starttime > self.__pool._recycle
  650. ):
  651. self.__pool.logger.info(
  652. "Connection %r exceeded timeout; recycling",
  653. self.dbapi_connection,
  654. )
  655. recycle = True
  656. elif self.__pool._invalidate_time > self.starttime:
  657. self.__pool.logger.info(
  658. "Connection %r invalidated due to pool invalidation; "
  659. + "recycling",
  660. self.dbapi_connection,
  661. )
  662. recycle = True
  663. elif self._soft_invalidate_time > self.starttime:
  664. self.__pool.logger.info(
  665. "Connection %r invalidated due to local soft invalidation; "
  666. + "recycling",
  667. self.dbapi_connection,
  668. )
  669. recycle = True
  670. if recycle:
  671. self.__close(terminate=True)
  672. self.info.clear()
  673. self.__connect()
  674. assert self.dbapi_connection is not None
  675. return self.dbapi_connection
  676. def _is_hard_or_soft_invalidated(self) -> bool:
  677. return (
  678. self.dbapi_connection is None
  679. or self.__pool._invalidate_time > self.starttime
  680. or (self._soft_invalidate_time > self.starttime)
  681. )
  682. def __close(self, *, terminate: bool = False) -> None:
  683. self.finalize_callback.clear()
  684. if self.__pool.dispatch.close:
  685. self.__pool.dispatch.close(self.dbapi_connection, self)
  686. assert self.dbapi_connection is not None
  687. self.__pool._close_connection(
  688. self.dbapi_connection, terminate=terminate
  689. )
  690. self.dbapi_connection = None
  691. def __connect(self) -> None:
  692. pool = self.__pool
  693. # ensure any existing connection is removed, so that if
  694. # creator fails, this attribute stays None
  695. self.dbapi_connection = None
  696. try:
  697. self.starttime = time.time()
  698. self.dbapi_connection = connection = pool._invoke_creator(self)
  699. pool.logger.debug("Created new connection %r", connection)
  700. self.fresh = True
  701. except BaseException as e:
  702. with util.safe_reraise():
  703. pool.logger.debug("Error on connect(): %s", e)
  704. else:
  705. # in SQLAlchemy 1.4 the first_connect event is not used by
  706. # the engine, so this will usually not be set
  707. if pool.dispatch.first_connect:
  708. pool.dispatch.first_connect.for_modify(
  709. pool.dispatch
  710. ).exec_once_unless_exception(self.dbapi_connection, self)
  711. # init of the dialect now takes place within the connect
  712. # event, so ensure a mutex is used on the first run
  713. pool.dispatch.connect.for_modify(
  714. pool.dispatch
  715. )._exec_w_sync_on_first_run(self.dbapi_connection, self)
  716. def _finalize_fairy(
  717. dbapi_connection: Optional[DBAPIConnection],
  718. connection_record: Optional[_ConnectionRecord],
  719. pool: Pool,
  720. ref: Optional[
  721. weakref.ref[_ConnectionFairy]
  722. ], # this is None when called directly, not by the gc
  723. echo: Optional[log._EchoFlagType],
  724. transaction_was_reset: bool = False,
  725. fairy: Optional[_ConnectionFairy] = None,
  726. ) -> None:
  727. """Cleanup for a :class:`._ConnectionFairy` whether or not it's already
  728. been garbage collected.
  729. When using an async dialect no IO can happen here (without using
  730. a dedicated thread), since this is called outside the greenlet
  731. context and with an already running loop. In this case function
  732. will only log a message and raise a warning.
  733. """
  734. is_gc_cleanup = ref is not None
  735. if is_gc_cleanup:
  736. assert ref is not None
  737. _strong_ref_connection_records.pop(ref, None)
  738. assert connection_record is not None
  739. if connection_record.fairy_ref is not ref:
  740. return
  741. assert dbapi_connection is None
  742. dbapi_connection = connection_record.dbapi_connection
  743. elif fairy:
  744. _strong_ref_connection_records.pop(weakref.ref(fairy), None)
  745. # null pool is not _is_asyncio but can be used also with async dialects
  746. dont_restore_gced = pool._dialect.is_async
  747. if dont_restore_gced:
  748. detach = connection_record is None or is_gc_cleanup
  749. can_manipulate_connection = not is_gc_cleanup
  750. can_close_or_terminate_connection = (
  751. not pool._dialect.is_async or pool._dialect.has_terminate
  752. )
  753. requires_terminate_for_close = (
  754. pool._dialect.is_async and pool._dialect.has_terminate
  755. )
  756. else:
  757. detach = connection_record is None
  758. can_manipulate_connection = can_close_or_terminate_connection = True
  759. requires_terminate_for_close = False
  760. if dbapi_connection is not None:
  761. if connection_record and echo:
  762. pool.logger.debug(
  763. "Connection %r being returned to pool", dbapi_connection
  764. )
  765. try:
  766. if not fairy:
  767. assert connection_record is not None
  768. fairy = _ConnectionFairy(
  769. pool,
  770. dbapi_connection,
  771. connection_record,
  772. echo,
  773. )
  774. assert fairy.dbapi_connection is dbapi_connection
  775. fairy._reset(
  776. pool,
  777. transaction_was_reset=transaction_was_reset,
  778. terminate_only=detach,
  779. asyncio_safe=can_manipulate_connection,
  780. )
  781. if detach:
  782. if connection_record:
  783. fairy._pool = pool
  784. fairy.detach()
  785. if can_close_or_terminate_connection:
  786. if pool.dispatch.close_detached:
  787. pool.dispatch.close_detached(dbapi_connection)
  788. pool._close_connection(
  789. dbapi_connection,
  790. terminate=requires_terminate_for_close,
  791. )
  792. except BaseException as e:
  793. pool.logger.error(
  794. "Exception during reset or similar", exc_info=True
  795. )
  796. if connection_record:
  797. connection_record.invalidate(e=e)
  798. if not isinstance(e, Exception):
  799. raise
  800. finally:
  801. if detach and is_gc_cleanup and dont_restore_gced:
  802. message = (
  803. "The garbage collector is trying to clean up "
  804. f"non-checked-in connection {dbapi_connection!r}, "
  805. f"""which will be {
  806. 'dropped, as it cannot be safely terminated'
  807. if not can_close_or_terminate_connection
  808. else 'terminated'
  809. }. """
  810. "Please ensure that SQLAlchemy pooled connections are "
  811. "returned to "
  812. "the pool explicitly, either by calling ``close()`` "
  813. "or by using appropriate context managers to manage "
  814. "their lifecycle."
  815. )
  816. pool.logger.error(message)
  817. util.warn(message)
  818. if connection_record and connection_record.fairy_ref is not None:
  819. connection_record.checkin()
  820. # give gc some help. See
  821. # test/engine/test_pool.py::PoolEventsTest::test_checkin_event_gc[True]
  822. # which actually started failing when pytest warnings plugin was
  823. # turned on, due to util.warn() above
  824. if fairy is not None:
  825. fairy.dbapi_connection = None # type: ignore
  826. fairy._connection_record = None
  827. del dbapi_connection
  828. del connection_record
  829. del fairy
  830. # a dictionary of the _ConnectionFairy weakrefs to _ConnectionRecord, so that
  831. # GC under pypy will call ConnectionFairy finalizers. linked directly to the
  832. # weakref that will empty itself when collected so that it should not create
  833. # any unmanaged memory references.
  834. _strong_ref_connection_records: Dict[
  835. weakref.ref[_ConnectionFairy], _ConnectionRecord
  836. ] = {}
  837. class PoolProxiedConnection(ManagesConnection):
  838. """A connection-like adapter for a :pep:`249` DBAPI connection, which
  839. includes additional methods specific to the :class:`.Pool` implementation.
  840. :class:`.PoolProxiedConnection` is the public-facing interface for the
  841. internal :class:`._ConnectionFairy` implementation object; users familiar
  842. with :class:`._ConnectionFairy` can consider this object to be equivalent.
  843. .. versionadded:: 2.0 :class:`.PoolProxiedConnection` provides the public-
  844. facing interface for the :class:`._ConnectionFairy` internal class.
  845. """
  846. __slots__ = ()
  847. if typing.TYPE_CHECKING:
  848. def commit(self) -> None: ...
  849. def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor: ...
  850. def rollback(self) -> None: ...
  851. def __getattr__(self, key: str) -> Any: ...
  852. @property
  853. def is_valid(self) -> bool:
  854. """Return True if this :class:`.PoolProxiedConnection` still refers
  855. to an active DBAPI connection."""
  856. raise NotImplementedError()
  857. @property
  858. def is_detached(self) -> bool:
  859. """Return True if this :class:`.PoolProxiedConnection` is detached
  860. from its pool."""
  861. raise NotImplementedError()
  862. def detach(self) -> None:
  863. """Separate this connection from its Pool.
  864. This means that the connection will no longer be returned to the
  865. pool when closed, and will instead be literally closed. The
  866. associated :class:`.ConnectionPoolEntry` is de-associated from this
  867. DBAPI connection.
  868. Note that any overall connection limiting constraints imposed by a
  869. Pool implementation may be violated after a detach, as the detached
  870. connection is removed from the pool's knowledge and control.
  871. """
  872. raise NotImplementedError()
  873. def close(self) -> None:
  874. """Release this connection back to the pool.
  875. The :meth:`.PoolProxiedConnection.close` method shadows the
  876. :pep:`249` ``.close()`` method, altering its behavior to instead
  877. :term:`release` the proxied connection back to the connection pool.
  878. Upon release to the pool, whether the connection stays "opened" and
  879. pooled in the Python process, versus actually closed out and removed
  880. from the Python process, is based on the pool implementation in use and
  881. its configuration and current state.
  882. """
  883. raise NotImplementedError()
  884. def __enter__(self) -> Self:
  885. return self
  886. def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
  887. self.close()
  888. return None
  889. class _AdhocProxiedConnection(PoolProxiedConnection):
  890. """provides the :class:`.PoolProxiedConnection` interface for cases where
  891. the DBAPI connection is not actually proxied.
  892. This is used by the engine internals to pass a consistent
  893. :class:`.PoolProxiedConnection` object to consuming dialects in response to
  894. pool events that may not always have the :class:`._ConnectionFairy`
  895. available.
  896. """
  897. __slots__ = ("dbapi_connection", "_connection_record", "_is_valid")
  898. dbapi_connection: DBAPIConnection
  899. _connection_record: ConnectionPoolEntry
  900. def __init__(
  901. self,
  902. dbapi_connection: DBAPIConnection,
  903. connection_record: ConnectionPoolEntry,
  904. ):
  905. self.dbapi_connection = dbapi_connection
  906. self._connection_record = connection_record
  907. self._is_valid = True
  908. @property
  909. def driver_connection(self) -> Any: # type: ignore[override] # mypy#4125
  910. return self._connection_record.driver_connection
  911. @property
  912. def connection(self) -> DBAPIConnection:
  913. return self.dbapi_connection
  914. @property
  915. def is_valid(self) -> bool:
  916. """Implement is_valid state attribute.
  917. for the adhoc proxied connection it's assumed the connection is valid
  918. as there is no "invalidate" routine.
  919. """
  920. return self._is_valid
  921. def invalidate(
  922. self, e: Optional[BaseException] = None, soft: bool = False
  923. ) -> None:
  924. self._is_valid = False
  925. @util.ro_non_memoized_property
  926. def record_info(self) -> Optional[_InfoType]:
  927. return self._connection_record.record_info
  928. def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor:
  929. return self.dbapi_connection.cursor(*args, **kwargs)
  930. def __getattr__(self, key: Any) -> Any:
  931. return getattr(self.dbapi_connection, key)
  932. class _ConnectionFairy(PoolProxiedConnection):
  933. """Proxies a DBAPI connection and provides return-on-dereference
  934. support.
  935. This is an internal object used by the :class:`_pool.Pool` implementation
  936. to provide context management to a DBAPI connection delivered by
  937. that :class:`_pool.Pool`. The public facing interface for this class
  938. is described by the :class:`.PoolProxiedConnection` class. See that
  939. class for public API details.
  940. The name "fairy" is inspired by the fact that the
  941. :class:`._ConnectionFairy` object's lifespan is transitory, as it lasts
  942. only for the length of a specific DBAPI connection being checked out from
  943. the pool, and additionally that as a transparent proxy, it is mostly
  944. invisible.
  945. .. seealso::
  946. :class:`.PoolProxiedConnection`
  947. :class:`.ConnectionPoolEntry`
  948. """
  949. __slots__ = (
  950. "dbapi_connection",
  951. "_connection_record",
  952. "_echo",
  953. "_pool",
  954. "_counter",
  955. "__weakref__",
  956. "__dict__",
  957. )
  958. pool: Pool
  959. dbapi_connection: DBAPIConnection
  960. _echo: log._EchoFlagType
  961. def __init__(
  962. self,
  963. pool: Pool,
  964. dbapi_connection: DBAPIConnection,
  965. connection_record: _ConnectionRecord,
  966. echo: log._EchoFlagType,
  967. ):
  968. self._pool = pool
  969. self._counter = 0
  970. self.dbapi_connection = dbapi_connection
  971. self._connection_record = connection_record
  972. self._echo = echo
  973. _connection_record: Optional[_ConnectionRecord]
  974. @property
  975. def driver_connection(self) -> Optional[Any]: # type: ignore[override] # mypy#4125 # noqa: E501
  976. if self._connection_record is None:
  977. return None
  978. return self._connection_record.driver_connection
  979. @property
  980. @util.deprecated(
  981. "2.0",
  982. "The _ConnectionFairy.connection attribute is deprecated; "
  983. "please use 'driver_connection'",
  984. )
  985. def connection(self) -> DBAPIConnection:
  986. return self.dbapi_connection
  987. @classmethod
  988. def _checkout(
  989. cls,
  990. pool: Pool,
  991. threadconns: Optional[threading.local] = None,
  992. fairy: Optional[_ConnectionFairy] = None,
  993. ) -> _ConnectionFairy:
  994. if not fairy:
  995. fairy = _ConnectionRecord.checkout(pool)
  996. if threadconns is not None:
  997. threadconns.current = weakref.ref(fairy)
  998. assert (
  999. fairy._connection_record is not None
  1000. ), "can't 'checkout' a detached connection fairy"
  1001. assert (
  1002. fairy.dbapi_connection is not None
  1003. ), "can't 'checkout' an invalidated connection fairy"
  1004. fairy._counter += 1
  1005. if (
  1006. not pool.dispatch.checkout and not pool._pre_ping
  1007. ) or fairy._counter != 1:
  1008. return fairy
  1009. # Pool listeners can trigger a reconnection on checkout, as well
  1010. # as the pre-pinger.
  1011. # there are three attempts made here, but note that if the database
  1012. # is not accessible from a connection standpoint, those won't proceed
  1013. # here.
  1014. attempts = 2
  1015. while attempts > 0:
  1016. connection_is_fresh = fairy._connection_record.fresh
  1017. fairy._connection_record.fresh = False
  1018. try:
  1019. if pool._pre_ping:
  1020. if not connection_is_fresh:
  1021. if fairy._echo:
  1022. pool.logger.debug(
  1023. "Pool pre-ping on connection %s",
  1024. fairy.dbapi_connection,
  1025. )
  1026. result = pool._dialect._do_ping_w_event(
  1027. fairy.dbapi_connection
  1028. )
  1029. if not result:
  1030. if fairy._echo:
  1031. pool.logger.debug(
  1032. "Pool pre-ping on connection %s failed, "
  1033. "will invalidate pool",
  1034. fairy.dbapi_connection,
  1035. )
  1036. raise exc.InvalidatePoolError()
  1037. elif fairy._echo:
  1038. pool.logger.debug(
  1039. "Connection %s is fresh, skipping pre-ping",
  1040. fairy.dbapi_connection,
  1041. )
  1042. pool.dispatch.checkout(
  1043. fairy.dbapi_connection, fairy._connection_record, fairy
  1044. )
  1045. return fairy
  1046. except exc.DisconnectionError as e:
  1047. if e.invalidate_pool:
  1048. pool.logger.info(
  1049. "Disconnection detected on checkout, "
  1050. "invalidating all pooled connections prior to "
  1051. "current timestamp (reason: %r)",
  1052. e,
  1053. )
  1054. fairy._connection_record.invalidate(e)
  1055. pool._invalidate(fairy, e, _checkin=False)
  1056. else:
  1057. pool.logger.info(
  1058. "Disconnection detected on checkout, "
  1059. "invalidating individual connection %s (reason: %r)",
  1060. fairy.dbapi_connection,
  1061. e,
  1062. )
  1063. fairy._connection_record.invalidate(e)
  1064. try:
  1065. fairy.dbapi_connection = (
  1066. fairy._connection_record.get_connection()
  1067. )
  1068. except BaseException as err:
  1069. with util.safe_reraise():
  1070. fairy._connection_record._checkin_failed(
  1071. err,
  1072. _fairy_was_created=True,
  1073. )
  1074. # prevent _ConnectionFairy from being carried
  1075. # in the stack trace. Do this after the
  1076. # connection record has been checked in, so that
  1077. # if the del triggers a finalize fairy, it won't
  1078. # try to checkin a second time.
  1079. del fairy
  1080. # never called, this is for code linters
  1081. raise
  1082. attempts -= 1
  1083. except BaseException as be_outer:
  1084. with util.safe_reraise():
  1085. rec = fairy._connection_record
  1086. if rec is not None:
  1087. rec._checkin_failed(
  1088. be_outer,
  1089. _fairy_was_created=True,
  1090. )
  1091. # prevent _ConnectionFairy from being carried
  1092. # in the stack trace, see above
  1093. del fairy
  1094. # never called, this is for code linters
  1095. raise
  1096. pool.logger.info("Reconnection attempts exhausted on checkout")
  1097. fairy.invalidate()
  1098. raise exc.InvalidRequestError("This connection is closed")
  1099. def _checkout_existing(self) -> _ConnectionFairy:
  1100. return _ConnectionFairy._checkout(self._pool, fairy=self)
  1101. def _checkin(self, transaction_was_reset: bool = False) -> None:
  1102. _finalize_fairy(
  1103. self.dbapi_connection,
  1104. self._connection_record,
  1105. self._pool,
  1106. None,
  1107. self._echo,
  1108. transaction_was_reset=transaction_was_reset,
  1109. fairy=self,
  1110. )
  1111. def _close(self) -> None:
  1112. self._checkin()
  1113. def _reset(
  1114. self,
  1115. pool: Pool,
  1116. transaction_was_reset: bool,
  1117. terminate_only: bool,
  1118. asyncio_safe: bool,
  1119. ) -> None:
  1120. if pool.dispatch.reset:
  1121. pool.dispatch.reset(
  1122. self.dbapi_connection,
  1123. self._connection_record,
  1124. PoolResetState(
  1125. transaction_was_reset=transaction_was_reset,
  1126. terminate_only=terminate_only,
  1127. asyncio_safe=asyncio_safe,
  1128. ),
  1129. )
  1130. if not asyncio_safe:
  1131. return
  1132. if pool._reset_on_return is reset_rollback:
  1133. if transaction_was_reset:
  1134. if self._echo:
  1135. pool.logger.debug(
  1136. "Connection %s reset, transaction already reset",
  1137. self.dbapi_connection,
  1138. )
  1139. else:
  1140. if self._echo:
  1141. pool.logger.debug(
  1142. "Connection %s rollback-on-return",
  1143. self.dbapi_connection,
  1144. )
  1145. pool._dialect.do_rollback(self)
  1146. elif pool._reset_on_return is reset_commit:
  1147. if self._echo:
  1148. pool.logger.debug(
  1149. "Connection %s commit-on-return",
  1150. self.dbapi_connection,
  1151. )
  1152. pool._dialect.do_commit(self)
  1153. @property
  1154. def _logger(self) -> log._IdentifiedLoggerType:
  1155. return self._pool.logger
  1156. @property
  1157. def is_valid(self) -> bool:
  1158. return self.dbapi_connection is not None
  1159. @property
  1160. def is_detached(self) -> bool:
  1161. return self._connection_record is None
  1162. @util.ro_memoized_property
  1163. def info(self) -> _InfoType:
  1164. if self._connection_record is None:
  1165. return {}
  1166. else:
  1167. return self._connection_record.info
  1168. @util.ro_non_memoized_property
  1169. def record_info(self) -> Optional[_InfoType]:
  1170. if self._connection_record is None:
  1171. return None
  1172. else:
  1173. return self._connection_record.record_info
  1174. def invalidate(
  1175. self, e: Optional[BaseException] = None, soft: bool = False
  1176. ) -> None:
  1177. if self.dbapi_connection is None:
  1178. util.warn("Can't invalidate an already-closed connection.")
  1179. return
  1180. if self._connection_record:
  1181. self._connection_record.invalidate(e=e, soft=soft)
  1182. if not soft:
  1183. # prevent any rollback / reset actions etc. on
  1184. # the connection
  1185. self.dbapi_connection = None # type: ignore
  1186. # finalize
  1187. self._checkin()
  1188. def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor:
  1189. assert self.dbapi_connection is not None
  1190. return self.dbapi_connection.cursor(*args, **kwargs)
  1191. def __getattr__(self, key: str) -> Any:
  1192. return getattr(self.dbapi_connection, key)
  1193. def detach(self) -> None:
  1194. if self._connection_record is not None:
  1195. rec = self._connection_record
  1196. rec.fairy_ref = None
  1197. rec.dbapi_connection = None
  1198. # TODO: should this be _return_conn?
  1199. self._pool._do_return_conn(self._connection_record)
  1200. # can't get the descriptor assignment to work here
  1201. # in pylance. mypy is OK w/ it
  1202. self.info = self.info.copy() # type: ignore
  1203. self._connection_record = None
  1204. if self._pool.dispatch.detach:
  1205. self._pool.dispatch.detach(self.dbapi_connection, rec)
  1206. def close(self) -> None:
  1207. self._counter -= 1
  1208. if self._counter == 0:
  1209. self._checkin()
  1210. def _close_special(self, transaction_reset: bool = False) -> None:
  1211. self._counter -= 1
  1212. if self._counter == 0:
  1213. self._checkin(transaction_was_reset=transaction_reset)