SQLAlchemy 2.0.47__cp313-cp313t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win_amd64.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win_amd64.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1524 @@
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
+
8
+
9
+ """Base constructs for connection pools."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections import deque
14
+ import dataclasses
15
+ from enum import Enum
16
+ import threading
17
+ import time
18
+ import typing
19
+ from typing import Any
20
+ from typing import Callable
21
+ from typing import cast
22
+ from typing import Deque
23
+ from typing import Dict
24
+ from typing import List
25
+ from typing import Optional
26
+ from typing import Tuple
27
+ from typing import TYPE_CHECKING
28
+ from typing import Union
29
+ import weakref
30
+
31
+ from .. import event
32
+ from .. import exc
33
+ from .. import log
34
+ from .. import util
35
+ from ..util.typing import Literal
36
+ from ..util.typing import Protocol
37
+ from ..util.typing import Self
38
+
39
+ if TYPE_CHECKING:
40
+ from ..engine.interfaces import DBAPIConnection
41
+ from ..engine.interfaces import DBAPICursor
42
+ from ..engine.interfaces import Dialect
43
+ from ..event import _DispatchCommon
44
+ from ..event import _ListenerFnType
45
+ from ..event import dispatcher
46
+ from ..sql._typing import _InfoType
47
+
48
+
49
+ @dataclasses.dataclass(frozen=True)
50
+ class PoolResetState:
51
+ """describes the state of a DBAPI connection as it is being passed to
52
+ the :meth:`.PoolEvents.reset` connection pool event.
53
+
54
+ .. versionadded:: 2.0.0b3
55
+
56
+ """
57
+
58
+ __slots__ = ("transaction_was_reset", "terminate_only", "asyncio_safe")
59
+
60
+ transaction_was_reset: bool
61
+ """Indicates if the transaction on the DBAPI connection was already
62
+ essentially "reset" back by the :class:`.Connection` object.
63
+
64
+ This boolean is True if the :class:`.Connection` had transactional
65
+ state present upon it, which was then not closed using the
66
+ :meth:`.Connection.rollback` or :meth:`.Connection.commit` method;
67
+ instead, the transaction was closed inline within the
68
+ :meth:`.Connection.close` method so is guaranteed to remain non-present
69
+ when this event is reached.
70
+
71
+ """
72
+
73
+ terminate_only: bool
74
+ """indicates if the connection is to be immediately terminated and
75
+ not checked in to the pool.
76
+
77
+ This occurs for connections that were invalidated, as well as asyncio
78
+ connections that were not cleanly handled by the calling code that
79
+ are instead being garbage collected. In the latter case,
80
+ operations can't be safely run on asyncio connections within garbage
81
+ collection as there is not necessarily an event loop present.
82
+
83
+ """
84
+
85
+ asyncio_safe: bool
86
+ """Indicates if the reset operation is occurring within a scope where
87
+ an enclosing event loop is expected to be present for asyncio applications.
88
+
89
+ Will be False in the case that the connection is being garbage collected.
90
+
91
+ """
92
+
93
+
94
+ class ResetStyle(Enum):
95
+ """Describe options for "reset on return" behaviors."""
96
+
97
+ reset_rollback = 0
98
+ reset_commit = 1
99
+ reset_none = 2
100
+
101
+
102
+ _ResetStyleArgType = Union[
103
+ ResetStyle,
104
+ Literal[True, None, False, "commit", "rollback"],
105
+ ]
106
+ reset_rollback, reset_commit, reset_none = list(ResetStyle)
107
+
108
+
109
+ class _ConnDialect:
110
+ """partial implementation of :class:`.Dialect`
111
+ which provides DBAPI connection methods.
112
+
113
+ When a :class:`_pool.Pool` is combined with an :class:`_engine.Engine`,
114
+ the :class:`_engine.Engine` replaces this with its own
115
+ :class:`.Dialect`.
116
+
117
+ """
118
+
119
+ is_async = False
120
+ has_terminate = False
121
+
122
+ def do_rollback(self, dbapi_connection: PoolProxiedConnection) -> None:
123
+ dbapi_connection.rollback()
124
+
125
+ def do_commit(self, dbapi_connection: PoolProxiedConnection) -> None:
126
+ dbapi_connection.commit()
127
+
128
+ def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
129
+ dbapi_connection.close()
130
+
131
+ def do_close(self, dbapi_connection: DBAPIConnection) -> None:
132
+ dbapi_connection.close()
133
+
134
+ def _do_ping_w_event(self, dbapi_connection: DBAPIConnection) -> bool:
135
+ raise NotImplementedError(
136
+ "The ping feature requires that a dialect is "
137
+ "passed to the connection pool."
138
+ )
139
+
140
+ def get_driver_connection(self, connection: DBAPIConnection) -> Any:
141
+ return connection
142
+
143
+
144
+ class _AsyncConnDialect(_ConnDialect):
145
+ is_async = True
146
+
147
+
148
+ class _CreatorFnType(Protocol):
149
+ def __call__(self) -> DBAPIConnection: ...
150
+
151
+
152
+ class _CreatorWRecFnType(Protocol):
153
+ def __call__(self, rec: ConnectionPoolEntry) -> DBAPIConnection: ...
154
+
155
+
156
+ class Pool(log.Identified, event.EventTarget):
157
+ """Abstract base class for connection pools."""
158
+
159
+ dispatch: dispatcher[Pool]
160
+ echo: log._EchoFlagType
161
+
162
+ _orig_logging_name: Optional[str]
163
+ _dialect: Union[_ConnDialect, Dialect] = _ConnDialect()
164
+ _creator_arg: Union[_CreatorFnType, _CreatorWRecFnType]
165
+ _invoke_creator: _CreatorWRecFnType
166
+ _invalidate_time: float
167
+
168
+ def __init__(
169
+ self,
170
+ creator: Union[_CreatorFnType, _CreatorWRecFnType],
171
+ recycle: int = -1,
172
+ echo: log._EchoFlagType = None,
173
+ logging_name: Optional[str] = None,
174
+ reset_on_return: _ResetStyleArgType = True,
175
+ events: Optional[List[Tuple[_ListenerFnType, str]]] = None,
176
+ dialect: Optional[Union[_ConnDialect, Dialect]] = None,
177
+ pre_ping: bool = False,
178
+ _dispatch: Optional[_DispatchCommon[Pool]] = None,
179
+ ):
180
+ """
181
+ Construct a Pool.
182
+
183
+ :param creator: a callable function that returns a DB-API
184
+ connection object. The function will be called with
185
+ parameters.
186
+
187
+ :param recycle: If set to a value other than -1, number of
188
+ seconds between connection recycling, which means upon
189
+ checkout, if this timeout is surpassed the connection will be
190
+ closed and replaced with a newly opened connection. Defaults to -1.
191
+
192
+ :param logging_name: String identifier which will be used within
193
+ the "name" field of logging records generated within the
194
+ "sqlalchemy.pool" logger. Defaults to a hexstring of the object's
195
+ id.
196
+
197
+ :param echo: if True, the connection pool will log
198
+ informational output such as when connections are invalidated
199
+ as well as when connections are recycled to the default log handler,
200
+ which defaults to ``sys.stdout`` for output.. If set to the string
201
+ ``"debug"``, the logging will include pool checkouts and checkins.
202
+
203
+ The :paramref:`_pool.Pool.echo` parameter can also be set from the
204
+ :func:`_sa.create_engine` call by using the
205
+ :paramref:`_sa.create_engine.echo_pool` parameter.
206
+
207
+ .. seealso::
208
+
209
+ :ref:`dbengine_logging` - further detail on how to configure
210
+ logging.
211
+
212
+ :param reset_on_return: Determine steps to take on
213
+ connections as they are returned to the pool, which were
214
+ not otherwise handled by a :class:`_engine.Connection`.
215
+ Available from :func:`_sa.create_engine` via the
216
+ :paramref:`_sa.create_engine.pool_reset_on_return` parameter.
217
+
218
+ :paramref:`_pool.Pool.reset_on_return` can have any of these values:
219
+
220
+ * ``"rollback"`` - call rollback() on the connection,
221
+ to release locks and transaction resources.
222
+ This is the default value. The vast majority
223
+ of use cases should leave this value set.
224
+ * ``"commit"`` - call commit() on the connection,
225
+ to release locks and transaction resources.
226
+ A commit here may be desirable for databases that
227
+ cache query plans if a commit is emitted,
228
+ such as Microsoft SQL Server. However, this
229
+ value is more dangerous than 'rollback' because
230
+ any data changes present on the transaction
231
+ are committed unconditionally.
232
+ * ``None`` - don't do anything on the connection.
233
+ This setting may be appropriate if the database / DBAPI
234
+ works in pure "autocommit" mode at all times, or if
235
+ a custom reset handler is established using the
236
+ :meth:`.PoolEvents.reset` event handler.
237
+
238
+ * ``True`` - same as 'rollback', this is here for
239
+ backwards compatibility.
240
+ * ``False`` - same as None, this is here for
241
+ backwards compatibility.
242
+
243
+ For further customization of reset on return, the
244
+ :meth:`.PoolEvents.reset` event hook may be used which can perform
245
+ any connection activity desired on reset.
246
+
247
+ .. seealso::
248
+
249
+ :ref:`pool_reset_on_return`
250
+
251
+ :meth:`.PoolEvents.reset`
252
+
253
+ :param events: a list of 2-tuples, each of the form
254
+ ``(callable, target)`` which will be passed to :func:`.event.listen`
255
+ upon construction. Provided here so that event listeners
256
+ can be assigned via :func:`_sa.create_engine` before dialect-level
257
+ listeners are applied.
258
+
259
+ :param dialect: a :class:`.Dialect` that will handle the job
260
+ of calling rollback(), close(), or commit() on DBAPI connections.
261
+ If omitted, a built-in "stub" dialect is used. Applications that
262
+ make use of :func:`_sa.create_engine` should not use this parameter
263
+ as it is handled by the engine creation strategy.
264
+
265
+ :param pre_ping: if True, the pool will emit a "ping" (typically
266
+ "SELECT 1", but is dialect-specific) on the connection
267
+ upon checkout, to test if the connection is alive or not. If not,
268
+ the connection is transparently re-connected and upon success, all
269
+ other pooled connections established prior to that timestamp are
270
+ invalidated. Requires that a dialect is passed as well to
271
+ interpret the disconnection error.
272
+
273
+ .. versionadded:: 1.2
274
+
275
+ """
276
+ if logging_name:
277
+ self.logging_name = self._orig_logging_name = logging_name
278
+ else:
279
+ self._orig_logging_name = None
280
+
281
+ log.instance_logger(self, echoflag=echo)
282
+ self._creator = creator
283
+ self._recycle = recycle
284
+ self._invalidate_time = 0
285
+ self._pre_ping = pre_ping
286
+ self._reset_on_return = util.parse_user_argument_for_enum(
287
+ reset_on_return,
288
+ {
289
+ ResetStyle.reset_rollback: ["rollback", True],
290
+ ResetStyle.reset_none: ["none", None, False],
291
+ ResetStyle.reset_commit: ["commit"],
292
+ },
293
+ "reset_on_return",
294
+ )
295
+
296
+ self.echo = echo
297
+
298
+ if _dispatch:
299
+ self.dispatch._update(_dispatch, only_propagate=False)
300
+ if dialect:
301
+ self._dialect = dialect
302
+ if events:
303
+ for fn, target in events:
304
+ event.listen(self, target, fn)
305
+
306
+ @util.hybridproperty
307
+ def _is_asyncio(self) -> bool:
308
+ return self._dialect.is_async
309
+
310
+ @property
311
+ def _creator(self) -> Union[_CreatorFnType, _CreatorWRecFnType]:
312
+ return self._creator_arg
313
+
314
+ @_creator.setter
315
+ def _creator(
316
+ self, creator: Union[_CreatorFnType, _CreatorWRecFnType]
317
+ ) -> None:
318
+ self._creator_arg = creator
319
+
320
+ # mypy seems to get super confused assigning functions to
321
+ # attributes
322
+ self._invoke_creator = self._should_wrap_creator(creator)
323
+
324
+ @_creator.deleter
325
+ def _creator(self) -> None:
326
+ # needed for mock testing
327
+ del self._creator_arg
328
+ del self._invoke_creator
329
+
330
+ def _should_wrap_creator(
331
+ self, creator: Union[_CreatorFnType, _CreatorWRecFnType]
332
+ ) -> _CreatorWRecFnType:
333
+ """Detect if creator accepts a single argument, or is sent
334
+ as a legacy style no-arg function.
335
+
336
+ """
337
+
338
+ try:
339
+ argspec = util.get_callable_argspec(self._creator, no_self=True)
340
+ except TypeError:
341
+ creator_fn = cast(_CreatorFnType, creator)
342
+ return lambda rec: creator_fn()
343
+
344
+ if argspec.defaults is not None:
345
+ defaulted = len(argspec.defaults)
346
+ else:
347
+ defaulted = 0
348
+ positionals = len(argspec[0]) - defaulted
349
+
350
+ # look for the exact arg signature that DefaultStrategy
351
+ # sends us
352
+ if (argspec[0], argspec[3]) == (["connection_record"], (None,)):
353
+ return cast(_CreatorWRecFnType, creator)
354
+ # or just a single positional
355
+ elif positionals == 1:
356
+ return cast(_CreatorWRecFnType, creator)
357
+ # all other cases, just wrap and assume legacy "creator" callable
358
+ # thing
359
+ else:
360
+ creator_fn = cast(_CreatorFnType, creator)
361
+ return lambda rec: creator_fn()
362
+
363
+ def _close_connection(
364
+ self, connection: DBAPIConnection, *, terminate: bool = False
365
+ ) -> None:
366
+ self.logger.debug(
367
+ "%s connection %r",
368
+ "Hard-closing" if terminate else "Closing",
369
+ connection,
370
+ )
371
+ try:
372
+ if terminate:
373
+ self._dialect.do_terminate(connection)
374
+ else:
375
+ self._dialect.do_close(connection)
376
+ except BaseException as e:
377
+ self.logger.error(
378
+ f"Exception {'terminating' if terminate else 'closing'} "
379
+ f"connection %r",
380
+ connection,
381
+ exc_info=True,
382
+ )
383
+ if not isinstance(e, Exception):
384
+ raise
385
+
386
+ def _create_connection(self) -> ConnectionPoolEntry:
387
+ """Called by subclasses to create a new ConnectionRecord."""
388
+
389
+ return _ConnectionRecord(self)
390
+
391
+ def _invalidate(
392
+ self,
393
+ connection: PoolProxiedConnection,
394
+ exception: Optional[BaseException] = None,
395
+ _checkin: bool = True,
396
+ ) -> None:
397
+ """Mark all connections established within the generation
398
+ of the given connection as invalidated.
399
+
400
+ If this pool's last invalidate time is before when the given
401
+ connection was created, update the timestamp til now. Otherwise,
402
+ no action is performed.
403
+
404
+ Connections with a start time prior to this pool's invalidation
405
+ time will be recycled upon next checkout.
406
+ """
407
+ rec = getattr(connection, "_connection_record", None)
408
+ if not rec or self._invalidate_time < rec.starttime:
409
+ self._invalidate_time = time.time()
410
+ if _checkin and getattr(connection, "is_valid", False):
411
+ connection.invalidate(exception)
412
+
413
+ def recreate(self) -> Pool:
414
+ """Return a new :class:`_pool.Pool`, of the same class as this one
415
+ and configured with identical creation arguments.
416
+
417
+ This method is used in conjunction with :meth:`dispose`
418
+ to close out an entire :class:`_pool.Pool` and create a new one in
419
+ its place.
420
+
421
+ """
422
+
423
+ raise NotImplementedError()
424
+
425
+ def dispose(self) -> None:
426
+ """Dispose of this pool.
427
+
428
+ This method leaves the possibility of checked-out connections
429
+ remaining open, as it only affects connections that are
430
+ idle in the pool.
431
+
432
+ .. seealso::
433
+
434
+ :meth:`Pool.recreate`
435
+
436
+ """
437
+
438
+ raise NotImplementedError()
439
+
440
+ def connect(self) -> PoolProxiedConnection:
441
+ """Return a DBAPI connection from the pool.
442
+
443
+ The connection is instrumented such that when its
444
+ ``close()`` method is called, the connection will be returned to
445
+ the pool.
446
+
447
+ """
448
+ return _ConnectionFairy._checkout(self)
449
+
450
+ def _return_conn(self, record: ConnectionPoolEntry) -> None:
451
+ """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`.
452
+
453
+ This method is called when an instrumented DBAPI connection
454
+ has its ``close()`` method called.
455
+
456
+ """
457
+ self._do_return_conn(record)
458
+
459
+ def _do_get(self) -> ConnectionPoolEntry:
460
+ """Implementation for :meth:`get`, supplied by subclasses."""
461
+
462
+ raise NotImplementedError()
463
+
464
+ def _do_return_conn(self, record: ConnectionPoolEntry) -> None:
465
+ """Implementation for :meth:`return_conn`, supplied by subclasses."""
466
+
467
+ raise NotImplementedError()
468
+
469
+ def status(self) -> str:
470
+ """Returns a brief description of the state of this pool."""
471
+ raise NotImplementedError()
472
+
473
+
474
+ class ManagesConnection:
475
+ """Common base for the two connection-management interfaces
476
+ :class:`.PoolProxiedConnection` and :class:`.ConnectionPoolEntry`.
477
+
478
+ These two objects are typically exposed in the public facing API
479
+ via the connection pool event hooks, documented at :class:`.PoolEvents`.
480
+
481
+ .. versionadded:: 2.0
482
+
483
+ """
484
+
485
+ __slots__ = ()
486
+
487
+ dbapi_connection: Optional[DBAPIConnection]
488
+ """A reference to the actual DBAPI connection being tracked.
489
+
490
+ This is a :pep:`249`-compliant object that for traditional sync-style
491
+ dialects is provided by the third-party
492
+ DBAPI implementation in use. For asyncio dialects, the implementation
493
+ is typically an adapter object provided by the SQLAlchemy dialect
494
+ itself; the underlying asyncio object is available via the
495
+ :attr:`.ManagesConnection.driver_connection` attribute.
496
+
497
+ SQLAlchemy's interface for the DBAPI connection is based on the
498
+ :class:`.DBAPIConnection` protocol object
499
+
500
+ .. seealso::
501
+
502
+ :attr:`.ManagesConnection.driver_connection`
503
+
504
+ :ref:`faq_dbapi_connection`
505
+
506
+ """
507
+
508
+ driver_connection: Optional[Any]
509
+ """The "driver level" connection object as used by the Python
510
+ DBAPI or database driver.
511
+
512
+ For traditional :pep:`249` DBAPI implementations, this object will
513
+ be the same object as that of
514
+ :attr:`.ManagesConnection.dbapi_connection`. For an asyncio database
515
+ driver, this will be the ultimate "connection" object used by that
516
+ driver, such as the ``asyncpg.Connection`` object which will not have
517
+ standard pep-249 methods.
518
+
519
+ .. versionadded:: 1.4.24
520
+
521
+ .. seealso::
522
+
523
+ :attr:`.ManagesConnection.dbapi_connection`
524
+
525
+ :ref:`faq_dbapi_connection`
526
+
527
+ """
528
+
529
+ @util.ro_memoized_property
530
+ def info(self) -> _InfoType:
531
+ """Info dictionary associated with the underlying DBAPI connection
532
+ referred to by this :class:`.ManagesConnection` instance, allowing
533
+ user-defined data to be associated with the connection.
534
+
535
+ The data in this dictionary is persistent for the lifespan
536
+ of the DBAPI connection itself, including across pool checkins
537
+ and checkouts. When the connection is invalidated
538
+ and replaced with a new one, this dictionary is cleared.
539
+
540
+ For a :class:`.PoolProxiedConnection` instance that's not associated
541
+ with a :class:`.ConnectionPoolEntry`, such as if it were detached, the
542
+ attribute returns a dictionary that is local to that
543
+ :class:`.ConnectionPoolEntry`. Therefore the
544
+ :attr:`.ManagesConnection.info` attribute will always provide a Python
545
+ dictionary.
546
+
547
+ .. seealso::
548
+
549
+ :attr:`.ManagesConnection.record_info`
550
+
551
+
552
+ """
553
+ raise NotImplementedError()
554
+
555
+ @util.ro_memoized_property
556
+ def record_info(self) -> Optional[_InfoType]:
557
+ """Persistent info dictionary associated with this
558
+ :class:`.ManagesConnection`.
559
+
560
+ Unlike the :attr:`.ManagesConnection.info` dictionary, the lifespan
561
+ of this dictionary is that of the :class:`.ConnectionPoolEntry`
562
+ which owns it; therefore this dictionary will persist across
563
+ reconnects and connection invalidation for a particular entry
564
+ in the connection pool.
565
+
566
+ For a :class:`.PoolProxiedConnection` instance that's not associated
567
+ with a :class:`.ConnectionPoolEntry`, such as if it were detached, the
568
+ attribute returns None. Contrast to the :attr:`.ManagesConnection.info`
569
+ dictionary which is never None.
570
+
571
+
572
+ .. seealso::
573
+
574
+ :attr:`.ManagesConnection.info`
575
+
576
+ """
577
+ raise NotImplementedError()
578
+
579
+ def invalidate(
580
+ self, e: Optional[BaseException] = None, soft: bool = False
581
+ ) -> None:
582
+ """Mark the managed connection as invalidated.
583
+
584
+ :param e: an exception object indicating a reason for the invalidation.
585
+
586
+ :param soft: if True, the connection isn't closed; instead, this
587
+ connection will be recycled on next checkout.
588
+
589
+ .. seealso::
590
+
591
+ :ref:`pool_connection_invalidation`
592
+
593
+
594
+ """
595
+ raise NotImplementedError()
596
+
597
+
598
+ class ConnectionPoolEntry(ManagesConnection):
599
+ """Interface for the object that maintains an individual database
600
+ connection on behalf of a :class:`_pool.Pool` instance.
601
+
602
+ The :class:`.ConnectionPoolEntry` object represents the long term
603
+ maintenance of a particular connection for a pool, including expiring or
604
+ invalidating that connection to have it replaced with a new one, which will
605
+ continue to be maintained by that same :class:`.ConnectionPoolEntry`
606
+ instance. Compared to :class:`.PoolProxiedConnection`, which is the
607
+ short-term, per-checkout connection manager, this object lasts for the
608
+ lifespan of a particular "slot" within a connection pool.
609
+
610
+ The :class:`.ConnectionPoolEntry` object is mostly visible to public-facing
611
+ API code when it is delivered to connection pool event hooks, such as
612
+ :meth:`_events.PoolEvents.connect` and :meth:`_events.PoolEvents.checkout`.
613
+
614
+ .. versionadded:: 2.0 :class:`.ConnectionPoolEntry` provides the public
615
+ facing interface for the :class:`._ConnectionRecord` internal class.
616
+
617
+ """
618
+
619
+ __slots__ = ()
620
+
621
+ @property
622
+ def in_use(self) -> bool:
623
+ """Return True the connection is currently checked out"""
624
+
625
+ raise NotImplementedError()
626
+
627
+ def close(self) -> None:
628
+ """Close the DBAPI connection managed by this connection pool entry."""
629
+ raise NotImplementedError()
630
+
631
+
632
+ class _ConnectionRecord(ConnectionPoolEntry):
633
+ """Maintains a position in a connection pool which references a pooled
634
+ connection.
635
+
636
+ This is an internal object used by the :class:`_pool.Pool` implementation
637
+ to provide context management to a DBAPI connection maintained by
638
+ that :class:`_pool.Pool`. The public facing interface for this class
639
+ is described by the :class:`.ConnectionPoolEntry` class. See that
640
+ class for public API details.
641
+
642
+ .. seealso::
643
+
644
+ :class:`.ConnectionPoolEntry`
645
+
646
+ :class:`.PoolProxiedConnection`
647
+
648
+ """
649
+
650
+ __slots__ = (
651
+ "__pool",
652
+ "fairy_ref",
653
+ "finalize_callback",
654
+ "fresh",
655
+ "starttime",
656
+ "dbapi_connection",
657
+ "__weakref__",
658
+ "__dict__",
659
+ )
660
+
661
+ finalize_callback: Deque[Callable[[DBAPIConnection], None]]
662
+ fresh: bool
663
+ fairy_ref: Optional[weakref.ref[_ConnectionFairy]]
664
+ starttime: float
665
+
666
+ def __init__(self, pool: Pool, connect: bool = True):
667
+ self.fresh = False
668
+ self.fairy_ref = None
669
+ self.starttime = 0
670
+ self.dbapi_connection = None
671
+
672
+ self.__pool = pool
673
+ if connect:
674
+ self.__connect()
675
+ self.finalize_callback = deque()
676
+
677
+ dbapi_connection: Optional[DBAPIConnection]
678
+
679
+ @property
680
+ def driver_connection(self) -> Optional[Any]: # type: ignore[override] # mypy#4125 # noqa: E501
681
+ if self.dbapi_connection is None:
682
+ return None
683
+ else:
684
+ return self.__pool._dialect.get_driver_connection(
685
+ self.dbapi_connection
686
+ )
687
+
688
+ @property
689
+ @util.deprecated(
690
+ "2.0",
691
+ "The _ConnectionRecord.connection attribute is deprecated; "
692
+ "please use 'driver_connection'",
693
+ )
694
+ def connection(self) -> Optional[DBAPIConnection]:
695
+ return self.dbapi_connection
696
+
697
+ _soft_invalidate_time: float = 0
698
+
699
+ @util.ro_memoized_property
700
+ def info(self) -> _InfoType:
701
+ return {}
702
+
703
+ @util.ro_memoized_property
704
+ def record_info(self) -> Optional[_InfoType]:
705
+ return {}
706
+
707
+ @classmethod
708
+ def checkout(cls, pool: Pool) -> _ConnectionFairy:
709
+ if TYPE_CHECKING:
710
+ rec = cast(_ConnectionRecord, pool._do_get())
711
+ else:
712
+ rec = pool._do_get()
713
+
714
+ try:
715
+ dbapi_connection = rec.get_connection()
716
+ except BaseException as err:
717
+ with util.safe_reraise():
718
+ rec._checkin_failed(err, _fairy_was_created=False)
719
+
720
+ # not reached, for code linters only
721
+ raise
722
+
723
+ echo = pool._should_log_debug()
724
+ fairy = _ConnectionFairy(pool, dbapi_connection, rec, echo)
725
+
726
+ rec.fairy_ref = ref = weakref.ref(
727
+ fairy,
728
+ lambda ref: (
729
+ _finalize_fairy(
730
+ None, rec, pool, ref, echo, transaction_was_reset=False
731
+ )
732
+ if _finalize_fairy is not None
733
+ else None
734
+ ),
735
+ )
736
+ _strong_ref_connection_records[ref] = rec
737
+ if echo:
738
+ pool.logger.debug(
739
+ "Connection %r checked out from pool", dbapi_connection
740
+ )
741
+ return fairy
742
+
743
+ def _checkin_failed(
744
+ self, err: BaseException, _fairy_was_created: bool = True
745
+ ) -> None:
746
+ self.invalidate(e=err)
747
+ self.checkin(
748
+ _fairy_was_created=_fairy_was_created,
749
+ )
750
+
751
+ def checkin(self, _fairy_was_created: bool = True) -> None:
752
+ if self.fairy_ref is None and _fairy_was_created:
753
+ # _fairy_was_created is False for the initial get connection phase;
754
+ # meaning there was no _ConnectionFairy and we must unconditionally
755
+ # do a checkin.
756
+ #
757
+ # otherwise, if fairy_was_created==True, if fairy_ref is None here
758
+ # that means we were checked in already, so this looks like
759
+ # a double checkin.
760
+ util.warn("Double checkin attempted on %s" % self)
761
+ return
762
+ self.fairy_ref = None
763
+ connection = self.dbapi_connection
764
+ pool = self.__pool
765
+ while self.finalize_callback:
766
+ finalizer = self.finalize_callback.pop()
767
+ if connection is not None:
768
+ finalizer(connection)
769
+ if pool.dispatch.checkin:
770
+ pool.dispatch.checkin(connection, self)
771
+
772
+ pool._return_conn(self)
773
+
774
+ @property
775
+ def in_use(self) -> bool:
776
+ return self.fairy_ref is not None
777
+
778
+ @property
779
+ def last_connect_time(self) -> float:
780
+ return self.starttime
781
+
782
+ def close(self) -> None:
783
+ if self.dbapi_connection is not None:
784
+ self.__close()
785
+
786
+ def invalidate(
787
+ self, e: Optional[BaseException] = None, soft: bool = False
788
+ ) -> None:
789
+ # already invalidated
790
+ if self.dbapi_connection is None:
791
+ return
792
+ if soft:
793
+ self.__pool.dispatch.soft_invalidate(
794
+ self.dbapi_connection, self, e
795
+ )
796
+ else:
797
+ self.__pool.dispatch.invalidate(self.dbapi_connection, self, e)
798
+ if e is not None:
799
+ self.__pool.logger.info(
800
+ "%sInvalidate connection %r (reason: %s:%s)",
801
+ "Soft " if soft else "",
802
+ self.dbapi_connection,
803
+ e.__class__.__name__,
804
+ e,
805
+ )
806
+ else:
807
+ self.__pool.logger.info(
808
+ "%sInvalidate connection %r",
809
+ "Soft " if soft else "",
810
+ self.dbapi_connection,
811
+ )
812
+
813
+ if soft:
814
+ self._soft_invalidate_time = time.time()
815
+ else:
816
+ self.__close(terminate=True)
817
+ self.dbapi_connection = None
818
+
819
+ def get_connection(self) -> DBAPIConnection:
820
+ recycle = False
821
+
822
+ # NOTE: the various comparisons here are assuming that measurable time
823
+ # passes between these state changes. however, time.time() is not
824
+ # guaranteed to have sub-second precision. comparisons of
825
+ # "invalidation time" to "starttime" should perhaps use >= so that the
826
+ # state change can take place assuming no measurable time has passed,
827
+ # however this does not guarantee correct behavior here as if time
828
+ # continues to not pass, it will try to reconnect repeatedly until
829
+ # these timestamps diverge, so in that sense using > is safer. Per
830
+ # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be
831
+ # within 16 milliseconds accuracy, so unit tests for connection
832
+ # invalidation need a sleep of at least this long between initial start
833
+ # time and invalidation for the logic below to work reliably.
834
+
835
+ if self.dbapi_connection is None:
836
+ self.info.clear()
837
+ self.__connect()
838
+ elif (
839
+ self.__pool._recycle > -1
840
+ and time.time() - self.starttime > self.__pool._recycle
841
+ ):
842
+ self.__pool.logger.info(
843
+ "Connection %r exceeded timeout; recycling",
844
+ self.dbapi_connection,
845
+ )
846
+ recycle = True
847
+ elif self.__pool._invalidate_time > self.starttime:
848
+ self.__pool.logger.info(
849
+ "Connection %r invalidated due to pool invalidation; "
850
+ + "recycling",
851
+ self.dbapi_connection,
852
+ )
853
+ recycle = True
854
+ elif self._soft_invalidate_time > self.starttime:
855
+ self.__pool.logger.info(
856
+ "Connection %r invalidated due to local soft invalidation; "
857
+ + "recycling",
858
+ self.dbapi_connection,
859
+ )
860
+ recycle = True
861
+
862
+ if recycle:
863
+ self.__close(terminate=True)
864
+ self.info.clear()
865
+
866
+ self.__connect()
867
+
868
+ assert self.dbapi_connection is not None
869
+ return self.dbapi_connection
870
+
871
+ def _is_hard_or_soft_invalidated(self) -> bool:
872
+ return (
873
+ self.dbapi_connection is None
874
+ or self.__pool._invalidate_time > self.starttime
875
+ or (self._soft_invalidate_time > self.starttime)
876
+ )
877
+
878
+ def __close(self, *, terminate: bool = False) -> None:
879
+ self.finalize_callback.clear()
880
+ if self.__pool.dispatch.close:
881
+ self.__pool.dispatch.close(self.dbapi_connection, self)
882
+ assert self.dbapi_connection is not None
883
+ self.__pool._close_connection(
884
+ self.dbapi_connection, terminate=terminate
885
+ )
886
+ self.dbapi_connection = None
887
+
888
+ def __connect(self) -> None:
889
+ pool = self.__pool
890
+
891
+ # ensure any existing connection is removed, so that if
892
+ # creator fails, this attribute stays None
893
+ self.dbapi_connection = None
894
+ try:
895
+ self.starttime = time.time()
896
+ self.dbapi_connection = connection = pool._invoke_creator(self)
897
+ pool.logger.debug("Created new connection %r", connection)
898
+ self.fresh = True
899
+ except BaseException as e:
900
+ with util.safe_reraise():
901
+ pool.logger.debug("Error on connect(): %s", e)
902
+ else:
903
+ # in SQLAlchemy 1.4 the first_connect event is not used by
904
+ # the engine, so this will usually not be set
905
+ if pool.dispatch.first_connect:
906
+ pool.dispatch.first_connect.for_modify(
907
+ pool.dispatch
908
+ ).exec_once_unless_exception(self.dbapi_connection, self)
909
+
910
+ # init of the dialect now takes place within the connect
911
+ # event, so ensure a mutex is used on the first run
912
+ pool.dispatch.connect.for_modify(
913
+ pool.dispatch
914
+ )._exec_w_sync_on_first_run(self.dbapi_connection, self)
915
+
916
+
917
+ def _finalize_fairy(
918
+ dbapi_connection: Optional[DBAPIConnection],
919
+ connection_record: Optional[_ConnectionRecord],
920
+ pool: Pool,
921
+ ref: Optional[
922
+ weakref.ref[_ConnectionFairy]
923
+ ], # this is None when called directly, not by the gc
924
+ echo: Optional[log._EchoFlagType],
925
+ transaction_was_reset: bool = False,
926
+ fairy: Optional[_ConnectionFairy] = None,
927
+ ) -> None:
928
+ """Cleanup for a :class:`._ConnectionFairy` whether or not it's already
929
+ been garbage collected.
930
+
931
+ When using an async dialect no IO can happen here (without using
932
+ a dedicated thread), since this is called outside the greenlet
933
+ context and with an already running loop. In this case function
934
+ will only log a message and raise a warning.
935
+ """
936
+
937
+ is_gc_cleanup = ref is not None
938
+
939
+ if is_gc_cleanup:
940
+ assert ref is not None
941
+ _strong_ref_connection_records.pop(ref, None)
942
+ assert connection_record is not None
943
+ if connection_record.fairy_ref is not ref:
944
+ return
945
+ assert dbapi_connection is None
946
+ dbapi_connection = connection_record.dbapi_connection
947
+
948
+ elif fairy:
949
+ _strong_ref_connection_records.pop(weakref.ref(fairy), None)
950
+
951
+ # null pool is not _is_asyncio but can be used also with async dialects
952
+ dont_restore_gced = pool._dialect.is_async
953
+
954
+ if dont_restore_gced:
955
+ detach = connection_record is None or is_gc_cleanup
956
+ can_manipulate_connection = not is_gc_cleanup
957
+ can_close_or_terminate_connection = (
958
+ not pool._dialect.is_async or pool._dialect.has_terminate
959
+ )
960
+ requires_terminate_for_close = (
961
+ pool._dialect.is_async and pool._dialect.has_terminate
962
+ )
963
+
964
+ else:
965
+ detach = connection_record is None
966
+ can_manipulate_connection = can_close_or_terminate_connection = True
967
+ requires_terminate_for_close = False
968
+
969
+ if dbapi_connection is not None:
970
+ if connection_record and echo:
971
+ pool.logger.debug(
972
+ "Connection %r being returned to pool", dbapi_connection
973
+ )
974
+
975
+ try:
976
+ if not fairy:
977
+ assert connection_record is not None
978
+ fairy = _ConnectionFairy(
979
+ pool,
980
+ dbapi_connection,
981
+ connection_record,
982
+ echo,
983
+ )
984
+ assert fairy.dbapi_connection is dbapi_connection
985
+
986
+ fairy._reset(
987
+ pool,
988
+ transaction_was_reset=transaction_was_reset,
989
+ terminate_only=detach,
990
+ asyncio_safe=can_manipulate_connection,
991
+ )
992
+
993
+ if detach:
994
+ if connection_record:
995
+ fairy._pool = pool
996
+ fairy.detach()
997
+
998
+ if can_close_or_terminate_connection:
999
+ if pool.dispatch.close_detached:
1000
+ pool.dispatch.close_detached(dbapi_connection)
1001
+
1002
+ pool._close_connection(
1003
+ dbapi_connection,
1004
+ terminate=requires_terminate_for_close,
1005
+ )
1006
+
1007
+ except BaseException as e:
1008
+ pool.logger.error(
1009
+ "Exception during reset or similar", exc_info=True
1010
+ )
1011
+ if connection_record:
1012
+ connection_record.invalidate(e=e)
1013
+ if not isinstance(e, Exception):
1014
+ raise
1015
+ finally:
1016
+ if detach and is_gc_cleanup and dont_restore_gced:
1017
+ message = (
1018
+ "The garbage collector is trying to clean up "
1019
+ f"non-checked-in connection {dbapi_connection!r}, "
1020
+ f"""which will be {
1021
+ 'dropped, as it cannot be safely terminated'
1022
+ if not can_close_or_terminate_connection
1023
+ else 'terminated'
1024
+ }. """
1025
+ "Please ensure that SQLAlchemy pooled connections are "
1026
+ "returned to "
1027
+ "the pool explicitly, either by calling ``close()`` "
1028
+ "or by using appropriate context managers to manage "
1029
+ "their lifecycle."
1030
+ )
1031
+ pool.logger.error(message)
1032
+ util.warn(message)
1033
+
1034
+ if connection_record and connection_record.fairy_ref is not None:
1035
+ connection_record.checkin()
1036
+
1037
+ # give gc some help. See
1038
+ # test/engine/test_pool.py::PoolEventsTest::test_checkin_event_gc[True]
1039
+ # which actually started failing when pytest warnings plugin was
1040
+ # turned on, due to util.warn() above
1041
+ if fairy is not None:
1042
+ fairy.dbapi_connection = None # type: ignore
1043
+ fairy._connection_record = None
1044
+ del dbapi_connection
1045
+ del connection_record
1046
+ del fairy
1047
+
1048
+
1049
+ # a dictionary of the _ConnectionFairy weakrefs to _ConnectionRecord, so that
1050
+ # GC under pypy will call ConnectionFairy finalizers. linked directly to the
1051
+ # weakref that will empty itself when collected so that it should not create
1052
+ # any unmanaged memory references.
1053
+ _strong_ref_connection_records: Dict[
1054
+ weakref.ref[_ConnectionFairy], _ConnectionRecord
1055
+ ] = {}
1056
+
1057
+
1058
+ class PoolProxiedConnection(ManagesConnection):
1059
+ """A connection-like adapter for a :pep:`249` DBAPI connection, which
1060
+ includes additional methods specific to the :class:`.Pool` implementation.
1061
+
1062
+ :class:`.PoolProxiedConnection` is the public-facing interface for the
1063
+ internal :class:`._ConnectionFairy` implementation object; users familiar
1064
+ with :class:`._ConnectionFairy` can consider this object to be equivalent.
1065
+
1066
+ .. versionadded:: 2.0 :class:`.PoolProxiedConnection` provides the public-
1067
+ facing interface for the :class:`._ConnectionFairy` internal class.
1068
+
1069
+ """
1070
+
1071
+ __slots__ = ()
1072
+
1073
+ if typing.TYPE_CHECKING:
1074
+
1075
+ def commit(self) -> None: ...
1076
+
1077
+ def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor: ...
1078
+
1079
+ def rollback(self) -> None: ...
1080
+
1081
+ def __getattr__(self, key: str) -> Any: ...
1082
+
1083
+ @property
1084
+ def is_valid(self) -> bool:
1085
+ """Return True if this :class:`.PoolProxiedConnection` still refers
1086
+ to an active DBAPI connection."""
1087
+
1088
+ raise NotImplementedError()
1089
+
1090
+ @property
1091
+ def is_detached(self) -> bool:
1092
+ """Return True if this :class:`.PoolProxiedConnection` is detached
1093
+ from its pool."""
1094
+
1095
+ raise NotImplementedError()
1096
+
1097
+ def detach(self) -> None:
1098
+ """Separate this connection from its Pool.
1099
+
1100
+ This means that the connection will no longer be returned to the
1101
+ pool when closed, and will instead be literally closed. The
1102
+ associated :class:`.ConnectionPoolEntry` is de-associated from this
1103
+ DBAPI connection.
1104
+
1105
+ Note that any overall connection limiting constraints imposed by a
1106
+ Pool implementation may be violated after a detach, as the detached
1107
+ connection is removed from the pool's knowledge and control.
1108
+
1109
+ """
1110
+
1111
+ raise NotImplementedError()
1112
+
1113
+ def close(self) -> None:
1114
+ """Release this connection back to the pool.
1115
+
1116
+ The :meth:`.PoolProxiedConnection.close` method shadows the
1117
+ :pep:`249` ``.close()`` method, altering its behavior to instead
1118
+ :term:`release` the proxied connection back to the connection pool.
1119
+
1120
+ Upon release to the pool, whether the connection stays "opened" and
1121
+ pooled in the Python process, versus actually closed out and removed
1122
+ from the Python process, is based on the pool implementation in use and
1123
+ its configuration and current state.
1124
+
1125
+ """
1126
+ raise NotImplementedError()
1127
+
1128
+ def __enter__(self) -> Self:
1129
+ return self
1130
+
1131
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
1132
+ self.close()
1133
+ return None
1134
+
1135
+
1136
+ class _AdhocProxiedConnection(PoolProxiedConnection):
1137
+ """provides the :class:`.PoolProxiedConnection` interface for cases where
1138
+ the DBAPI connection is not actually proxied.
1139
+
1140
+ This is used by the engine internals to pass a consistent
1141
+ :class:`.PoolProxiedConnection` object to consuming dialects in response to
1142
+ pool events that may not always have the :class:`._ConnectionFairy`
1143
+ available.
1144
+
1145
+ """
1146
+
1147
+ __slots__ = ("dbapi_connection", "_connection_record", "_is_valid")
1148
+
1149
+ dbapi_connection: DBAPIConnection
1150
+ _connection_record: ConnectionPoolEntry
1151
+
1152
+ def __init__(
1153
+ self,
1154
+ dbapi_connection: DBAPIConnection,
1155
+ connection_record: ConnectionPoolEntry,
1156
+ ):
1157
+ self.dbapi_connection = dbapi_connection
1158
+ self._connection_record = connection_record
1159
+ self._is_valid = True
1160
+
1161
+ @property
1162
+ def driver_connection(self) -> Any: # type: ignore[override] # mypy#4125
1163
+ return self._connection_record.driver_connection
1164
+
1165
+ @property
1166
+ def connection(self) -> DBAPIConnection:
1167
+ return self.dbapi_connection
1168
+
1169
+ @property
1170
+ def is_valid(self) -> bool:
1171
+ """Implement is_valid state attribute.
1172
+
1173
+ for the adhoc proxied connection it's assumed the connection is valid
1174
+ as there is no "invalidate" routine.
1175
+
1176
+ """
1177
+ return self._is_valid
1178
+
1179
+ def invalidate(
1180
+ self, e: Optional[BaseException] = None, soft: bool = False
1181
+ ) -> None:
1182
+ self._is_valid = False
1183
+
1184
+ @util.ro_non_memoized_property
1185
+ def record_info(self) -> Optional[_InfoType]:
1186
+ return self._connection_record.record_info
1187
+
1188
+ def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor:
1189
+ return self.dbapi_connection.cursor(*args, **kwargs)
1190
+
1191
+ def __getattr__(self, key: Any) -> Any:
1192
+ return getattr(self.dbapi_connection, key)
1193
+
1194
+
1195
+ class _ConnectionFairy(PoolProxiedConnection):
1196
+ """Proxies a DBAPI connection and provides return-on-dereference
1197
+ support.
1198
+
1199
+ This is an internal object used by the :class:`_pool.Pool` implementation
1200
+ to provide context management to a DBAPI connection delivered by
1201
+ that :class:`_pool.Pool`. The public facing interface for this class
1202
+ is described by the :class:`.PoolProxiedConnection` class. See that
1203
+ class for public API details.
1204
+
1205
+ The name "fairy" is inspired by the fact that the
1206
+ :class:`._ConnectionFairy` object's lifespan is transitory, as it lasts
1207
+ only for the length of a specific DBAPI connection being checked out from
1208
+ the pool, and additionally that as a transparent proxy, it is mostly
1209
+ invisible.
1210
+
1211
+ .. seealso::
1212
+
1213
+ :class:`.PoolProxiedConnection`
1214
+
1215
+ :class:`.ConnectionPoolEntry`
1216
+
1217
+
1218
+ """
1219
+
1220
+ __slots__ = (
1221
+ "dbapi_connection",
1222
+ "_connection_record",
1223
+ "_echo",
1224
+ "_pool",
1225
+ "_counter",
1226
+ "__weakref__",
1227
+ "__dict__",
1228
+ )
1229
+
1230
+ pool: Pool
1231
+ dbapi_connection: DBAPIConnection
1232
+ _echo: log._EchoFlagType
1233
+
1234
+ def __init__(
1235
+ self,
1236
+ pool: Pool,
1237
+ dbapi_connection: DBAPIConnection,
1238
+ connection_record: _ConnectionRecord,
1239
+ echo: log._EchoFlagType,
1240
+ ):
1241
+ self._pool = pool
1242
+ self._counter = 0
1243
+ self.dbapi_connection = dbapi_connection
1244
+ self._connection_record = connection_record
1245
+ self._echo = echo
1246
+
1247
+ _connection_record: Optional[_ConnectionRecord]
1248
+
1249
+ @property
1250
+ def driver_connection(self) -> Optional[Any]: # type: ignore[override] # mypy#4125 # noqa: E501
1251
+ if self._connection_record is None:
1252
+ return None
1253
+ return self._connection_record.driver_connection
1254
+
1255
+ @property
1256
+ @util.deprecated(
1257
+ "2.0",
1258
+ "The _ConnectionFairy.connection attribute is deprecated; "
1259
+ "please use 'driver_connection'",
1260
+ )
1261
+ def connection(self) -> DBAPIConnection:
1262
+ return self.dbapi_connection
1263
+
1264
+ @classmethod
1265
+ def _checkout(
1266
+ cls,
1267
+ pool: Pool,
1268
+ threadconns: Optional[threading.local] = None,
1269
+ fairy: Optional[_ConnectionFairy] = None,
1270
+ ) -> _ConnectionFairy:
1271
+ if not fairy:
1272
+ fairy = _ConnectionRecord.checkout(pool)
1273
+
1274
+ if threadconns is not None:
1275
+ threadconns.current = weakref.ref(fairy)
1276
+
1277
+ assert (
1278
+ fairy._connection_record is not None
1279
+ ), "can't 'checkout' a detached connection fairy"
1280
+ assert (
1281
+ fairy.dbapi_connection is not None
1282
+ ), "can't 'checkout' an invalidated connection fairy"
1283
+
1284
+ fairy._counter += 1
1285
+ if (
1286
+ not pool.dispatch.checkout and not pool._pre_ping
1287
+ ) or fairy._counter != 1:
1288
+ return fairy
1289
+
1290
+ # Pool listeners can trigger a reconnection on checkout, as well
1291
+ # as the pre-pinger.
1292
+ # there are three attempts made here, but note that if the database
1293
+ # is not accessible from a connection standpoint, those won't proceed
1294
+ # here.
1295
+
1296
+ attempts = 2
1297
+
1298
+ while attempts > 0:
1299
+ connection_is_fresh = fairy._connection_record.fresh
1300
+ fairy._connection_record.fresh = False
1301
+ try:
1302
+ if pool._pre_ping:
1303
+ if not connection_is_fresh:
1304
+ if fairy._echo:
1305
+ pool.logger.debug(
1306
+ "Pool pre-ping on connection %s",
1307
+ fairy.dbapi_connection,
1308
+ )
1309
+ result = pool._dialect._do_ping_w_event(
1310
+ fairy.dbapi_connection
1311
+ )
1312
+ if not result:
1313
+ if fairy._echo:
1314
+ pool.logger.debug(
1315
+ "Pool pre-ping on connection %s failed, "
1316
+ "will invalidate pool",
1317
+ fairy.dbapi_connection,
1318
+ )
1319
+ raise exc.InvalidatePoolError()
1320
+ elif fairy._echo:
1321
+ pool.logger.debug(
1322
+ "Connection %s is fresh, skipping pre-ping",
1323
+ fairy.dbapi_connection,
1324
+ )
1325
+
1326
+ pool.dispatch.checkout(
1327
+ fairy.dbapi_connection, fairy._connection_record, fairy
1328
+ )
1329
+ return fairy
1330
+ except exc.DisconnectionError as e:
1331
+ if e.invalidate_pool:
1332
+ pool.logger.info(
1333
+ "Disconnection detected on checkout, "
1334
+ "invalidating all pooled connections prior to "
1335
+ "current timestamp (reason: %r)",
1336
+ e,
1337
+ )
1338
+ fairy._connection_record.invalidate(e)
1339
+ pool._invalidate(fairy, e, _checkin=False)
1340
+ else:
1341
+ pool.logger.info(
1342
+ "Disconnection detected on checkout, "
1343
+ "invalidating individual connection %s (reason: %r)",
1344
+ fairy.dbapi_connection,
1345
+ e,
1346
+ )
1347
+ fairy._connection_record.invalidate(e)
1348
+ try:
1349
+ fairy.dbapi_connection = (
1350
+ fairy._connection_record.get_connection()
1351
+ )
1352
+ except BaseException as err:
1353
+ with util.safe_reraise():
1354
+ fairy._connection_record._checkin_failed(
1355
+ err,
1356
+ _fairy_was_created=True,
1357
+ )
1358
+
1359
+ # prevent _ConnectionFairy from being carried
1360
+ # in the stack trace. Do this after the
1361
+ # connection record has been checked in, so that
1362
+ # if the del triggers a finalize fairy, it won't
1363
+ # try to checkin a second time.
1364
+ del fairy
1365
+
1366
+ # never called, this is for code linters
1367
+ raise
1368
+
1369
+ attempts -= 1
1370
+ except BaseException as be_outer:
1371
+ with util.safe_reraise():
1372
+ rec = fairy._connection_record
1373
+ if rec is not None:
1374
+ rec._checkin_failed(
1375
+ be_outer,
1376
+ _fairy_was_created=True,
1377
+ )
1378
+
1379
+ # prevent _ConnectionFairy from being carried
1380
+ # in the stack trace, see above
1381
+ del fairy
1382
+
1383
+ # never called, this is for code linters
1384
+ raise
1385
+
1386
+ pool.logger.info("Reconnection attempts exhausted on checkout")
1387
+ fairy.invalidate()
1388
+ raise exc.InvalidRequestError("This connection is closed")
1389
+
1390
+ def _checkout_existing(self) -> _ConnectionFairy:
1391
+ return _ConnectionFairy._checkout(self._pool, fairy=self)
1392
+
1393
+ def _checkin(self, transaction_was_reset: bool = False) -> None:
1394
+ _finalize_fairy(
1395
+ self.dbapi_connection,
1396
+ self._connection_record,
1397
+ self._pool,
1398
+ None,
1399
+ self._echo,
1400
+ transaction_was_reset=transaction_was_reset,
1401
+ fairy=self,
1402
+ )
1403
+
1404
+ def _close(self) -> None:
1405
+ self._checkin()
1406
+
1407
+ def _reset(
1408
+ self,
1409
+ pool: Pool,
1410
+ transaction_was_reset: bool,
1411
+ terminate_only: bool,
1412
+ asyncio_safe: bool,
1413
+ ) -> None:
1414
+ if pool.dispatch.reset:
1415
+ pool.dispatch.reset(
1416
+ self.dbapi_connection,
1417
+ self._connection_record,
1418
+ PoolResetState(
1419
+ transaction_was_reset=transaction_was_reset,
1420
+ terminate_only=terminate_only,
1421
+ asyncio_safe=asyncio_safe,
1422
+ ),
1423
+ )
1424
+
1425
+ if not asyncio_safe:
1426
+ return
1427
+
1428
+ if pool._reset_on_return is reset_rollback:
1429
+ if transaction_was_reset:
1430
+ if self._echo:
1431
+ pool.logger.debug(
1432
+ "Connection %s reset, transaction already reset",
1433
+ self.dbapi_connection,
1434
+ )
1435
+ else:
1436
+ if self._echo:
1437
+ pool.logger.debug(
1438
+ "Connection %s rollback-on-return",
1439
+ self.dbapi_connection,
1440
+ )
1441
+ pool._dialect.do_rollback(self)
1442
+ elif pool._reset_on_return is reset_commit:
1443
+ if self._echo:
1444
+ pool.logger.debug(
1445
+ "Connection %s commit-on-return",
1446
+ self.dbapi_connection,
1447
+ )
1448
+ pool._dialect.do_commit(self)
1449
+
1450
+ @property
1451
+ def _logger(self) -> log._IdentifiedLoggerType:
1452
+ return self._pool.logger
1453
+
1454
+ @property
1455
+ def is_valid(self) -> bool:
1456
+ return self.dbapi_connection is not None
1457
+
1458
+ @property
1459
+ def is_detached(self) -> bool:
1460
+ return self._connection_record is None
1461
+
1462
+ @util.ro_memoized_property
1463
+ def info(self) -> _InfoType:
1464
+ if self._connection_record is None:
1465
+ return {}
1466
+ else:
1467
+ return self._connection_record.info
1468
+
1469
+ @util.ro_non_memoized_property
1470
+ def record_info(self) -> Optional[_InfoType]:
1471
+ if self._connection_record is None:
1472
+ return None
1473
+ else:
1474
+ return self._connection_record.record_info
1475
+
1476
+ def invalidate(
1477
+ self, e: Optional[BaseException] = None, soft: bool = False
1478
+ ) -> None:
1479
+ if self.dbapi_connection is None:
1480
+ util.warn("Can't invalidate an already-closed connection.")
1481
+ return
1482
+ if self._connection_record:
1483
+ self._connection_record.invalidate(e=e, soft=soft)
1484
+ if not soft:
1485
+ # prevent any rollback / reset actions etc. on
1486
+ # the connection
1487
+ self.dbapi_connection = None # type: ignore
1488
+
1489
+ # finalize
1490
+ self._checkin()
1491
+
1492
+ def cursor(self, *args: Any, **kwargs: Any) -> DBAPICursor:
1493
+ assert self.dbapi_connection is not None
1494
+ return self.dbapi_connection.cursor(*args, **kwargs)
1495
+
1496
+ def __getattr__(self, key: str) -> Any:
1497
+ return getattr(self.dbapi_connection, key)
1498
+
1499
+ def detach(self) -> None:
1500
+ if self._connection_record is not None:
1501
+ rec = self._connection_record
1502
+ rec.fairy_ref = None
1503
+ rec.dbapi_connection = None
1504
+ # TODO: should this be _return_conn?
1505
+ self._pool._do_return_conn(self._connection_record)
1506
+
1507
+ # can't get the descriptor assignment to work here
1508
+ # in pylance. mypy is OK w/ it
1509
+ self.info = self.info.copy() # type: ignore
1510
+
1511
+ self._connection_record = None
1512
+
1513
+ if self._pool.dispatch.detach:
1514
+ self._pool.dispatch.detach(self.dbapi_connection, rec)
1515
+
1516
+ def close(self) -> None:
1517
+ self._counter -= 1
1518
+ if self._counter == 0:
1519
+ self._checkin()
1520
+
1521
+ def _close_special(self, transaction_reset: bool = False) -> None:
1522
+ self._counter -= 1
1523
+ if self._counter == 0:
1524
+ self._checkin(transaction_was_reset=transaction_reset)