base.py 51 KB

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