SQLAlchemy 2.1.0rc1__cp315-cp315-win32.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 (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win32.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win32.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win32.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win32.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win32.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win32.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win32.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win32.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3357 @@
1
+ # engine/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
+ """Defines :class:`_engine.Connection` and :class:`_engine.Engine`."""
8
+
9
+ from __future__ import annotations
10
+
11
+ import contextlib
12
+ import sys
13
+ import typing
14
+ from typing import Any
15
+ from typing import Callable
16
+ from typing import cast
17
+ from typing import Iterable
18
+ from typing import Iterator
19
+ from typing import List
20
+ from typing import Mapping
21
+ from typing import NoReturn
22
+ from typing import Optional
23
+ from typing import overload
24
+ from typing import Tuple
25
+ from typing import Type
26
+ from typing import TypeVar
27
+ from typing import Union
28
+
29
+ from .interfaces import BindTyping
30
+ from .interfaces import ConnectionEventsTarget
31
+ from .interfaces import DBAPICursor
32
+ from .interfaces import ExceptionContext
33
+ from .interfaces import ExecuteStyle
34
+ from .interfaces import ExecutionContext
35
+ from .interfaces import IsolationLevel
36
+ from .util import _distill_params_20
37
+ from .util import _distill_raw_params
38
+ from .util import TransactionalContext
39
+ from .. import exc
40
+ from .. import inspection
41
+ from .. import log
42
+ from .. import util
43
+ from ..sql import compiler
44
+ from ..sql import util as sql_util
45
+ from ..util.typing import Never
46
+ from ..util.typing import TupleAny
47
+ from ..util.typing import TypeVarTuple
48
+ from ..util.typing import Unpack
49
+
50
+ if typing.TYPE_CHECKING:
51
+ from . import CursorResult
52
+ from . import ScalarResult
53
+ from .interfaces import _AnyExecuteParams
54
+ from .interfaces import _AnyMultiExecuteParams
55
+ from .interfaces import _CoreAnyExecuteParams
56
+ from .interfaces import _CoreMultiExecuteParams
57
+ from .interfaces import _CoreSingleExecuteParams
58
+ from .interfaces import _DBAPIAnyExecuteParams
59
+ from .interfaces import _DBAPISingleExecuteParams
60
+ from .interfaces import _ExecuteOptions
61
+ from .interfaces import CompiledCacheType
62
+ from .interfaces import CoreExecuteOptionsParameter
63
+ from .interfaces import Dialect
64
+ from .interfaces import SchemaTranslateMapType
65
+ from .reflection import Inspector # noqa
66
+ from .url import URL
67
+ from ..event import dispatcher
68
+ from ..log import _EchoFlagType
69
+ from ..pool import _ConnectionFairy
70
+ from ..pool import Pool
71
+ from ..pool import PoolProxiedConnection
72
+ from ..sql import Executable
73
+ from ..sql._typing import _InfoType
74
+ from ..sql.compiler import Compiled
75
+ from ..sql.ddl import ExecutableDDLElement
76
+ from ..sql.ddl import InvokeDDLBase
77
+ from ..sql.functions import FunctionElement
78
+ from ..sql.schema import DefaultGenerator
79
+ from ..sql.schema import HasSchemaAttr
80
+ from ..sql.schema import SchemaVisitable
81
+ from ..sql.selectable import TypedReturnsRows
82
+
83
+
84
+ _T = TypeVar("_T", bound=Any)
85
+ _Ts = TypeVarTuple("_Ts")
86
+ _EMPTY_EXECUTION_OPTS: _ExecuteOptions = util.EMPTY_DICT
87
+ NO_OPTIONS: Mapping[str, Any] = util.EMPTY_DICT
88
+
89
+
90
+ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
91
+ """Provides high-level functionality for a wrapped DB-API connection.
92
+
93
+ The :class:`_engine.Connection` object is procured by calling the
94
+ :meth:`_engine.Engine.connect` method of the :class:`_engine.Engine`
95
+ object, and provides services for execution of SQL statements as well
96
+ as transaction control.
97
+
98
+ The Connection object is **not** thread-safe. While a Connection can be
99
+ shared among threads using properly synchronized access, it is still
100
+ possible that the underlying DBAPI connection may not support shared
101
+ access between threads. Check the DBAPI documentation for details.
102
+
103
+ The Connection object represents a single DBAPI connection checked out
104
+ from the connection pool. In this state, the connection pool has no
105
+ affect upon the connection, including its expiration or timeout state.
106
+ For the connection pool to properly manage connections, connections
107
+ should be returned to the connection pool (i.e. ``connection.close()``)
108
+ whenever the connection is not in use.
109
+
110
+ .. index::
111
+ single: thread safety; Connection
112
+
113
+ """
114
+
115
+ dialect: Dialect
116
+ dispatch: dispatcher[ConnectionEventsTarget]
117
+
118
+ _sqla_logger_namespace = "sqlalchemy.engine.Connection"
119
+
120
+ # used by sqlalchemy.engine.util.TransactionalContext
121
+ _trans_context_manager: Optional[TransactionalContext] = None
122
+
123
+ # legacy as of 2.0, should be eventually deprecated and
124
+ # removed. was used in the "pre_ping" recipe that's been in the docs
125
+ # a long time
126
+ should_close_with_result = False
127
+
128
+ _dbapi_connection: Optional[PoolProxiedConnection]
129
+
130
+ _execution_options: _ExecuteOptions
131
+
132
+ _transaction: Optional[RootTransaction]
133
+ _nested_transaction: Optional[NestedTransaction]
134
+
135
+ def __init__(
136
+ self,
137
+ engine: Engine,
138
+ connection: Optional[PoolProxiedConnection] = None,
139
+ _has_events: Optional[bool] = None,
140
+ _allow_revalidate: bool = True,
141
+ _allow_autobegin: bool = True,
142
+ ):
143
+ """Construct a new Connection."""
144
+ self.engine = engine
145
+ self.dialect = dialect = engine.dialect
146
+
147
+ if connection is None:
148
+ try:
149
+ self._dbapi_connection = engine.raw_connection()
150
+ except dialect.loaded_dbapi.Error as err:
151
+ Connection._handle_dbapi_exception_noconnection(
152
+ err, dialect, engine
153
+ )
154
+ raise
155
+ else:
156
+ self._dbapi_connection = connection
157
+
158
+ self._transaction = self._nested_transaction = None
159
+ self.__savepoint_seq = 0
160
+ self.__in_begin = False
161
+
162
+ self.__can_reconnect = _allow_revalidate
163
+ self._allow_autobegin = _allow_autobegin
164
+ self._echo = self.engine._should_log_info()
165
+
166
+ if _has_events is None:
167
+ # if _has_events is sent explicitly as False,
168
+ # then don't join the dispatch of the engine; we don't
169
+ # want to handle any of the engine's events in that case.
170
+ self.dispatch = self.dispatch._join(engine.dispatch)
171
+ self._has_events = _has_events or (
172
+ _has_events is None and engine._has_events
173
+ )
174
+
175
+ self._execution_options = engine._execution_options
176
+
177
+ if self._has_events or self.engine._has_events:
178
+ self.dispatch.engine_connect(self)
179
+
180
+ # this can be assigned differently via
181
+ # characteristics.LoggingTokenCharacteristic
182
+ _message_formatter: Any = None
183
+
184
+ def _log_info(self, message: str, *arg: Any, **kw: Any) -> None:
185
+ fmt = self._message_formatter
186
+
187
+ if fmt:
188
+ message = fmt(message)
189
+
190
+ if log.STACKLEVEL:
191
+ kw["stacklevel"] = 1 + log.STACKLEVEL_OFFSET
192
+
193
+ self.engine.logger.info(message, *arg, **kw)
194
+
195
+ def _log_debug(self, message: str, *arg: Any, **kw: Any) -> None:
196
+ fmt = self._message_formatter
197
+
198
+ if fmt:
199
+ message = fmt(message)
200
+
201
+ if log.STACKLEVEL:
202
+ kw["stacklevel"] = 1 + log.STACKLEVEL_OFFSET
203
+
204
+ self.engine.logger.debug(message, *arg, **kw)
205
+
206
+ @property
207
+ def _schema_translate_map(self) -> Optional[SchemaTranslateMapType]:
208
+ schema_translate_map: Optional[SchemaTranslateMapType] = (
209
+ self._execution_options.get("schema_translate_map", None)
210
+ )
211
+
212
+ return schema_translate_map
213
+
214
+ def schema_for_object(self, obj: HasSchemaAttr) -> Optional[str]:
215
+ """Return the schema name for the given schema item taking into
216
+ account current schema translate map.
217
+
218
+ """
219
+
220
+ name = obj.schema
221
+ schema_translate_map: Optional[SchemaTranslateMapType] = (
222
+ self._execution_options.get("schema_translate_map", None)
223
+ )
224
+
225
+ if (
226
+ schema_translate_map
227
+ and name in schema_translate_map
228
+ and obj._use_schema_map
229
+ ):
230
+ return schema_translate_map[name]
231
+ else:
232
+ return name
233
+
234
+ def __enter__(self) -> Connection:
235
+ return self
236
+
237
+ def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
238
+ self.close()
239
+
240
+ @overload
241
+ def execution_options(
242
+ self,
243
+ *,
244
+ compiled_cache: Optional[CompiledCacheType] = ...,
245
+ logging_token: str = ...,
246
+ isolation_level: IsolationLevel = ...,
247
+ no_parameters: bool = False,
248
+ stream_results: bool = False,
249
+ max_row_buffer: int = ...,
250
+ yield_per: int = ...,
251
+ insertmanyvalues_page_size: int = ...,
252
+ schema_translate_map: Optional[SchemaTranslateMapType] = ...,
253
+ preserve_rowcount: bool = False,
254
+ driver_column_names: bool = False,
255
+ **opt: Any,
256
+ ) -> Connection: ...
257
+
258
+ @overload
259
+ def execution_options(self, **opt: Any) -> Connection: ...
260
+
261
+ def execution_options(self, **opt: Any) -> Connection:
262
+ r"""Set non-SQL options for the connection which take effect
263
+ during execution.
264
+
265
+ This method modifies this :class:`_engine.Connection` **in-place**;
266
+ the return value is the same :class:`_engine.Connection` object
267
+ upon which the method is called. Note that this is in contrast
268
+ to the behavior of the ``execution_options`` methods on other
269
+ objects such as :meth:`_engine.Engine.execution_options` and
270
+ :meth:`_sql.Executable.execution_options`. The rationale is that many
271
+ such execution options necessarily modify the state of the base
272
+ DBAPI connection in any case so there is no feasible means of
273
+ keeping the effect of such an option localized to a "sub" connection.
274
+
275
+ .. versionchanged:: 2.0 The :meth:`_engine.Connection.execution_options`
276
+ method, in contrast to other objects with this method, modifies
277
+ the connection in-place without creating copy of it.
278
+
279
+ As discussed elsewhere, the :meth:`_engine.Connection.execution_options`
280
+ method accepts any arbitrary parameters including user defined names.
281
+ All parameters given are consumable in a number of ways including
282
+ by using the :meth:`_engine.Connection.get_execution_options` method.
283
+ See the examples at :meth:`_sql.Executable.execution_options`
284
+ and :meth:`_engine.Engine.execution_options`.
285
+
286
+ The keywords that are currently recognized by SQLAlchemy itself
287
+ include all those listed under :meth:`.Executable.execution_options`,
288
+ as well as others that are specific to :class:`_engine.Connection`.
289
+
290
+ :param compiled_cache: Available on: :class:`_engine.Connection`,
291
+ :class:`_engine.Engine`.
292
+
293
+ A dictionary where :class:`.Compiled` objects
294
+ will be cached when the :class:`_engine.Connection`
295
+ compiles a clause
296
+ expression into a :class:`.Compiled` object. This dictionary will
297
+ supersede the statement cache that may be configured on the
298
+ :class:`_engine.Engine` itself. If set to None, caching
299
+ is disabled, even if the engine has a configured cache size.
300
+
301
+ Note that the ORM makes use of its own "compiled" caches for
302
+ some operations, including flush operations. The caching
303
+ used by the ORM internally supersedes a cache dictionary
304
+ specified here.
305
+
306
+ :param logging_token: Available on: :class:`_engine.Connection`,
307
+ :class:`_engine.Engine`, :class:`_sql.Executable`.
308
+
309
+ Adds the specified string token surrounded by brackets in log
310
+ messages logged by the connection, i.e. the logging that's enabled
311
+ either via the :paramref:`_sa.create_engine.echo` flag or via the
312
+ ``logging.getLogger("sqlalchemy.engine")`` logger. This allows a
313
+ per-connection or per-sub-engine token to be available which is
314
+ useful for debugging concurrent connection scenarios.
315
+
316
+ .. versionadded:: 1.4.0b2
317
+
318
+ .. seealso::
319
+
320
+ :ref:`dbengine_logging_tokens` - usage example
321
+
322
+ :paramref:`_sa.create_engine.logging_name` - adds a name to the
323
+ name used by the Python logger object itself.
324
+
325
+ :param isolation_level: Available on: :class:`_engine.Connection`,
326
+ :class:`_engine.Engine`.
327
+
328
+ Set the transaction isolation level for the lifespan of this
329
+ :class:`_engine.Connection` object.
330
+ Valid values include those string
331
+ values accepted by the :paramref:`_sa.create_engine.isolation_level`
332
+ parameter passed to :func:`_sa.create_engine`. These levels are
333
+ semi-database specific; see individual dialect documentation for
334
+ valid levels.
335
+
336
+ The isolation level option applies the isolation level by emitting
337
+ statements on the DBAPI connection, and **necessarily affects the
338
+ original Connection object overall**. The isolation level will remain
339
+ at the given setting until explicitly changed, or when the DBAPI
340
+ connection itself is :term:`released` to the connection pool, i.e. the
341
+ :meth:`_engine.Connection.close` method is called, at which time an
342
+ event handler will emit additional statements on the DBAPI connection
343
+ in order to revert the isolation level change.
344
+
345
+ .. note:: The ``isolation_level`` execution option may only be
346
+ established before the :meth:`_engine.Connection.begin` method is
347
+ called, as well as before any SQL statements are emitted which
348
+ would otherwise trigger "autobegin", or directly after a call to
349
+ :meth:`_engine.Connection.commit` or
350
+ :meth:`_engine.Connection.rollback`. A database cannot change the
351
+ isolation level on a transaction in progress.
352
+
353
+ .. note:: The ``isolation_level`` execution option is implicitly
354
+ reset if the :class:`_engine.Connection` is invalidated, e.g. via
355
+ the :meth:`_engine.Connection.invalidate` method, or if a
356
+ disconnection error occurs. The new connection produced after the
357
+ invalidation will **not** have the selected isolation level
358
+ re-applied to it automatically.
359
+
360
+ .. seealso::
361
+
362
+ :ref:`dbapi_autocommit`
363
+
364
+ :meth:`_engine.Connection.get_isolation_level`
365
+ - view current actual level
366
+
367
+ :param no_parameters: Available on: :class:`_engine.Connection`,
368
+ :class:`_sql.Executable`.
369
+
370
+ When ``True``, if the final parameter
371
+ list or dictionary is totally empty, will invoke the
372
+ statement on the cursor as ``cursor.execute(statement)``,
373
+ not passing the parameter collection at all.
374
+ Some DBAPIs such as psycopg2 and mysql-python consider
375
+ percent signs as significant only when parameters are
376
+ present; this option allows code to generate SQL
377
+ containing percent signs (and possibly other characters)
378
+ that is neutral regarding whether it's executed by the DBAPI
379
+ or piped into a script that's later invoked by
380
+ command line tools.
381
+
382
+ :param stream_results: Available on: :class:`_engine.Connection`,
383
+ :class:`_sql.Executable`.
384
+
385
+ Indicate to the dialect that results should be "streamed" and not
386
+ pre-buffered, if possible. For backends such as PostgreSQL, MySQL
387
+ and MariaDB, this indicates the use of a "server side cursor" as
388
+ opposed to a client side cursor. Other backends such as that of
389
+ Oracle Database may already use server side cursors by default.
390
+
391
+ The usage of
392
+ :paramref:`_engine.Connection.execution_options.stream_results` is
393
+ usually combined with setting a fixed number of rows to to be fetched
394
+ in batches, to allow for efficient iteration of database rows while
395
+ at the same time not loading all result rows into memory at once;
396
+ this can be configured on a :class:`_engine.Result` object using the
397
+ :meth:`_engine.Result.yield_per` method, after execution has
398
+ returned a new :class:`_engine.Result`. If
399
+ :meth:`_engine.Result.yield_per` is not used,
400
+ the :paramref:`_engine.Connection.execution_options.stream_results`
401
+ mode of operation will instead use a dynamically sized buffer
402
+ which buffers sets of rows at a time, growing on each batch
403
+ based on a fixed growth size up until a limit which may
404
+ be configured using the
405
+ :paramref:`_engine.Connection.execution_options.max_row_buffer`
406
+ parameter.
407
+
408
+ When using the ORM to fetch ORM mapped objects from a result,
409
+ :meth:`_engine.Result.yield_per` should always be used with
410
+ :paramref:`_engine.Connection.execution_options.stream_results`,
411
+ so that the ORM does not fetch all rows into new ORM objects at once.
412
+
413
+ For typical use, the
414
+ :paramref:`_engine.Connection.execution_options.yield_per` execution
415
+ option should be preferred, which sets up both
416
+ :paramref:`_engine.Connection.execution_options.stream_results` and
417
+ :meth:`_engine.Result.yield_per` at once. This option is supported
418
+ both at a core level by :class:`_engine.Connection` as well as by the
419
+ ORM :class:`_engine.Session`; the latter is described at
420
+ :ref:`orm_queryguide_yield_per`.
421
+
422
+ .. seealso::
423
+
424
+ :ref:`engine_stream_results` - background on
425
+ :paramref:`_engine.Connection.execution_options.stream_results`
426
+
427
+ :paramref:`_engine.Connection.execution_options.max_row_buffer`
428
+
429
+ :paramref:`_engine.Connection.execution_options.yield_per`
430
+
431
+ :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
432
+ describing the ORM version of ``yield_per``
433
+
434
+ :param max_row_buffer: Available on: :class:`_engine.Connection`,
435
+ :class:`_sql.Executable`. Sets a maximum
436
+ buffer size to use when the
437
+ :paramref:`_engine.Connection.execution_options.stream_results`
438
+ execution option is used on a backend that supports server side
439
+ cursors. The default value if not specified is 1000.
440
+
441
+ .. seealso::
442
+
443
+ :paramref:`_engine.Connection.execution_options.stream_results`
444
+
445
+ :ref:`engine_stream_results`
446
+
447
+
448
+ :param yield_per: Available on: :class:`_engine.Connection`,
449
+ :class:`_sql.Executable`. Integer value applied which will
450
+ set the :paramref:`_engine.Connection.execution_options.stream_results`
451
+ execution option and invoke :meth:`_engine.Result.yield_per`
452
+ automatically at once. Allows equivalent functionality as
453
+ is present when using this parameter with the ORM.
454
+
455
+ .. versionadded:: 1.4.40
456
+
457
+ .. seealso::
458
+
459
+ :ref:`engine_stream_results` - background and examples
460
+ on using server side cursors with Core.
461
+
462
+ :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
463
+ describing the ORM version of ``yield_per``
464
+
465
+ :param insertmanyvalues_page_size: Available on: :class:`_engine.Connection`,
466
+ :class:`_engine.Engine`. Number of rows to format into an
467
+ INSERT statement when the statement uses "insertmanyvalues" mode,
468
+ which is a paged form of bulk insert that is used for many backends
469
+ when using :term:`executemany` execution typically in conjunction
470
+ with RETURNING. Defaults to 1000. May also be modified on a
471
+ per-engine basis using the
472
+ :paramref:`_sa.create_engine.insertmanyvalues_page_size` parameter.
473
+
474
+ .. versionadded:: 2.0
475
+
476
+ .. seealso::
477
+
478
+ :ref:`engine_insertmanyvalues`
479
+
480
+ :param schema_translate_map: Available on: :class:`_engine.Connection`,
481
+ :class:`_engine.Engine`, :class:`_sql.Executable`.
482
+
483
+ A dictionary mapping schema names to schema names, that will be
484
+ applied to the :paramref:`_schema.Table.schema` element of each
485
+ :class:`_schema.Table`
486
+ encountered when SQL or DDL expression elements
487
+ are compiled into strings; the resulting schema name will be
488
+ converted based on presence in the map of the original name.
489
+
490
+ .. seealso::
491
+
492
+ :ref:`schema_translating`
493
+
494
+ :param preserve_rowcount: Boolean; when True, the ``cursor.rowcount``
495
+ attribute will be unconditionally memoized within the result and
496
+ made available via the :attr:`.CursorResult.rowcount` attribute.
497
+ Normally, this attribute is only preserved for UPDATE and DELETE
498
+ statements. Using this option, the DBAPIs rowcount value can
499
+ be accessed for other kinds of statements such as INSERT and SELECT,
500
+ to the degree that the DBAPI supports these statements. See
501
+ :attr:`.CursorResult.rowcount` for notes regarding the behavior
502
+ of this attribute.
503
+
504
+ .. versionadded:: 2.0.28
505
+
506
+ .. seealso::
507
+
508
+ :meth:`_engine.Engine.execution_options`
509
+
510
+ :meth:`.Executable.execution_options`
511
+
512
+ :meth:`_engine.Connection.get_execution_options`
513
+
514
+ :ref:`orm_queryguide_execution_options` - documentation on all
515
+ ORM-specific execution options
516
+
517
+ :param driver_column_names: When True, the returned
518
+ :class:`_engine.CursorResult` will use the column names as written in
519
+ ``cursor.description`` to set up the keys for the result set,
520
+ including the names of columns for the :class:`_engine.Row` object as
521
+ well as the dictionary keys when using :attr:`_engine.Row._mapping`.
522
+ On backends that use "name normalization" such as Oracle Database to
523
+ correct for lower case names being converted to all uppercase, this
524
+ behavior is turned off and the raw UPPERCASE names in
525
+ cursor.description will be present.
526
+
527
+ .. versionadded:: 2.1
528
+
529
+ """ # noqa
530
+ if self._has_events or self.engine._has_events:
531
+ self.dispatch.set_connection_execution_options(self, opt)
532
+ self._execution_options = self._execution_options.union(opt)
533
+ self.dialect.set_connection_execution_options(self, opt)
534
+ return self
535
+
536
+ def get_execution_options(self) -> _ExecuteOptions:
537
+ """Get the non-SQL options which will take effect during execution.
538
+
539
+ .. seealso::
540
+
541
+ :meth:`_engine.Connection.execution_options`
542
+ """
543
+ return self._execution_options
544
+
545
+ @property
546
+ def _still_open_and_dbapi_connection_is_valid(self) -> bool:
547
+ pool_proxied_connection = self._dbapi_connection
548
+ return (
549
+ pool_proxied_connection is not None
550
+ and pool_proxied_connection.is_valid
551
+ )
552
+
553
+ @property
554
+ def closed(self) -> bool:
555
+ """Return True if this connection is closed."""
556
+
557
+ return self._dbapi_connection is None and not self.__can_reconnect
558
+
559
+ @property
560
+ def invalidated(self) -> bool:
561
+ """Return True if this connection was invalidated.
562
+
563
+ This does not indicate whether or not the connection was
564
+ invalidated at the pool level, however
565
+
566
+ """
567
+
568
+ # prior to 1.4, "invalid" was stored as a state independent of
569
+ # "closed", meaning an invalidated connection could be "closed",
570
+ # the _dbapi_connection would be None and closed=True, yet the
571
+ # "invalid" flag would stay True. This meant that there were
572
+ # three separate states (open/valid, closed/valid, closed/invalid)
573
+ # when there is really no reason for that; a connection that's
574
+ # "closed" does not need to be "invalid". So the state is now
575
+ # represented by the two facts alone.
576
+
577
+ pool_proxied_connection = self._dbapi_connection
578
+ return pool_proxied_connection is None and self.__can_reconnect
579
+
580
+ @property
581
+ def connection(self) -> PoolProxiedConnection:
582
+ """The underlying DB-API connection managed by this Connection.
583
+
584
+ This is a SQLAlchemy connection-pool proxied connection
585
+ which then has the attribute
586
+ :attr:`_pool._ConnectionFairy.dbapi_connection` that refers to the
587
+ actual driver connection.
588
+
589
+ .. seealso::
590
+
591
+
592
+ :ref:`dbapi_connections`
593
+
594
+ """
595
+
596
+ if self._dbapi_connection is None:
597
+ try:
598
+ return self._revalidate_connection()
599
+ except (exc.PendingRollbackError, exc.ResourceClosedError):
600
+ raise
601
+ except BaseException as e:
602
+ self._handle_dbapi_exception(e, None, None, None, None)
603
+ else:
604
+ return self._dbapi_connection
605
+
606
+ def get_isolation_level(self) -> IsolationLevel:
607
+ """Return the current **actual** isolation level that's present on
608
+ the database within the scope of this connection.
609
+
610
+ This attribute will perform a live SQL operation against the database
611
+ in order to procure the current isolation level, so the value returned
612
+ is the actual level on the underlying DBAPI connection regardless of
613
+ how this state was set. This will be one of the four actual isolation
614
+ modes ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE READ``,
615
+ ``SERIALIZABLE``. It will **not** include the ``AUTOCOMMIT`` isolation
616
+ level setting. Third party dialects may also feature additional
617
+ isolation level settings.
618
+
619
+ .. note:: This method **will not report** on the ``AUTOCOMMIT``
620
+ isolation level, which is a separate :term:`dbapi` setting that's
621
+ independent of **actual** isolation level. When ``AUTOCOMMIT`` is
622
+ in use, the database connection still has a "traditional" isolation
623
+ mode in effect, that is typically one of the four values
624
+ ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE READ``,
625
+ ``SERIALIZABLE``.
626
+
627
+ Compare to the :attr:`_engine.Connection.default_isolation_level`
628
+ accessor which returns the isolation level that is present on the
629
+ database at initial connection time.
630
+
631
+ .. seealso::
632
+
633
+ :attr:`_engine.Connection.default_isolation_level`
634
+ - view default level
635
+
636
+ :paramref:`_sa.create_engine.isolation_level`
637
+ - set per :class:`_engine.Engine` isolation level
638
+
639
+ :paramref:`.Connection.execution_options.isolation_level`
640
+ - set per :class:`_engine.Connection` isolation level
641
+
642
+ """
643
+ dbapi_connection = self.connection.dbapi_connection
644
+ assert dbapi_connection is not None
645
+ try:
646
+ return self.dialect.get_isolation_level(dbapi_connection)
647
+ except BaseException as e:
648
+ self._handle_dbapi_exception(e, None, None, None, None)
649
+
650
+ @property
651
+ def default_isolation_level(self) -> Optional[IsolationLevel]:
652
+ """The initial-connection time isolation level associated with the
653
+ :class:`_engine.Dialect` in use.
654
+
655
+ This value is independent of the
656
+ :paramref:`.Connection.execution_options.isolation_level` and
657
+ :paramref:`.Engine.execution_options.isolation_level` execution
658
+ options, and is determined by the :class:`_engine.Dialect` when the
659
+ first connection is created, by performing a SQL query against the
660
+ database for the current isolation level before any additional commands
661
+ have been emitted.
662
+
663
+ Calling this accessor does not invoke any new SQL queries.
664
+
665
+ .. seealso::
666
+
667
+ :meth:`_engine.Connection.get_isolation_level`
668
+ - view current actual isolation level
669
+
670
+ :paramref:`_sa.create_engine.isolation_level`
671
+ - set per :class:`_engine.Engine` isolation level
672
+
673
+ :paramref:`.Connection.execution_options.isolation_level`
674
+ - set per :class:`_engine.Connection` isolation level
675
+
676
+ """
677
+ return self.dialect.default_isolation_level
678
+
679
+ def _invalid_transaction(self) -> NoReturn:
680
+ raise exc.PendingRollbackError(
681
+ "Can't reconnect until invalid %stransaction is rolled "
682
+ "back. Please rollback() fully before proceeding"
683
+ % ("savepoint " if self._nested_transaction is not None else ""),
684
+ code="8s2b",
685
+ )
686
+
687
+ def _revalidate_connection(self) -> PoolProxiedConnection:
688
+ if self.__can_reconnect and self.invalidated:
689
+ if self._transaction is not None:
690
+ self._invalid_transaction()
691
+ self._dbapi_connection = self.engine.raw_connection()
692
+ return self._dbapi_connection
693
+ raise exc.ResourceClosedError("This Connection is closed")
694
+
695
+ @property
696
+ def info(self) -> _InfoType:
697
+ """Info dictionary associated with the underlying DBAPI connection
698
+ referred to by this :class:`_engine.Connection`, allowing user-defined
699
+ data to be associated with the connection.
700
+
701
+ The data here will follow along with the DBAPI connection including
702
+ after it is returned to the connection pool and used again
703
+ in subsequent instances of :class:`_engine.Connection`.
704
+
705
+ """
706
+
707
+ return self.connection.info
708
+
709
+ def invalidate(self, exception: Optional[BaseException] = None) -> None:
710
+ """Invalidate the underlying DBAPI connection associated with
711
+ this :class:`_engine.Connection`.
712
+
713
+ An attempt will be made to close the underlying DBAPI connection
714
+ immediately; however if this operation fails, the error is logged
715
+ but not raised. The connection is then discarded whether or not
716
+ close() succeeded.
717
+
718
+ Upon the next use (where "use" typically means using the
719
+ :meth:`_engine.Connection.execute` method or similar),
720
+ this :class:`_engine.Connection` will attempt to
721
+ procure a new DBAPI connection using the services of the
722
+ :class:`_pool.Pool` as a source of connectivity (e.g.
723
+ a "reconnection").
724
+
725
+ If a transaction was in progress (e.g. the
726
+ :meth:`_engine.Connection.begin` method has been called) when
727
+ :meth:`_engine.Connection.invalidate` method is called, at the DBAPI
728
+ level all state associated with this transaction is lost, as
729
+ the DBAPI connection is closed. The :class:`_engine.Connection`
730
+ will not allow a reconnection to proceed until the
731
+ :class:`.Transaction` object is ended, by calling the
732
+ :meth:`.Transaction.rollback` method; until that point, any attempt at
733
+ continuing to use the :class:`_engine.Connection` will raise an
734
+ :class:`~sqlalchemy.exc.InvalidRequestError`.
735
+ This is to prevent applications from accidentally
736
+ continuing an ongoing transactional operations despite the
737
+ fact that the transaction has been lost due to an
738
+ invalidation.
739
+
740
+ The :meth:`_engine.Connection.invalidate` method,
741
+ just like auto-invalidation,
742
+ will at the connection pool level invoke the
743
+ :meth:`_events.PoolEvents.invalidate` event.
744
+
745
+ :param exception: an optional ``Exception`` instance that's the
746
+ reason for the invalidation. is passed along to event handlers
747
+ and logging functions.
748
+
749
+ .. seealso::
750
+
751
+ :ref:`pool_connection_invalidation`
752
+
753
+ """
754
+
755
+ if self.invalidated:
756
+ return
757
+
758
+ if self.closed:
759
+ raise exc.ResourceClosedError("This Connection is closed")
760
+
761
+ if self._still_open_and_dbapi_connection_is_valid:
762
+ pool_proxied_connection = self._dbapi_connection
763
+ assert pool_proxied_connection is not None
764
+ pool_proxied_connection.invalidate(exception)
765
+
766
+ self._dbapi_connection = None
767
+
768
+ def detach(self) -> None:
769
+ """Detach the underlying DB-API connection from its connection pool.
770
+
771
+ E.g.::
772
+
773
+ with engine.connect() as conn:
774
+ conn.detach()
775
+ conn.execute(text("SET search_path TO schema1, schema2"))
776
+
777
+ # work with connection
778
+
779
+ # connection is fully closed (since we used "with:", can
780
+ # also call .close())
781
+
782
+ This :class:`_engine.Connection` instance will remain usable.
783
+ When closed
784
+ (or exited from a context manager context as above),
785
+ the DB-API connection will be literally closed and not
786
+ returned to its originating pool.
787
+
788
+ This method can be used to insulate the rest of an application
789
+ from a modified state on a connection (such as a transaction
790
+ isolation level or similar).
791
+
792
+ """
793
+
794
+ if self.closed:
795
+ raise exc.ResourceClosedError("This Connection is closed")
796
+
797
+ pool_proxied_connection = self._dbapi_connection
798
+ if pool_proxied_connection is None:
799
+ raise exc.InvalidRequestError(
800
+ "Can't detach an invalidated Connection"
801
+ )
802
+ pool_proxied_connection.detach()
803
+
804
+ def _autobegin(self) -> None:
805
+ if self._allow_autobegin and not self.__in_begin:
806
+ self.begin()
807
+
808
+ def begin(self) -> RootTransaction:
809
+ """Begin a transaction prior to autobegin occurring.
810
+
811
+ E.g.::
812
+
813
+ with engine.connect() as conn:
814
+ with conn.begin() as trans:
815
+ conn.execute(table.insert(), {"username": "sandy"})
816
+
817
+ The returned object is an instance of :class:`_engine.RootTransaction`.
818
+ This object represents the "scope" of the transaction,
819
+ which completes when either the :meth:`_engine.Transaction.rollback`
820
+ or :meth:`_engine.Transaction.commit` method is called; the object
821
+ also works as a context manager as illustrated above.
822
+
823
+ The :meth:`_engine.Connection.begin` method begins a
824
+ transaction that normally will be begun in any case when the connection
825
+ is first used to execute a statement. The reason this method might be
826
+ used would be to invoke the :meth:`_events.ConnectionEvents.begin`
827
+ event at a specific time, or to organize code within the scope of a
828
+ connection checkout in terms of context managed blocks, such as::
829
+
830
+ with engine.connect() as conn:
831
+ with conn.begin():
832
+ conn.execute(...)
833
+ conn.execute(...)
834
+
835
+ with conn.begin():
836
+ conn.execute(...)
837
+ conn.execute(...)
838
+
839
+ The above code is not fundamentally any different in its behavior than
840
+ the following code which does not use
841
+ :meth:`_engine.Connection.begin`; the below style is known
842
+ as "commit as you go" style::
843
+
844
+ with engine.connect() as conn:
845
+ conn.execute(...)
846
+ conn.execute(...)
847
+ conn.commit()
848
+
849
+ conn.execute(...)
850
+ conn.execute(...)
851
+ conn.commit()
852
+
853
+ From a database point of view, the :meth:`_engine.Connection.begin`
854
+ method does not emit any SQL or change the state of the underlying
855
+ DBAPI connection in any way; the Python DBAPI does not have any
856
+ concept of explicit transaction begin.
857
+
858
+ .. seealso::
859
+
860
+ :ref:`tutorial_working_with_transactions` - in the
861
+ :ref:`unified_tutorial`
862
+
863
+ :meth:`_engine.Connection.begin_nested` - use a SAVEPOINT
864
+
865
+ :meth:`_engine.Connection.begin_twophase` -
866
+ use a two phase /XID transaction
867
+
868
+ :meth:`_engine.Engine.begin` - context manager available from
869
+ :class:`_engine.Engine`
870
+
871
+ """
872
+ if self._transaction is None:
873
+ self._transaction = RootTransaction(self)
874
+ return self._transaction
875
+ else:
876
+ raise exc.InvalidRequestError(
877
+ "This connection has already initialized a SQLAlchemy "
878
+ "Transaction() object via begin() or autobegin; can't "
879
+ "call begin() here unless rollback() or commit() "
880
+ "is called first."
881
+ )
882
+
883
+ def begin_nested(self) -> NestedTransaction:
884
+ """Begin a nested transaction (i.e. SAVEPOINT) and return a transaction
885
+ handle that controls the scope of the SAVEPOINT.
886
+
887
+ E.g.::
888
+
889
+ with engine.begin() as connection:
890
+ with connection.begin_nested():
891
+ connection.execute(table.insert(), {"username": "sandy"})
892
+
893
+ The returned object is an instance of
894
+ :class:`_engine.NestedTransaction`, which includes transactional
895
+ methods :meth:`_engine.NestedTransaction.commit` and
896
+ :meth:`_engine.NestedTransaction.rollback`; for a nested transaction,
897
+ these methods correspond to the operations "RELEASE SAVEPOINT <name>"
898
+ and "ROLLBACK TO SAVEPOINT <name>". The name of the savepoint is local
899
+ to the :class:`_engine.NestedTransaction` object and is generated
900
+ automatically. Like any other :class:`_engine.Transaction`, the
901
+ :class:`_engine.NestedTransaction` may be used as a context manager as
902
+ illustrated above which will "release" or "rollback" corresponding to
903
+ if the operation within the block were successful or raised an
904
+ exception.
905
+
906
+ Nested transactions require SAVEPOINT support in the underlying
907
+ database, else the behavior is undefined. SAVEPOINT is commonly used to
908
+ run operations within a transaction that may fail, while continuing the
909
+ outer transaction. E.g.::
910
+
911
+ from sqlalchemy import exc
912
+
913
+ with engine.begin() as connection:
914
+ trans = connection.begin_nested()
915
+ try:
916
+ connection.execute(table.insert(), {"username": "sandy"})
917
+ trans.commit()
918
+ except exc.IntegrityError: # catch for duplicate username
919
+ trans.rollback() # rollback to savepoint
920
+
921
+ # outer transaction continues
922
+ connection.execute(...)
923
+
924
+ If :meth:`_engine.Connection.begin_nested` is called without first
925
+ calling :meth:`_engine.Connection.begin` or
926
+ :meth:`_engine.Engine.begin`, the :class:`_engine.Connection` object
927
+ will "autobegin" the outer transaction first. This outer transaction
928
+ may be committed using "commit-as-you-go" style, e.g.::
929
+
930
+ with engine.connect() as connection: # begin() wasn't called
931
+
932
+ with connection.begin_nested(): # will auto-"begin()" first
933
+ connection.execute(...)
934
+ # savepoint is released
935
+
936
+ connection.execute(...)
937
+
938
+ # explicitly commit outer transaction
939
+ connection.commit()
940
+
941
+ # can continue working with connection here
942
+
943
+ .. versionchanged:: 2.0
944
+
945
+ :meth:`_engine.Connection.begin_nested` will now participate
946
+ in the connection "autobegin" behavior that is new as of
947
+ 2.0 / "future" style connections in 1.4.
948
+
949
+ .. seealso::
950
+
951
+ :meth:`_engine.Connection.begin`
952
+
953
+ :ref:`session_begin_nested` - ORM support for SAVEPOINT
954
+
955
+ """
956
+ if self._transaction is None:
957
+ self._autobegin()
958
+
959
+ return NestedTransaction(self)
960
+
961
+ def begin_twophase(self, xid: Optional[Any] = None) -> TwoPhaseTransaction:
962
+ """Begin a two-phase or XA transaction and return a transaction
963
+ handle.
964
+
965
+ The returned object is an instance of :class:`.TwoPhaseTransaction`,
966
+ which in addition to the methods provided by
967
+ :class:`.Transaction`, also provides a
968
+ :meth:`~.TwoPhaseTransaction.prepare` method.
969
+
970
+ :param xid: the two phase transaction id. If not supplied, a
971
+ random id will be generated. The accepted type and value depends on
972
+ the driver in use.
973
+
974
+ .. seealso::
975
+
976
+ :meth:`_engine.Connection.begin`
977
+
978
+ :meth:`_engine.Connection.begin_twophase`
979
+
980
+ """
981
+
982
+ if self._transaction is not None:
983
+ raise exc.InvalidRequestError(
984
+ "Cannot start a two phase transaction when a transaction "
985
+ "is already in progress."
986
+ )
987
+ if xid is None:
988
+ xid = self.engine.dialect.create_xid()
989
+ return TwoPhaseTransaction(self, xid)
990
+
991
+ def commit(self) -> None:
992
+ """Commit the transaction that is currently in progress.
993
+
994
+ This method commits the current transaction if one has been started.
995
+ If no transaction was started, the method has no effect, assuming
996
+ the connection is in a non-invalidated state.
997
+
998
+ A transaction is begun on a :class:`_engine.Connection` automatically
999
+ whenever a statement is first executed, or when the
1000
+ :meth:`_engine.Connection.begin` method is called.
1001
+
1002
+ .. note:: The :meth:`_engine.Connection.commit` method only acts upon
1003
+ the primary database transaction that is linked to the
1004
+ :class:`_engine.Connection` object. It does not operate upon a
1005
+ SAVEPOINT that would have been invoked from the
1006
+ :meth:`_engine.Connection.begin_nested` method; for control of a
1007
+ SAVEPOINT, call :meth:`_engine.NestedTransaction.commit` on the
1008
+ :class:`_engine.NestedTransaction` that is returned by the
1009
+ :meth:`_engine.Connection.begin_nested` method itself.
1010
+
1011
+
1012
+ """
1013
+ if self._transaction:
1014
+ self._transaction.commit()
1015
+
1016
+ def rollback(self) -> None:
1017
+ """Roll back the transaction that is currently in progress.
1018
+
1019
+ This method rolls back the current transaction if one has been started.
1020
+ If no transaction was started, the method has no effect. If a
1021
+ transaction was started and the connection is in an invalidated state,
1022
+ the transaction is cleared using this method.
1023
+
1024
+ A transaction is begun on a :class:`_engine.Connection` automatically
1025
+ whenever a statement is first executed, or when the
1026
+ :meth:`_engine.Connection.begin` method is called.
1027
+
1028
+ .. note:: The :meth:`_engine.Connection.rollback` method only acts
1029
+ upon the primary database transaction that is linked to the
1030
+ :class:`_engine.Connection` object. It does not operate upon a
1031
+ SAVEPOINT that would have been invoked from the
1032
+ :meth:`_engine.Connection.begin_nested` method; for control of a
1033
+ SAVEPOINT, call :meth:`_engine.NestedTransaction.rollback` on the
1034
+ :class:`_engine.NestedTransaction` that is returned by the
1035
+ :meth:`_engine.Connection.begin_nested` method itself.
1036
+
1037
+
1038
+ """
1039
+ if self._transaction:
1040
+ self._transaction.rollback()
1041
+
1042
+ def recover_twophase(self) -> List[Any]:
1043
+ return self.engine.dialect.do_recover_twophase(self)
1044
+
1045
+ def rollback_prepared(self, xid: Any, recover: bool = False) -> None:
1046
+ self.engine.dialect.do_rollback_twophase(self, xid, recover=recover)
1047
+
1048
+ def commit_prepared(self, xid: Any, recover: bool = False) -> None:
1049
+ self.engine.dialect.do_commit_twophase(self, xid, recover=recover)
1050
+
1051
+ def in_transaction(self) -> bool:
1052
+ """Return True if a transaction is in progress."""
1053
+ return self._transaction is not None and self._transaction.is_active
1054
+
1055
+ def in_nested_transaction(self) -> bool:
1056
+ """Return True if a transaction is in progress."""
1057
+ return (
1058
+ self._nested_transaction is not None
1059
+ and self._nested_transaction.is_active
1060
+ )
1061
+
1062
+ def _is_autocommit_isolation(self) -> bool:
1063
+ opt_iso = self._execution_options.get("isolation_level", None)
1064
+ return bool(
1065
+ opt_iso == "AUTOCOMMIT"
1066
+ or (
1067
+ opt_iso is None
1068
+ and self.engine.dialect._on_connect_isolation_level
1069
+ == "AUTOCOMMIT"
1070
+ )
1071
+ )
1072
+
1073
+ def _get_required_transaction(self) -> RootTransaction:
1074
+ trans = self._transaction
1075
+ if trans is None:
1076
+ raise exc.InvalidRequestError("connection is not in a transaction")
1077
+ return trans
1078
+
1079
+ def _get_required_nested_transaction(self) -> NestedTransaction:
1080
+ trans = self._nested_transaction
1081
+ if trans is None:
1082
+ raise exc.InvalidRequestError(
1083
+ "connection is not in a nested transaction"
1084
+ )
1085
+ return trans
1086
+
1087
+ def get_transaction(self) -> Optional[RootTransaction]:
1088
+ """Return the current root transaction in progress, if any.
1089
+
1090
+ .. versionadded:: 1.4
1091
+
1092
+ """
1093
+
1094
+ return self._transaction
1095
+
1096
+ def get_nested_transaction(self) -> Optional[NestedTransaction]:
1097
+ """Return the current nested transaction in progress, if any.
1098
+
1099
+ .. versionadded:: 1.4
1100
+
1101
+ """
1102
+ return self._nested_transaction
1103
+
1104
+ def _begin_impl(self, transaction: RootTransaction) -> None:
1105
+ if self._echo:
1106
+ if self._is_autocommit_isolation():
1107
+ self._log_info(
1108
+ "BEGIN (implicit; DBAPI should not BEGIN due to "
1109
+ "autocommit mode)"
1110
+ )
1111
+ else:
1112
+ self._log_info("BEGIN (implicit)")
1113
+
1114
+ self.__in_begin = True
1115
+
1116
+ if self._has_events or self.engine._has_events:
1117
+ self.dispatch.begin(self)
1118
+
1119
+ try:
1120
+ self.engine.dialect.do_begin(self.connection)
1121
+ except BaseException as e:
1122
+ self._handle_dbapi_exception(e, None, None, None, None)
1123
+ finally:
1124
+ self.__in_begin = False
1125
+
1126
+ def _rollback_impl(self) -> None:
1127
+ if self._has_events or self.engine._has_events:
1128
+ self.dispatch.rollback(self)
1129
+
1130
+ if self._still_open_and_dbapi_connection_is_valid:
1131
+ if self._echo:
1132
+ if self._is_autocommit_isolation():
1133
+ if self.dialect.skip_autocommit_rollback:
1134
+ self._log_info(
1135
+ "ROLLBACK will be skipped by "
1136
+ "skip_autocommit_rollback"
1137
+ )
1138
+ else:
1139
+ self._log_info(
1140
+ "ROLLBACK using DBAPI connection.rollback(); "
1141
+ "set skip_autocommit_rollback to prevent fully"
1142
+ )
1143
+ else:
1144
+ self._log_info("ROLLBACK")
1145
+ try:
1146
+ self.engine.dialect.do_rollback(self.connection)
1147
+ except BaseException as e:
1148
+ self._handle_dbapi_exception(e, None, None, None, None)
1149
+
1150
+ def _commit_impl(self) -> None:
1151
+ if self._has_events or self.engine._has_events:
1152
+ self.dispatch.commit(self)
1153
+
1154
+ if self._echo:
1155
+ if self._is_autocommit_isolation():
1156
+ self._log_info(
1157
+ "COMMIT using DBAPI connection.commit(), "
1158
+ "has no effect due to autocommit mode"
1159
+ )
1160
+ else:
1161
+ self._log_info("COMMIT")
1162
+ try:
1163
+ self.engine.dialect.do_commit(self.connection)
1164
+ except BaseException as e:
1165
+ self._handle_dbapi_exception(e, None, None, None, None)
1166
+
1167
+ def _savepoint_impl(self, name: Optional[str] = None) -> str:
1168
+ if self._has_events or self.engine._has_events:
1169
+ self.dispatch.savepoint(self, name)
1170
+
1171
+ if name is None:
1172
+ self.__savepoint_seq += 1
1173
+ name = "sa_savepoint_%s" % self.__savepoint_seq
1174
+ self.engine.dialect.do_savepoint(self, name)
1175
+ return name
1176
+
1177
+ def _rollback_to_savepoint_impl(self, name: str) -> None:
1178
+ if self._has_events or self.engine._has_events:
1179
+ self.dispatch.rollback_savepoint(self, name, None)
1180
+
1181
+ if self._still_open_and_dbapi_connection_is_valid:
1182
+ self.engine.dialect.do_rollback_to_savepoint(self, name)
1183
+
1184
+ def _release_savepoint_impl(self, name: str) -> None:
1185
+ if self._has_events or self.engine._has_events:
1186
+ self.dispatch.release_savepoint(self, name, None)
1187
+
1188
+ self.engine.dialect.do_release_savepoint(self, name)
1189
+
1190
+ def _begin_twophase_impl(self, transaction: TwoPhaseTransaction) -> None:
1191
+ if self._echo:
1192
+ self._log_info("BEGIN TWOPHASE (implicit)")
1193
+ if self._has_events or self.engine._has_events:
1194
+ self.dispatch.begin_twophase(self, transaction.xid)
1195
+
1196
+ self.__in_begin = True
1197
+ try:
1198
+ self.engine.dialect.do_begin_twophase(self, transaction.xid)
1199
+ except BaseException as e:
1200
+ self._handle_dbapi_exception(e, None, None, None, None)
1201
+ finally:
1202
+ self.__in_begin = False
1203
+
1204
+ def _prepare_twophase_impl(self, xid: Any) -> None:
1205
+ if self._has_events or self.engine._has_events:
1206
+ self.dispatch.prepare_twophase(self, xid)
1207
+
1208
+ assert isinstance(self._transaction, TwoPhaseTransaction)
1209
+ try:
1210
+ self.engine.dialect.do_prepare_twophase(self, xid)
1211
+ except BaseException as e:
1212
+ self._handle_dbapi_exception(e, None, None, None, None)
1213
+
1214
+ def _rollback_twophase_impl(self, xid: Any, is_prepared: bool) -> None:
1215
+ if self._has_events or self.engine._has_events:
1216
+ self.dispatch.rollback_twophase(self, xid, is_prepared)
1217
+
1218
+ if self._still_open_and_dbapi_connection_is_valid:
1219
+ assert isinstance(self._transaction, TwoPhaseTransaction)
1220
+ try:
1221
+ self.engine.dialect.do_rollback_twophase(
1222
+ self, xid, is_prepared
1223
+ )
1224
+ except BaseException as e:
1225
+ self._handle_dbapi_exception(e, None, None, None, None)
1226
+
1227
+ def _commit_twophase_impl(self, xid: Any, is_prepared: bool) -> None:
1228
+ if self._has_events or self.engine._has_events:
1229
+ self.dispatch.commit_twophase(self, xid, is_prepared)
1230
+
1231
+ assert isinstance(self._transaction, TwoPhaseTransaction)
1232
+ try:
1233
+ self.engine.dialect.do_commit_twophase(self, xid, is_prepared)
1234
+ except BaseException as e:
1235
+ self._handle_dbapi_exception(e, None, None, None, None)
1236
+
1237
+ def close(self) -> None:
1238
+ """Close this :class:`_engine.Connection`.
1239
+
1240
+ This results in a release of the underlying database
1241
+ resources, that is, the DBAPI connection referenced
1242
+ internally. The DBAPI connection is typically restored
1243
+ back to the connection-holding :class:`_pool.Pool` referenced
1244
+ by the :class:`_engine.Engine` that produced this
1245
+ :class:`_engine.Connection`. Any transactional state present on
1246
+ the DBAPI connection is also unconditionally released via
1247
+ the DBAPI connection's ``rollback()`` method, regardless
1248
+ of any :class:`.Transaction` object that may be
1249
+ outstanding with regards to this :class:`_engine.Connection`.
1250
+
1251
+ This has the effect of also calling :meth:`_engine.Connection.rollback`
1252
+ if any transaction is in place.
1253
+
1254
+ After :meth:`_engine.Connection.close` is called, the
1255
+ :class:`_engine.Connection` is permanently in a closed state,
1256
+ and will allow no further operations.
1257
+
1258
+ """
1259
+
1260
+ if self._transaction:
1261
+ self._transaction.close()
1262
+ skip_reset = True
1263
+ else:
1264
+ skip_reset = False
1265
+
1266
+ if self._dbapi_connection is not None:
1267
+ conn = self._dbapi_connection
1268
+
1269
+ # as we just closed the transaction, close the connection
1270
+ # pool connection without doing an additional reset
1271
+ if skip_reset:
1272
+ cast("_ConnectionFairy", conn)._close_special(
1273
+ transaction_reset=True
1274
+ )
1275
+ else:
1276
+ conn.close()
1277
+
1278
+ # There is a slight chance that conn.close() may have
1279
+ # triggered an invalidation here in which case
1280
+ # _dbapi_connection would already be None, however usually
1281
+ # it will be non-None here and in a "closed" state.
1282
+ self._dbapi_connection = None
1283
+ self.__can_reconnect = False
1284
+
1285
+ # special case to handle mypy issue:
1286
+ # https://github.com/python/mypy/issues/20651
1287
+ @overload
1288
+ def scalar(
1289
+ self,
1290
+ statement: TypedReturnsRows[Never],
1291
+ parameters: Optional[_CoreSingleExecuteParams] = None,
1292
+ *,
1293
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1294
+ ) -> Optional[Any]: ...
1295
+
1296
+ @overload
1297
+ def scalar(
1298
+ self,
1299
+ statement: TypedReturnsRows[_T],
1300
+ parameters: Optional[_CoreSingleExecuteParams] = None,
1301
+ *,
1302
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1303
+ ) -> Optional[_T]: ...
1304
+
1305
+ @overload
1306
+ def scalar(
1307
+ self,
1308
+ statement: Executable,
1309
+ parameters: Optional[_CoreSingleExecuteParams] = None,
1310
+ *,
1311
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1312
+ ) -> Any: ...
1313
+
1314
+ def scalar(
1315
+ self,
1316
+ statement: Executable,
1317
+ parameters: Optional[_CoreSingleExecuteParams] = None,
1318
+ *,
1319
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1320
+ ) -> Any:
1321
+ r"""Executes a SQL statement construct and returns a scalar object.
1322
+
1323
+ This method is shorthand for invoking the
1324
+ :meth:`_engine.Result.scalar` method after invoking the
1325
+ :meth:`_engine.Connection.execute` method. Parameters are equivalent.
1326
+
1327
+ :return: a scalar Python value representing the first column of the
1328
+ first row returned.
1329
+
1330
+ """
1331
+ distilled_parameters = _distill_params_20(parameters)
1332
+ try:
1333
+ meth = statement._execute_on_scalar
1334
+ except AttributeError as err:
1335
+ raise exc.ObjectNotExecutableError(statement) from err
1336
+ else:
1337
+ return meth(
1338
+ self,
1339
+ distilled_parameters,
1340
+ execution_options or NO_OPTIONS,
1341
+ )
1342
+
1343
+ @overload
1344
+ def scalars(
1345
+ self,
1346
+ statement: TypedReturnsRows[_T],
1347
+ parameters: Optional[_CoreAnyExecuteParams] = None,
1348
+ *,
1349
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1350
+ ) -> ScalarResult[_T]: ...
1351
+
1352
+ @overload
1353
+ def scalars(
1354
+ self,
1355
+ statement: Executable,
1356
+ parameters: Optional[_CoreAnyExecuteParams] = None,
1357
+ *,
1358
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1359
+ ) -> ScalarResult[Any]: ...
1360
+
1361
+ def scalars(
1362
+ self,
1363
+ statement: Executable,
1364
+ parameters: Optional[_CoreAnyExecuteParams] = None,
1365
+ *,
1366
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1367
+ ) -> ScalarResult[Any]:
1368
+ """Executes and returns a scalar result set, which yields scalar values
1369
+ from the first column of each row.
1370
+
1371
+ This method is equivalent to calling :meth:`_engine.Connection.execute`
1372
+ to receive a :class:`_result.Result` object, then invoking the
1373
+ :meth:`_result.Result.scalars` method to produce a
1374
+ :class:`_result.ScalarResult` instance.
1375
+
1376
+ :return: a :class:`_result.ScalarResult`
1377
+
1378
+ .. versionadded:: 1.4.24
1379
+
1380
+ """
1381
+
1382
+ return self.execute(
1383
+ statement, parameters, execution_options=execution_options
1384
+ ).scalars()
1385
+
1386
+ @overload
1387
+ def execute(
1388
+ self,
1389
+ statement: TypedReturnsRows[Unpack[_Ts]],
1390
+ parameters: Optional[_CoreAnyExecuteParams] = None,
1391
+ *,
1392
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1393
+ ) -> CursorResult[Unpack[_Ts]]: ...
1394
+
1395
+ @overload
1396
+ def execute(
1397
+ self,
1398
+ statement: Executable,
1399
+ parameters: Optional[_CoreAnyExecuteParams] = None,
1400
+ *,
1401
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1402
+ ) -> CursorResult[Unpack[TupleAny]]: ...
1403
+
1404
+ def execute(
1405
+ self,
1406
+ statement: Executable,
1407
+ parameters: Optional[_CoreAnyExecuteParams] = None,
1408
+ *,
1409
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1410
+ ) -> CursorResult[Unpack[TupleAny]]:
1411
+ r"""Executes a SQL statement construct and returns a
1412
+ :class:`_engine.CursorResult`.
1413
+
1414
+ :param statement: The statement to be executed. This is always
1415
+ an object that is in both the :class:`_expression.ClauseElement` and
1416
+ :class:`_expression.Executable` hierarchies, including:
1417
+
1418
+ * :class:`_expression.Select`
1419
+ * :class:`_expression.Insert`, :class:`_expression.Update`,
1420
+ :class:`_expression.Delete`
1421
+ * :class:`_expression.TextClause` and
1422
+ :class:`_expression.TextualSelect`
1423
+ * :class:`_schema.DDL` and objects which inherit from
1424
+ :class:`_schema.ExecutableDDLElement`
1425
+
1426
+ :param parameters: parameters which will be bound into the statement.
1427
+ This may be either a dictionary of parameter names to values,
1428
+ or a mutable sequence (e.g. a list) of dictionaries. When a
1429
+ list of dictionaries is passed, the underlying statement execution
1430
+ will make use of the DBAPI ``cursor.executemany()`` method.
1431
+ When a single dictionary is passed, the DBAPI ``cursor.execute()``
1432
+ method will be used.
1433
+
1434
+ :param execution_options: optional dictionary of execution options,
1435
+ which will be associated with the statement execution. This
1436
+ dictionary can provide a subset of the options that are accepted
1437
+ by :meth:`_engine.Connection.execution_options`.
1438
+
1439
+ :return: a :class:`_engine.Result` object.
1440
+
1441
+ """
1442
+ distilled_parameters = _distill_params_20(parameters)
1443
+ try:
1444
+ meth = statement._execute_on_connection
1445
+ except AttributeError as err:
1446
+ raise exc.ObjectNotExecutableError(statement) from err
1447
+ else:
1448
+ return meth(
1449
+ self,
1450
+ distilled_parameters,
1451
+ execution_options or NO_OPTIONS,
1452
+ )
1453
+
1454
+ def _execute_function(
1455
+ self,
1456
+ func: FunctionElement[Any],
1457
+ distilled_parameters: _CoreMultiExecuteParams,
1458
+ execution_options: CoreExecuteOptionsParameter,
1459
+ ) -> CursorResult[Unpack[TupleAny]]:
1460
+ """Execute a sql.FunctionElement object."""
1461
+
1462
+ return self._execute_clauseelement(
1463
+ func.select(), distilled_parameters, execution_options
1464
+ )
1465
+
1466
+ def _execute_default(
1467
+ self,
1468
+ default: DefaultGenerator,
1469
+ distilled_parameters: _CoreMultiExecuteParams,
1470
+ execution_options: CoreExecuteOptionsParameter,
1471
+ ) -> Any:
1472
+ """Execute a schema.ColumnDefault object."""
1473
+
1474
+ exec_opts = self._execution_options.merge_with(execution_options)
1475
+
1476
+ event_multiparams: Optional[_CoreMultiExecuteParams]
1477
+ event_params: Optional[_CoreAnyExecuteParams]
1478
+
1479
+ # note for event handlers, the "distilled parameters" which is always
1480
+ # a list of dicts is broken out into separate "multiparams" and
1481
+ # "params" collections, which allows the handler to distinguish
1482
+ # between an executemany and execute style set of parameters.
1483
+ if self._has_events or self.engine._has_events:
1484
+ (
1485
+ default,
1486
+ distilled_parameters,
1487
+ event_multiparams,
1488
+ event_params,
1489
+ ) = self._invoke_before_exec_event(
1490
+ default, distilled_parameters, exec_opts
1491
+ )
1492
+ else:
1493
+ event_multiparams = event_params = None
1494
+
1495
+ try:
1496
+ conn = self._dbapi_connection
1497
+ if conn is None:
1498
+ conn = self._revalidate_connection()
1499
+
1500
+ dialect = self.dialect
1501
+ ctx = dialect.execution_ctx_cls._init_default(
1502
+ dialect, self, conn, exec_opts
1503
+ )
1504
+ except (exc.PendingRollbackError, exc.ResourceClosedError):
1505
+ raise
1506
+ except BaseException as e:
1507
+ self._handle_dbapi_exception(e, None, None, None, None)
1508
+
1509
+ ret = ctx._exec_default(None, default, None)
1510
+
1511
+ if self._has_events or self.engine._has_events:
1512
+ self.dispatch.after_execute(
1513
+ self,
1514
+ default,
1515
+ event_multiparams,
1516
+ event_params,
1517
+ exec_opts,
1518
+ ret,
1519
+ )
1520
+
1521
+ return ret
1522
+
1523
+ def _execute_ddl(
1524
+ self,
1525
+ ddl: ExecutableDDLElement,
1526
+ distilled_parameters: _CoreMultiExecuteParams,
1527
+ execution_options: CoreExecuteOptionsParameter,
1528
+ ) -> CursorResult[Unpack[TupleAny]]:
1529
+ """Execute a schema.DDL object."""
1530
+
1531
+ exec_opts = ddl._execution_options.merge_with(
1532
+ self._execution_options, execution_options
1533
+ )
1534
+
1535
+ event_multiparams: Optional[_CoreMultiExecuteParams]
1536
+ event_params: Optional[_CoreSingleExecuteParams]
1537
+
1538
+ if self._has_events or self.engine._has_events:
1539
+ (
1540
+ ddl,
1541
+ distilled_parameters,
1542
+ event_multiparams,
1543
+ event_params,
1544
+ ) = self._invoke_before_exec_event(
1545
+ ddl, distilled_parameters, exec_opts
1546
+ )
1547
+ else:
1548
+ event_multiparams = event_params = None
1549
+
1550
+ schema_translate_map = exec_opts.get("schema_translate_map", None)
1551
+
1552
+ dialect = self.dialect
1553
+
1554
+ compiled = ddl.compile(
1555
+ dialect=dialect, schema_translate_map=schema_translate_map
1556
+ )
1557
+ ret = self._execute_context(
1558
+ dialect,
1559
+ dialect.execution_ctx_cls._init_ddl,
1560
+ compiled,
1561
+ None,
1562
+ exec_opts,
1563
+ compiled,
1564
+ )
1565
+ if self._has_events or self.engine._has_events:
1566
+ self.dispatch.after_execute(
1567
+ self,
1568
+ ddl,
1569
+ event_multiparams,
1570
+ event_params,
1571
+ exec_opts,
1572
+ ret,
1573
+ )
1574
+ return ret
1575
+
1576
+ def _invoke_before_exec_event(
1577
+ self,
1578
+ elem: Any,
1579
+ distilled_params: _CoreMultiExecuteParams,
1580
+ execution_options: _ExecuteOptions,
1581
+ ) -> Tuple[
1582
+ Any,
1583
+ _CoreMultiExecuteParams,
1584
+ _CoreMultiExecuteParams,
1585
+ _CoreSingleExecuteParams,
1586
+ ]:
1587
+ event_multiparams: _CoreMultiExecuteParams
1588
+ event_params: _CoreSingleExecuteParams
1589
+
1590
+ if len(distilled_params) == 1:
1591
+ event_multiparams, event_params = [], distilled_params[0]
1592
+ else:
1593
+ event_multiparams, event_params = distilled_params, {}
1594
+
1595
+ for fn in self.dispatch.before_execute:
1596
+ elem, event_multiparams, event_params = fn(
1597
+ self,
1598
+ elem,
1599
+ event_multiparams,
1600
+ event_params,
1601
+ execution_options,
1602
+ )
1603
+
1604
+ if event_multiparams:
1605
+ distilled_params = list(event_multiparams)
1606
+ if event_params:
1607
+ raise exc.InvalidRequestError(
1608
+ "Event handler can't return non-empty multiparams "
1609
+ "and params at the same time"
1610
+ )
1611
+ elif event_params:
1612
+ distilled_params = [event_params]
1613
+ else:
1614
+ distilled_params = []
1615
+
1616
+ return elem, distilled_params, event_multiparams, event_params
1617
+
1618
+ def _execute_clauseelement(
1619
+ self,
1620
+ elem: Executable,
1621
+ distilled_parameters: _CoreMultiExecuteParams,
1622
+ execution_options: CoreExecuteOptionsParameter,
1623
+ ) -> CursorResult[Unpack[TupleAny]]:
1624
+ """Execute a sql.ClauseElement object."""
1625
+
1626
+ exec_opts = elem._execution_options.merge_with(
1627
+ self._execution_options, execution_options
1628
+ )
1629
+
1630
+ has_events = self._has_events or self.engine._has_events
1631
+ if has_events:
1632
+ (
1633
+ elem,
1634
+ distilled_parameters,
1635
+ event_multiparams,
1636
+ event_params,
1637
+ ) = self._invoke_before_exec_event(
1638
+ elem, distilled_parameters, exec_opts
1639
+ )
1640
+
1641
+ if distilled_parameters:
1642
+ # ensure we don't retain a link to the view object for keys()
1643
+ # which links to the values, which we don't want to cache
1644
+ keys = sorted(distilled_parameters[0])
1645
+ for_executemany = len(distilled_parameters) > 1
1646
+ else:
1647
+ keys = []
1648
+ for_executemany = False
1649
+
1650
+ dialect = self.dialect
1651
+
1652
+ schema_translate_map = exec_opts.get("schema_translate_map", None)
1653
+
1654
+ compiled_cache: Optional[CompiledCacheType] = exec_opts.get(
1655
+ "compiled_cache", self.engine._compiled_cache
1656
+ )
1657
+
1658
+ compiled_sql, extracted_params, param_dict, cache_hit = (
1659
+ elem._compile_w_cache(
1660
+ dialect=dialect,
1661
+ compiled_cache=compiled_cache,
1662
+ column_keys=keys,
1663
+ for_executemany=for_executemany,
1664
+ schema_translate_map=schema_translate_map,
1665
+ linting=self.dialect.compiler_linting | compiler.WARN_LINTING,
1666
+ )
1667
+ )
1668
+ ret = self._execute_context(
1669
+ dialect,
1670
+ dialect.execution_ctx_cls._init_compiled,
1671
+ compiled_sql,
1672
+ distilled_parameters,
1673
+ exec_opts,
1674
+ compiled_sql,
1675
+ distilled_parameters,
1676
+ elem,
1677
+ extracted_params,
1678
+ cache_hit=cache_hit,
1679
+ param_dict=param_dict,
1680
+ )
1681
+ if has_events:
1682
+ self.dispatch.after_execute(
1683
+ self,
1684
+ elem,
1685
+ event_multiparams,
1686
+ event_params,
1687
+ exec_opts,
1688
+ ret,
1689
+ )
1690
+ return ret
1691
+
1692
+ def exec_driver_sql(
1693
+ self,
1694
+ statement: str,
1695
+ parameters: Optional[_DBAPIAnyExecuteParams] = None,
1696
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
1697
+ ) -> CursorResult[Unpack[TupleAny]]:
1698
+ r"""Executes a string SQL statement on the DBAPI cursor directly,
1699
+ without any SQL compilation steps.
1700
+
1701
+ This can be used to pass any string directly to the
1702
+ ``cursor.execute()`` method of the DBAPI in use.
1703
+
1704
+ :param statement: The statement str to be executed. Bound parameters
1705
+ must use the underlying DBAPI's paramstyle, such as "qmark",
1706
+ "pyformat", "format", etc.
1707
+
1708
+ :param parameters: represent bound parameter values to be used in the
1709
+ execution. The format is one of: a dictionary of named parameters,
1710
+ a tuple of positional parameters, or a list containing either
1711
+ dictionaries or tuples for multiple-execute support.
1712
+
1713
+ :return: a :class:`_engine.CursorResult`.
1714
+
1715
+ E.g. multiple dictionaries::
1716
+
1717
+
1718
+ conn.exec_driver_sql(
1719
+ "INSERT INTO table (id, value) VALUES (%(id)s, %(value)s)",
1720
+ [{"id": 1, "value": "v1"}, {"id": 2, "value": "v2"}],
1721
+ )
1722
+
1723
+ Single dictionary::
1724
+
1725
+ conn.exec_driver_sql(
1726
+ "INSERT INTO table (id, value) VALUES (%(id)s, %(value)s)",
1727
+ dict(id=1, value="v1"),
1728
+ )
1729
+
1730
+ Single tuple::
1731
+
1732
+ conn.exec_driver_sql(
1733
+ "INSERT INTO table (id, value) VALUES (?, ?)", (1, "v1")
1734
+ )
1735
+
1736
+ .. note:: The :meth:`_engine.Connection.exec_driver_sql` method does
1737
+ not participate in the
1738
+ :meth:`_events.ConnectionEvents.before_execute` and
1739
+ :meth:`_events.ConnectionEvents.after_execute` events. To
1740
+ intercept calls to :meth:`_engine.Connection.exec_driver_sql`, use
1741
+ :meth:`_events.ConnectionEvents.before_cursor_execute` and
1742
+ :meth:`_events.ConnectionEvents.after_cursor_execute`.
1743
+
1744
+ .. seealso::
1745
+
1746
+ :pep:`249`
1747
+
1748
+ """
1749
+
1750
+ distilled_parameters = _distill_raw_params(parameters)
1751
+
1752
+ exec_opts = self._execution_options.merge_with(execution_options)
1753
+
1754
+ dialect = self.dialect
1755
+ ret = self._execute_context(
1756
+ dialect,
1757
+ dialect.execution_ctx_cls._init_statement,
1758
+ statement,
1759
+ None,
1760
+ exec_opts,
1761
+ statement,
1762
+ distilled_parameters,
1763
+ )
1764
+
1765
+ return ret
1766
+
1767
+ def _execute_context(
1768
+ self,
1769
+ dialect: Dialect,
1770
+ constructor: Callable[..., ExecutionContext],
1771
+ statement: Union[str, Compiled],
1772
+ parameters: Optional[_AnyMultiExecuteParams],
1773
+ execution_options: _ExecuteOptions,
1774
+ *args: Any,
1775
+ **kw: Any,
1776
+ ) -> CursorResult[Unpack[TupleAny]]:
1777
+ """Create an :class:`.ExecutionContext` and execute, returning
1778
+ a :class:`_engine.CursorResult`."""
1779
+
1780
+ if execution_options:
1781
+ yp = execution_options.get("yield_per", None)
1782
+ if yp:
1783
+ execution_options = execution_options.union(
1784
+ {"stream_results": True, "max_row_buffer": yp}
1785
+ )
1786
+ try:
1787
+ conn = self._dbapi_connection
1788
+ if conn is None:
1789
+ conn = self._revalidate_connection()
1790
+
1791
+ context = constructor(
1792
+ dialect, self, conn, execution_options, *args, **kw
1793
+ )
1794
+ except (exc.PendingRollbackError, exc.ResourceClosedError):
1795
+ raise
1796
+ except BaseException as e:
1797
+ self._handle_dbapi_exception(
1798
+ e, str(statement), parameters, None, None
1799
+ )
1800
+
1801
+ if (
1802
+ self._transaction
1803
+ and not self._transaction.is_active
1804
+ or (
1805
+ self._nested_transaction
1806
+ and not self._nested_transaction.is_active
1807
+ )
1808
+ ):
1809
+ self._invalid_transaction()
1810
+
1811
+ elif self._trans_context_manager:
1812
+ TransactionalContext._trans_ctx_check(self)
1813
+
1814
+ if self._transaction is None:
1815
+ self._autobegin()
1816
+
1817
+ context.pre_exec()
1818
+
1819
+ if context.execute_style is ExecuteStyle.INSERTMANYVALUES:
1820
+ return self._exec_insertmany_context(dialect, context)
1821
+ else:
1822
+ return self._exec_single_context(
1823
+ dialect, context, statement, parameters
1824
+ )
1825
+
1826
+ def _exec_single_context(
1827
+ self,
1828
+ dialect: Dialect,
1829
+ context: ExecutionContext,
1830
+ statement: Union[str, Compiled],
1831
+ parameters: Optional[_AnyMultiExecuteParams],
1832
+ ) -> CursorResult[Unpack[TupleAny]]:
1833
+ """continue the _execute_context() method for a single DBAPI
1834
+ cursor.execute() or cursor.executemany() call.
1835
+
1836
+ """
1837
+ if dialect.bind_typing is BindTyping.SETINPUTSIZES:
1838
+ generic_setinputsizes = context._prepare_set_input_sizes()
1839
+
1840
+ if generic_setinputsizes:
1841
+ try:
1842
+ dialect.do_set_input_sizes(
1843
+ context.cursor, generic_setinputsizes, context
1844
+ )
1845
+ except BaseException as e:
1846
+ self._handle_dbapi_exception(
1847
+ e, str(statement), parameters, None, context
1848
+ )
1849
+
1850
+ cursor, str_statement, parameters = (
1851
+ context.cursor,
1852
+ context.statement,
1853
+ context.parameters,
1854
+ )
1855
+
1856
+ effective_parameters: Optional[_AnyExecuteParams]
1857
+
1858
+ if not context.executemany:
1859
+ effective_parameters = parameters[0]
1860
+ else:
1861
+ effective_parameters = parameters
1862
+
1863
+ evt_handled: bool = False
1864
+ try:
1865
+ if self._has_events or self.engine._has_events:
1866
+ for fn in self.dispatch.before_cursor_execute:
1867
+ str_statement, effective_parameters = fn(
1868
+ self,
1869
+ cursor,
1870
+ str_statement,
1871
+ effective_parameters,
1872
+ context,
1873
+ context.executemany,
1874
+ )
1875
+
1876
+ if self._echo:
1877
+ self._log_info(str_statement)
1878
+
1879
+ stats = context._get_cache_stats()
1880
+
1881
+ if not self.engine.hide_parameters:
1882
+ self._log_info(
1883
+ "[%s] %r",
1884
+ stats,
1885
+ sql_util._repr_params(
1886
+ effective_parameters,
1887
+ batches=10,
1888
+ ismulti=context.executemany,
1889
+ ),
1890
+ )
1891
+ else:
1892
+ self._log_info(
1893
+ "[%s] [SQL parameters hidden due to "
1894
+ "hide_parameters=True]",
1895
+ stats,
1896
+ )
1897
+ if context.execute_style is ExecuteStyle.EXECUTEMANY:
1898
+ effective_parameters = cast(
1899
+ "_CoreMultiExecuteParams", effective_parameters
1900
+ )
1901
+ if self.dialect._has_events:
1902
+ for fn in self.dialect.dispatch.do_executemany:
1903
+ if fn(
1904
+ cursor,
1905
+ str_statement,
1906
+ effective_parameters,
1907
+ context,
1908
+ ):
1909
+ evt_handled = True
1910
+ break
1911
+ if not evt_handled:
1912
+ self.dialect.do_executemany(
1913
+ cursor,
1914
+ str_statement,
1915
+ effective_parameters,
1916
+ context,
1917
+ )
1918
+ elif not effective_parameters and context.no_parameters:
1919
+ if self.dialect._has_events:
1920
+ for fn in self.dialect.dispatch.do_execute_no_params:
1921
+ if fn(cursor, str_statement, context):
1922
+ evt_handled = True
1923
+ break
1924
+ if not evt_handled:
1925
+ self.dialect.do_execute_no_params(
1926
+ cursor, str_statement, context
1927
+ )
1928
+ else:
1929
+ effective_parameters = cast(
1930
+ "_CoreSingleExecuteParams", effective_parameters
1931
+ )
1932
+ if self.dialect._has_events:
1933
+ for fn in self.dialect.dispatch.do_execute:
1934
+ if fn(
1935
+ cursor,
1936
+ str_statement,
1937
+ effective_parameters,
1938
+ context,
1939
+ ):
1940
+ evt_handled = True
1941
+ break
1942
+ if not evt_handled:
1943
+ self.dialect.do_execute(
1944
+ cursor, str_statement, effective_parameters, context
1945
+ )
1946
+
1947
+ if self._has_events or self.engine._has_events:
1948
+ self.dispatch.after_cursor_execute(
1949
+ self,
1950
+ cursor,
1951
+ str_statement,
1952
+ effective_parameters,
1953
+ context,
1954
+ context.executemany,
1955
+ )
1956
+
1957
+ context.post_exec()
1958
+
1959
+ result = context._setup_result_proxy()
1960
+
1961
+ except BaseException as e:
1962
+ self._handle_dbapi_exception(
1963
+ e, str_statement, effective_parameters, cursor, context
1964
+ )
1965
+
1966
+ return result
1967
+
1968
+ def _exec_insertmany_context(
1969
+ self,
1970
+ dialect: Dialect,
1971
+ context: ExecutionContext,
1972
+ ) -> CursorResult[Unpack[TupleAny]]:
1973
+ """continue the _execute_context() method for an "insertmanyvalues"
1974
+ operation, which will invoke DBAPI
1975
+ cursor.execute() one or more times with individual log and
1976
+ event hook calls.
1977
+
1978
+ """
1979
+
1980
+ if dialect.bind_typing is BindTyping.SETINPUTSIZES:
1981
+ generic_setinputsizes = context._prepare_set_input_sizes()
1982
+ else:
1983
+ generic_setinputsizes = None
1984
+
1985
+ cursor, str_statement, parameters = (
1986
+ context.cursor,
1987
+ context.statement,
1988
+ context.parameters,
1989
+ )
1990
+
1991
+ effective_parameters = parameters
1992
+
1993
+ engine_events = self._has_events or self.engine._has_events
1994
+ if self.dialect._has_events:
1995
+ do_execute_dispatch: Iterable[Any] = (
1996
+ self.dialect.dispatch.do_execute
1997
+ )
1998
+ else:
1999
+ do_execute_dispatch = ()
2000
+
2001
+ if self._echo:
2002
+ stats = context._get_cache_stats() + " (insertmanyvalues)"
2003
+
2004
+ preserve_rowcount = context.execution_options.get(
2005
+ "preserve_rowcount", False
2006
+ )
2007
+ rowcount = 0
2008
+
2009
+ for imv_batch in dialect._deliver_insertmanyvalues_batches(
2010
+ self,
2011
+ cursor,
2012
+ str_statement,
2013
+ effective_parameters,
2014
+ generic_setinputsizes,
2015
+ context,
2016
+ ):
2017
+ if imv_batch.processed_setinputsizes:
2018
+ try:
2019
+ dialect.do_set_input_sizes(
2020
+ context.cursor,
2021
+ imv_batch.processed_setinputsizes,
2022
+ context,
2023
+ )
2024
+ except BaseException as e:
2025
+ self._handle_dbapi_exception(
2026
+ e,
2027
+ sql_util._long_statement(imv_batch.replaced_statement),
2028
+ imv_batch.replaced_parameters,
2029
+ None,
2030
+ context,
2031
+ is_sub_exec=True,
2032
+ )
2033
+
2034
+ sub_stmt = imv_batch.replaced_statement
2035
+ sub_params = imv_batch.replaced_parameters
2036
+
2037
+ try:
2038
+ if engine_events:
2039
+ for fn in self.dispatch.before_cursor_execute:
2040
+ sub_stmt, sub_params = fn(
2041
+ self,
2042
+ cursor,
2043
+ sub_stmt,
2044
+ sub_params,
2045
+ context,
2046
+ True,
2047
+ )
2048
+
2049
+ if self._echo:
2050
+ self._log_info(sql_util._long_statement(sub_stmt))
2051
+
2052
+ imv_stats = f""" {imv_batch.batchnum}/{
2053
+ imv_batch.total_batches
2054
+ } ({
2055
+ 'ordered'
2056
+ if imv_batch.rows_sorted else 'unordered'
2057
+ }{
2058
+ '; batch not supported'
2059
+ if imv_batch.is_downgraded
2060
+ else ''
2061
+ })"""
2062
+
2063
+ if imv_batch.batchnum == 1:
2064
+ stats += imv_stats
2065
+ else:
2066
+ stats = f"insertmanyvalues{imv_stats}"
2067
+
2068
+ if not self.engine.hide_parameters:
2069
+ self._log_info(
2070
+ "[%s] %r",
2071
+ stats,
2072
+ sql_util._repr_params(
2073
+ sub_params,
2074
+ batches=10,
2075
+ ismulti=False,
2076
+ ),
2077
+ )
2078
+ else:
2079
+ self._log_info(
2080
+ "[%s] [SQL parameters hidden due to "
2081
+ "hide_parameters=True]",
2082
+ stats,
2083
+ )
2084
+
2085
+ for fn in do_execute_dispatch:
2086
+ if fn(
2087
+ cursor,
2088
+ sub_stmt,
2089
+ sub_params,
2090
+ context,
2091
+ ):
2092
+ break
2093
+ else:
2094
+ dialect.do_execute(
2095
+ cursor,
2096
+ sub_stmt,
2097
+ sub_params,
2098
+ context,
2099
+ )
2100
+
2101
+ if engine_events:
2102
+ self.dispatch.after_cursor_execute(
2103
+ self,
2104
+ cursor,
2105
+ sub_stmt,
2106
+ sub_params,
2107
+ context,
2108
+ context.executemany,
2109
+ )
2110
+ except BaseException as e:
2111
+ self._handle_dbapi_exception(
2112
+ e,
2113
+ sql_util._long_statement(sub_stmt),
2114
+ sub_params,
2115
+ cursor,
2116
+ context,
2117
+ is_sub_exec=True,
2118
+ )
2119
+
2120
+ if preserve_rowcount:
2121
+ rowcount += imv_batch.current_batch_size
2122
+
2123
+ try:
2124
+ context.post_exec()
2125
+
2126
+ if preserve_rowcount:
2127
+ context._rowcount = rowcount # type: ignore[attr-defined]
2128
+
2129
+ result = context._setup_result_proxy()
2130
+
2131
+ except BaseException as e:
2132
+ self._handle_dbapi_exception(
2133
+ e, str_statement, effective_parameters, cursor, context
2134
+ )
2135
+
2136
+ return result
2137
+
2138
+ def _cursor_execute(
2139
+ self,
2140
+ cursor: DBAPICursor,
2141
+ statement: str,
2142
+ parameters: _DBAPISingleExecuteParams,
2143
+ context: Optional[ExecutionContext] = None,
2144
+ ) -> None:
2145
+ """Execute a statement + params on the given cursor.
2146
+
2147
+ Adds appropriate logging and exception handling.
2148
+
2149
+ This method is used by DefaultDialect for special-case
2150
+ executions, such as for sequences and column defaults.
2151
+ The path of statement execution in the majority of cases
2152
+ terminates at _execute_context().
2153
+
2154
+ """
2155
+ try:
2156
+ if self._has_events or self.engine._has_events:
2157
+ for fn in self.dispatch.before_cursor_execute:
2158
+ statement, parameters = fn(
2159
+ self, cursor, statement, parameters, context, False
2160
+ )
2161
+
2162
+ if self._echo:
2163
+ self._log_info(statement)
2164
+ self._log_info("[raw sql] %r", parameters)
2165
+
2166
+ for fn in (
2167
+ ()
2168
+ if not self.dialect._has_events
2169
+ else self.dialect.dispatch.do_execute
2170
+ ):
2171
+ if fn(cursor, statement, parameters, context):
2172
+ break
2173
+ else:
2174
+ self.dialect.do_execute(cursor, statement, parameters, context)
2175
+
2176
+ if self._has_events or self.engine._has_events:
2177
+ self.dispatch.after_cursor_execute(
2178
+ self, cursor, statement, parameters, context, False
2179
+ )
2180
+ except BaseException as e:
2181
+ self._handle_dbapi_exception(
2182
+ e, statement, parameters, cursor, context
2183
+ )
2184
+
2185
+ def _safe_close_cursor(self, cursor: DBAPICursor) -> None:
2186
+ """Close the given cursor, catching exceptions
2187
+ and turning into log warnings.
2188
+
2189
+ """
2190
+ try:
2191
+ cursor.close()
2192
+ except Exception:
2193
+ # log the error through the connection pool's logger.
2194
+ self.engine.pool.logger.error(
2195
+ "Error closing cursor", exc_info=True
2196
+ )
2197
+
2198
+ _reentrant_error = False
2199
+ _is_disconnect = False
2200
+
2201
+ def _handle_dbapi_exception(
2202
+ self,
2203
+ e: BaseException,
2204
+ statement: Optional[str],
2205
+ parameters: Optional[_AnyExecuteParams],
2206
+ cursor: Optional[DBAPICursor],
2207
+ context: Optional[ExecutionContext],
2208
+ is_sub_exec: bool = False,
2209
+ ) -> NoReturn:
2210
+ exc_info = sys.exc_info()
2211
+
2212
+ is_exit_exception = util.is_exit_exception(e)
2213
+
2214
+ if not self._is_disconnect:
2215
+ self._is_disconnect = (
2216
+ isinstance(e, self.dialect.loaded_dbapi.Error)
2217
+ and not self.closed
2218
+ and self.dialect.is_disconnect(
2219
+ e,
2220
+ self._dbapi_connection if not self.invalidated else None,
2221
+ cursor,
2222
+ )
2223
+ ) or (is_exit_exception and not self.closed)
2224
+
2225
+ invalidate_pool_on_disconnect = not is_exit_exception
2226
+
2227
+ ismulti: bool = (
2228
+ not is_sub_exec and context.executemany
2229
+ if context is not None
2230
+ else False
2231
+ )
2232
+ if self._reentrant_error:
2233
+ raise exc.DBAPIError.instance(
2234
+ statement,
2235
+ parameters,
2236
+ e,
2237
+ self.dialect.loaded_dbapi.Error,
2238
+ hide_parameters=self.engine.hide_parameters,
2239
+ dialect=self.dialect,
2240
+ ismulti=ismulti,
2241
+ ).with_traceback(exc_info[2]) from e
2242
+ self._reentrant_error = True
2243
+ try:
2244
+ # non-DBAPI error - if we already got a context,
2245
+ # or there's no string statement, don't wrap it
2246
+ should_wrap = isinstance(e, self.dialect.loaded_dbapi.Error) or (
2247
+ not isinstance(e, exc.StatementError)
2248
+ and statement is not None
2249
+ and context is None
2250
+ and not is_exit_exception
2251
+ )
2252
+
2253
+ if should_wrap:
2254
+ sqlalchemy_exception = exc.DBAPIError.instance(
2255
+ statement,
2256
+ parameters,
2257
+ cast(Exception, e),
2258
+ self.dialect.loaded_dbapi.Error,
2259
+ hide_parameters=self.engine.hide_parameters,
2260
+ connection_invalidated=self._is_disconnect,
2261
+ dialect=self.dialect,
2262
+ ismulti=ismulti,
2263
+ )
2264
+ else:
2265
+ sqlalchemy_exception = None
2266
+
2267
+ newraise = None
2268
+
2269
+ if (self.dialect._has_events) and not self._execution_options.get(
2270
+ "skip_user_error_events", False
2271
+ ):
2272
+ ctx = ExceptionContextImpl(
2273
+ e,
2274
+ sqlalchemy_exception,
2275
+ self.engine,
2276
+ self.dialect,
2277
+ self,
2278
+ cursor,
2279
+ statement,
2280
+ parameters,
2281
+ context,
2282
+ self._is_disconnect,
2283
+ invalidate_pool_on_disconnect,
2284
+ False,
2285
+ )
2286
+
2287
+ for fn in self.dialect.dispatch.handle_error:
2288
+ try:
2289
+ # handler returns an exception;
2290
+ # call next handler in a chain
2291
+ per_fn = fn(ctx)
2292
+ if per_fn is not None:
2293
+ ctx.chained_exception = newraise = per_fn
2294
+ except Exception as _raised:
2295
+ # handler raises an exception - stop processing
2296
+ newraise = _raised
2297
+ break
2298
+
2299
+ if self._is_disconnect != ctx.is_disconnect:
2300
+ self._is_disconnect = ctx.is_disconnect
2301
+ if sqlalchemy_exception:
2302
+ sqlalchemy_exception.connection_invalidated = (
2303
+ ctx.is_disconnect
2304
+ )
2305
+
2306
+ # set up potentially user-defined value for
2307
+ # invalidate pool.
2308
+ invalidate_pool_on_disconnect = (
2309
+ ctx.invalidate_pool_on_disconnect
2310
+ )
2311
+
2312
+ if should_wrap and context:
2313
+ context.handle_dbapi_exception(e)
2314
+
2315
+ if not self._is_disconnect:
2316
+ if cursor:
2317
+ self._safe_close_cursor(cursor)
2318
+ # "autorollback" was mostly relevant in 1.x series.
2319
+ # It's very unlikely to reach here, as the connection
2320
+ # does autobegin so when we are here, we are usually
2321
+ # in an explicit / semi-explicit transaction.
2322
+ # however we have a test which manufactures this
2323
+ # scenario in any case using an event handler.
2324
+ # test/engine/test_execute.py-> test_actual_autorollback
2325
+ if not self.in_transaction():
2326
+ self._rollback_impl()
2327
+
2328
+ if newraise:
2329
+ raise newraise.with_traceback(exc_info[2]) from e
2330
+ elif should_wrap:
2331
+ assert sqlalchemy_exception is not None
2332
+ raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
2333
+ else:
2334
+ assert exc_info[1] is not None
2335
+ raise exc_info[1].with_traceback(exc_info[2])
2336
+ finally:
2337
+ del self._reentrant_error
2338
+ if self._is_disconnect:
2339
+ del self._is_disconnect
2340
+ if not self.invalidated:
2341
+ dbapi_conn_wrapper = self._dbapi_connection
2342
+ assert dbapi_conn_wrapper is not None
2343
+ if invalidate_pool_on_disconnect:
2344
+ self.engine.pool._invalidate(dbapi_conn_wrapper, e)
2345
+ self.invalidate(e)
2346
+
2347
+ @classmethod
2348
+ def _handle_dbapi_exception_noconnection(
2349
+ cls,
2350
+ e: BaseException,
2351
+ dialect: Dialect,
2352
+ engine: Optional[Engine] = None,
2353
+ is_disconnect: Optional[bool] = None,
2354
+ invalidate_pool_on_disconnect: bool = True,
2355
+ is_pre_ping: bool = False,
2356
+ ) -> NoReturn:
2357
+ exc_info = sys.exc_info()
2358
+
2359
+ if is_disconnect is None:
2360
+ is_disconnect = isinstance(
2361
+ e, dialect.loaded_dbapi.Error
2362
+ ) and dialect.is_disconnect(e, None, None)
2363
+
2364
+ should_wrap = isinstance(e, dialect.loaded_dbapi.Error)
2365
+
2366
+ if should_wrap:
2367
+ sqlalchemy_exception = exc.DBAPIError.instance(
2368
+ None,
2369
+ None,
2370
+ cast(Exception, e),
2371
+ dialect.loaded_dbapi.Error,
2372
+ hide_parameters=(
2373
+ engine.hide_parameters if engine is not None else False
2374
+ ),
2375
+ connection_invalidated=is_disconnect,
2376
+ dialect=dialect,
2377
+ )
2378
+ else:
2379
+ sqlalchemy_exception = None
2380
+
2381
+ newraise = None
2382
+
2383
+ if dialect._has_events:
2384
+ ctx = ExceptionContextImpl(
2385
+ e,
2386
+ sqlalchemy_exception,
2387
+ engine,
2388
+ dialect,
2389
+ None,
2390
+ None,
2391
+ None,
2392
+ None,
2393
+ None,
2394
+ is_disconnect,
2395
+ invalidate_pool_on_disconnect,
2396
+ is_pre_ping,
2397
+ )
2398
+ for fn in dialect.dispatch.handle_error:
2399
+ try:
2400
+ # handler returns an exception;
2401
+ # call next handler in a chain
2402
+ per_fn = fn(ctx)
2403
+ if per_fn is not None:
2404
+ ctx.chained_exception = newraise = per_fn
2405
+ except Exception as _raised:
2406
+ # handler raises an exception - stop processing
2407
+ newraise = _raised
2408
+ break
2409
+
2410
+ if sqlalchemy_exception and is_disconnect != ctx.is_disconnect:
2411
+ sqlalchemy_exception.connection_invalidated = ctx.is_disconnect
2412
+
2413
+ if newraise:
2414
+ raise newraise.with_traceback(exc_info[2]) from e
2415
+ elif should_wrap:
2416
+ assert sqlalchemy_exception is not None
2417
+ raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
2418
+ else:
2419
+ assert exc_info[1] is not None
2420
+ raise exc_info[1].with_traceback(exc_info[2])
2421
+
2422
+ def _run_ddl_visitor(
2423
+ self,
2424
+ visitorcallable: Type[InvokeDDLBase],
2425
+ element: SchemaVisitable,
2426
+ **kwargs: Any,
2427
+ ) -> None:
2428
+ """run a DDL visitor.
2429
+
2430
+ This method is only here so that the MockConnection can change the
2431
+ options given to the visitor so that "checkfirst" is skipped.
2432
+
2433
+ """
2434
+ visitorcallable(
2435
+ dialect=self.dialect, connection=self, **kwargs
2436
+ ).traverse_single(element)
2437
+
2438
+
2439
+ class ExceptionContextImpl(ExceptionContext):
2440
+ """Implement the :class:`.ExceptionContext` interface."""
2441
+
2442
+ __slots__ = (
2443
+ "connection",
2444
+ "engine",
2445
+ "dialect",
2446
+ "cursor",
2447
+ "statement",
2448
+ "parameters",
2449
+ "original_exception",
2450
+ "sqlalchemy_exception",
2451
+ "chained_exception",
2452
+ "execution_context",
2453
+ "is_disconnect",
2454
+ "invalidate_pool_on_disconnect",
2455
+ "is_pre_ping",
2456
+ )
2457
+
2458
+ def __init__(
2459
+ self,
2460
+ exception: BaseException,
2461
+ sqlalchemy_exception: Optional[exc.StatementError],
2462
+ engine: Optional[Engine],
2463
+ dialect: Dialect,
2464
+ connection: Optional[Connection],
2465
+ cursor: Optional[DBAPICursor],
2466
+ statement: Optional[str],
2467
+ parameters: Optional[_DBAPIAnyExecuteParams],
2468
+ context: Optional[ExecutionContext],
2469
+ is_disconnect: bool,
2470
+ invalidate_pool_on_disconnect: bool,
2471
+ is_pre_ping: bool,
2472
+ ):
2473
+ self.engine = engine
2474
+ self.dialect = dialect
2475
+ self.connection = connection
2476
+ self.sqlalchemy_exception = sqlalchemy_exception
2477
+ self.original_exception = exception
2478
+ self.execution_context = context
2479
+ self.statement = statement
2480
+ self.parameters = parameters
2481
+ self.is_disconnect = is_disconnect
2482
+ self.invalidate_pool_on_disconnect = invalidate_pool_on_disconnect
2483
+ self.is_pre_ping = is_pre_ping
2484
+
2485
+
2486
+ class Transaction(TransactionalContext):
2487
+ """Represent a database transaction in progress.
2488
+
2489
+ The :class:`.Transaction` object is procured by
2490
+ calling the :meth:`_engine.Connection.begin` method of
2491
+ :class:`_engine.Connection`::
2492
+
2493
+ from sqlalchemy import create_engine
2494
+
2495
+ engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
2496
+ connection = engine.connect()
2497
+ trans = connection.begin()
2498
+ connection.execute(text("insert into x (a, b) values (1, 2)"))
2499
+ trans.commit()
2500
+
2501
+ The object provides :meth:`.rollback` and :meth:`.commit`
2502
+ methods in order to control transaction boundaries. It
2503
+ also implements a context manager interface so that
2504
+ the Python ``with`` statement can be used with the
2505
+ :meth:`_engine.Connection.begin` method::
2506
+
2507
+ with connection.begin():
2508
+ connection.execute(text("insert into x (a, b) values (1, 2)"))
2509
+
2510
+ The Transaction object is **not** threadsafe.
2511
+
2512
+ .. seealso::
2513
+
2514
+ :meth:`_engine.Connection.begin`
2515
+
2516
+ :meth:`_engine.Connection.begin_twophase`
2517
+
2518
+ :meth:`_engine.Connection.begin_nested`
2519
+
2520
+ .. index::
2521
+ single: thread safety; Transaction
2522
+ """ # noqa
2523
+
2524
+ __slots__ = ()
2525
+
2526
+ _is_root: bool = False
2527
+ is_active: bool
2528
+ connection: Connection
2529
+
2530
+ def __init__(self, connection: Connection):
2531
+ raise NotImplementedError()
2532
+
2533
+ @property
2534
+ def _deactivated_from_connection(self) -> bool:
2535
+ """True if this transaction is totally deactivated from the connection
2536
+ and therefore can no longer affect its state.
2537
+
2538
+ """
2539
+ raise NotImplementedError()
2540
+
2541
+ def _do_close(self) -> None:
2542
+ raise NotImplementedError()
2543
+
2544
+ def _do_rollback(self) -> None:
2545
+ raise NotImplementedError()
2546
+
2547
+ def _do_commit(self) -> None:
2548
+ raise NotImplementedError()
2549
+
2550
+ @property
2551
+ def is_valid(self) -> bool:
2552
+ return self.is_active and not self.connection.invalidated
2553
+
2554
+ def close(self) -> None:
2555
+ """Close this :class:`.Transaction`.
2556
+
2557
+ If this transaction is the base transaction in a begin/commit
2558
+ nesting, the transaction will rollback(). Otherwise, the
2559
+ method returns.
2560
+
2561
+ This is used to cancel a Transaction without affecting the scope of
2562
+ an enclosing transaction.
2563
+
2564
+ """
2565
+ try:
2566
+ self._do_close()
2567
+ finally:
2568
+ assert not self.is_active
2569
+
2570
+ def rollback(self) -> None:
2571
+ """Roll back this :class:`.Transaction`.
2572
+
2573
+ The implementation of this may vary based on the type of transaction in
2574
+ use:
2575
+
2576
+ * For a simple database transaction (e.g. :class:`.RootTransaction`),
2577
+ it corresponds to a ROLLBACK.
2578
+
2579
+ * For a :class:`.NestedTransaction`, it corresponds to a
2580
+ "ROLLBACK TO SAVEPOINT" operation.
2581
+
2582
+ * For a :class:`.TwoPhaseTransaction`, DBAPI-specific methods for two
2583
+ phase transactions may be used.
2584
+
2585
+
2586
+ """
2587
+ try:
2588
+ self._do_rollback()
2589
+ finally:
2590
+ assert not self.is_active
2591
+
2592
+ def commit(self) -> None:
2593
+ """Commit this :class:`.Transaction`.
2594
+
2595
+ The implementation of this may vary based on the type of transaction in
2596
+ use:
2597
+
2598
+ * For a simple database transaction (e.g. :class:`.RootTransaction`),
2599
+ it corresponds to a COMMIT.
2600
+
2601
+ * For a :class:`.NestedTransaction`, it corresponds to a
2602
+ "RELEASE SAVEPOINT" operation.
2603
+
2604
+ * For a :class:`.TwoPhaseTransaction`, DBAPI-specific methods for two
2605
+ phase transactions may be used.
2606
+
2607
+ """
2608
+ try:
2609
+ self._do_commit()
2610
+ finally:
2611
+ assert not self.is_active
2612
+
2613
+ def _get_subject(self) -> Connection:
2614
+ return self.connection
2615
+
2616
+ def _transaction_is_active(self) -> bool:
2617
+ return self.is_active
2618
+
2619
+ def _transaction_is_closed(self) -> bool:
2620
+ return not self._deactivated_from_connection
2621
+
2622
+ def _rollback_can_be_called(self) -> bool:
2623
+ # for RootTransaction / NestedTransaction, it's safe to call
2624
+ # rollback() even if the transaction is deactive and no warnings
2625
+ # will be emitted. tested in
2626
+ # test_transaction.py -> test_no_rollback_in_deactive(?:_savepoint)?
2627
+ return True
2628
+
2629
+
2630
+ class RootTransaction(Transaction):
2631
+ """Represent the "root" transaction on a :class:`_engine.Connection`.
2632
+
2633
+ This corresponds to the current "BEGIN/COMMIT/ROLLBACK" that's occurring
2634
+ for the :class:`_engine.Connection`. The :class:`_engine.RootTransaction`
2635
+ is created by calling upon the :meth:`_engine.Connection.begin` method, and
2636
+ remains associated with the :class:`_engine.Connection` throughout its
2637
+ active span. The current :class:`_engine.RootTransaction` in use is
2638
+ accessible via the :attr:`_engine.Connection.get_transaction` method of
2639
+ :class:`_engine.Connection`.
2640
+
2641
+ In :term:`2.0 style` use, the :class:`_engine.Connection` also employs
2642
+ "autobegin" behavior that will create a new
2643
+ :class:`_engine.RootTransaction` whenever a connection in a
2644
+ non-transactional state is used to emit commands on the DBAPI connection.
2645
+ The scope of the :class:`_engine.RootTransaction` in 2.0 style
2646
+ use can be controlled using the :meth:`_engine.Connection.commit` and
2647
+ :meth:`_engine.Connection.rollback` methods.
2648
+
2649
+
2650
+ """
2651
+
2652
+ _is_root = True
2653
+
2654
+ __slots__ = ("connection", "is_active")
2655
+
2656
+ def __init__(self, connection: Connection):
2657
+ assert connection._transaction is None
2658
+ if connection._trans_context_manager:
2659
+ TransactionalContext._trans_ctx_check(connection)
2660
+ self.connection = connection
2661
+ self._connection_begin_impl()
2662
+ connection._transaction = self
2663
+
2664
+ self.is_active = True
2665
+
2666
+ def _deactivate_from_connection(self) -> None:
2667
+ if self.is_active:
2668
+ assert self.connection._transaction is self
2669
+ self.is_active = False
2670
+
2671
+ elif self.connection._transaction is not self:
2672
+ util.warn("transaction already deassociated from connection")
2673
+
2674
+ @property
2675
+ def _deactivated_from_connection(self) -> bool:
2676
+ return self.connection._transaction is not self
2677
+
2678
+ def _connection_begin_impl(self) -> None:
2679
+ self.connection._begin_impl(self)
2680
+
2681
+ def _connection_rollback_impl(self) -> None:
2682
+ self.connection._rollback_impl()
2683
+
2684
+ def _connection_commit_impl(self) -> None:
2685
+ self.connection._commit_impl()
2686
+
2687
+ def _close_impl(self, try_deactivate: bool = False) -> None:
2688
+ try:
2689
+ if self.is_active:
2690
+ self._connection_rollback_impl()
2691
+
2692
+ if self.connection._nested_transaction:
2693
+ self.connection._nested_transaction._cancel()
2694
+ finally:
2695
+ if self.is_active or try_deactivate:
2696
+ self._deactivate_from_connection()
2697
+ if self.connection._transaction is self:
2698
+ self.connection._transaction = None
2699
+
2700
+ assert not self.is_active
2701
+ assert self.connection._transaction is not self
2702
+
2703
+ def _do_close(self) -> None:
2704
+ self._close_impl()
2705
+
2706
+ def _do_rollback(self) -> None:
2707
+ self._close_impl(try_deactivate=True)
2708
+
2709
+ def _do_commit(self) -> None:
2710
+ if self.is_active:
2711
+ assert self.connection._transaction is self
2712
+
2713
+ try:
2714
+ self._connection_commit_impl()
2715
+ finally:
2716
+ # whether or not commit succeeds, cancel any
2717
+ # nested transactions, make this transaction "inactive"
2718
+ # and remove it as a reset agent
2719
+ if self.connection._nested_transaction:
2720
+ self.connection._nested_transaction._cancel()
2721
+
2722
+ self._deactivate_from_connection()
2723
+
2724
+ # ...however only remove as the connection's current transaction
2725
+ # if commit succeeded. otherwise it stays on so that a rollback
2726
+ # needs to occur.
2727
+ self.connection._transaction = None
2728
+ else:
2729
+ if self.connection._transaction is self:
2730
+ self.connection._invalid_transaction()
2731
+ else:
2732
+ raise exc.InvalidRequestError("This transaction is inactive")
2733
+
2734
+ assert not self.is_active
2735
+ assert self.connection._transaction is not self
2736
+
2737
+
2738
+ class NestedTransaction(Transaction):
2739
+ """Represent a 'nested', or SAVEPOINT transaction.
2740
+
2741
+ The :class:`.NestedTransaction` object is created by calling the
2742
+ :meth:`_engine.Connection.begin_nested` method of
2743
+ :class:`_engine.Connection`.
2744
+
2745
+ When using :class:`.NestedTransaction`, the semantics of "begin" /
2746
+ "commit" / "rollback" are as follows:
2747
+
2748
+ * the "begin" operation corresponds to the "BEGIN SAVEPOINT" command, where
2749
+ the savepoint is given an explicit name that is part of the state
2750
+ of this object.
2751
+
2752
+ * The :meth:`.NestedTransaction.commit` method corresponds to a
2753
+ "RELEASE SAVEPOINT" operation, using the savepoint identifier associated
2754
+ with this :class:`.NestedTransaction`.
2755
+
2756
+ * The :meth:`.NestedTransaction.rollback` method corresponds to a
2757
+ "ROLLBACK TO SAVEPOINT" operation, using the savepoint identifier
2758
+ associated with this :class:`.NestedTransaction`.
2759
+
2760
+ The rationale for mimicking the semantics of an outer transaction in
2761
+ terms of savepoints so that code may deal with a "savepoint" transaction
2762
+ and an "outer" transaction in an agnostic way.
2763
+
2764
+ .. seealso::
2765
+
2766
+ :ref:`session_begin_nested` - ORM version of the SAVEPOINT API.
2767
+
2768
+ """
2769
+
2770
+ __slots__ = ("connection", "is_active", "_savepoint", "_previous_nested")
2771
+
2772
+ _savepoint: str
2773
+
2774
+ def __init__(self, connection: Connection):
2775
+ assert connection._transaction is not None
2776
+ if connection._trans_context_manager:
2777
+ TransactionalContext._trans_ctx_check(connection)
2778
+ self.connection = connection
2779
+ self._savepoint = self.connection._savepoint_impl()
2780
+ self.is_active = True
2781
+ self._previous_nested = connection._nested_transaction
2782
+ connection._nested_transaction = self
2783
+
2784
+ def _deactivate_from_connection(self, warn: bool = True) -> None:
2785
+ if self.connection._nested_transaction is self:
2786
+ self.connection._nested_transaction = self._previous_nested
2787
+ elif warn:
2788
+ util.warn(
2789
+ "nested transaction already deassociated from connection"
2790
+ )
2791
+
2792
+ @property
2793
+ def _deactivated_from_connection(self) -> bool:
2794
+ return self.connection._nested_transaction is not self
2795
+
2796
+ def _cancel(self) -> None:
2797
+ # called by RootTransaction when the outer transaction is
2798
+ # committed, rolled back, or closed to cancel all savepoints
2799
+ # without any action being taken
2800
+ self.is_active = False
2801
+ self._deactivate_from_connection()
2802
+ if self._previous_nested:
2803
+ self._previous_nested._cancel()
2804
+
2805
+ def _close_impl(
2806
+ self, deactivate_from_connection: bool, warn_already_deactive: bool
2807
+ ) -> None:
2808
+ try:
2809
+ if (
2810
+ self.is_active
2811
+ and self.connection._transaction
2812
+ and self.connection._transaction.is_active
2813
+ ):
2814
+ self.connection._rollback_to_savepoint_impl(self._savepoint)
2815
+ finally:
2816
+ self.is_active = False
2817
+
2818
+ if deactivate_from_connection:
2819
+ self._deactivate_from_connection(warn=warn_already_deactive)
2820
+
2821
+ assert not self.is_active
2822
+ if deactivate_from_connection:
2823
+ assert self.connection._nested_transaction is not self
2824
+
2825
+ def _do_close(self) -> None:
2826
+ self._close_impl(True, False)
2827
+
2828
+ def _do_rollback(self) -> None:
2829
+ self._close_impl(True, True)
2830
+
2831
+ def _do_commit(self) -> None:
2832
+ if self.is_active:
2833
+ try:
2834
+ self.connection._release_savepoint_impl(self._savepoint)
2835
+ finally:
2836
+ # nested trans becomes inactive on failed release
2837
+ # unconditionally. this prevents it from trying to
2838
+ # emit SQL when it rolls back.
2839
+ self.is_active = False
2840
+
2841
+ # but only de-associate from connection if it succeeded
2842
+ self._deactivate_from_connection()
2843
+ else:
2844
+ if self.connection._nested_transaction is self:
2845
+ self.connection._invalid_transaction()
2846
+ else:
2847
+ raise exc.InvalidRequestError(
2848
+ "This nested transaction is inactive"
2849
+ )
2850
+
2851
+
2852
+ class TwoPhaseTransaction(RootTransaction):
2853
+ """Represent a two-phase transaction.
2854
+
2855
+ A new :class:`.TwoPhaseTransaction` object may be procured
2856
+ using the :meth:`_engine.Connection.begin_twophase` method.
2857
+
2858
+ The interface is the same as that of :class:`.Transaction`
2859
+ with the addition of the :meth:`prepare` method.
2860
+
2861
+ """
2862
+
2863
+ __slots__ = ("xid", "_is_prepared")
2864
+
2865
+ xid: Any
2866
+
2867
+ def __init__(self, connection: Connection, xid: Any):
2868
+ self._is_prepared = False
2869
+ self.xid = xid
2870
+ super().__init__(connection)
2871
+
2872
+ def prepare(self) -> None:
2873
+ """Prepare this :class:`.TwoPhaseTransaction`.
2874
+
2875
+ After a PREPARE, the transaction can be committed.
2876
+
2877
+ """
2878
+ if not self.is_active:
2879
+ raise exc.InvalidRequestError("This transaction is inactive")
2880
+ self.connection._prepare_twophase_impl(self.xid)
2881
+ self._is_prepared = True
2882
+
2883
+ def _connection_begin_impl(self) -> None:
2884
+ self.connection._begin_twophase_impl(self)
2885
+
2886
+ def _connection_rollback_impl(self) -> None:
2887
+ self.connection._rollback_twophase_impl(self.xid, self._is_prepared)
2888
+
2889
+ def _connection_commit_impl(self) -> None:
2890
+ self.connection._commit_twophase_impl(self.xid, self._is_prepared)
2891
+
2892
+
2893
+ class Engine(
2894
+ ConnectionEventsTarget, log.Identified, inspection.Inspectable["Inspector"]
2895
+ ):
2896
+ """
2897
+ Connects a :class:`~sqlalchemy.pool.Pool` and
2898
+ :class:`~sqlalchemy.engine.interfaces.Dialect` together to provide a
2899
+ source of database connectivity and behavior.
2900
+
2901
+ An :class:`_engine.Engine` object is instantiated publicly using the
2902
+ :func:`~sqlalchemy.create_engine` function.
2903
+
2904
+ .. seealso::
2905
+
2906
+ :doc:`/core/engines`
2907
+
2908
+ :ref:`connections_toplevel`
2909
+
2910
+ """
2911
+
2912
+ dispatch: dispatcher[ConnectionEventsTarget]
2913
+
2914
+ _compiled_cache: Optional[CompiledCacheType]
2915
+
2916
+ _execution_options: _ExecuteOptions = _EMPTY_EXECUTION_OPTS
2917
+ _has_events: bool = False
2918
+ _connection_cls: Type[Connection] = Connection
2919
+ _sqla_logger_namespace: str = "sqlalchemy.engine.Engine"
2920
+ _is_future: bool = False
2921
+
2922
+ _schema_translate_map: Optional[SchemaTranslateMapType] = None
2923
+ _option_cls: Type[OptionEngine]
2924
+
2925
+ dialect: Dialect
2926
+ pool: Pool
2927
+ url: URL
2928
+ hide_parameters: bool
2929
+
2930
+ def __init__(
2931
+ self,
2932
+ pool: Pool,
2933
+ dialect: Dialect,
2934
+ url: URL,
2935
+ logging_name: Optional[str] = None,
2936
+ echo: Optional[_EchoFlagType] = None,
2937
+ query_cache_size: int = 500,
2938
+ execution_options: Optional[Mapping[str, Any]] = None,
2939
+ hide_parameters: bool = False,
2940
+ ):
2941
+ self.pool = pool
2942
+ self.url = url
2943
+ self.dialect = dialect
2944
+ if logging_name:
2945
+ self.logging_name = logging_name
2946
+ self.echo = echo
2947
+ self.hide_parameters = hide_parameters
2948
+ if query_cache_size != 0:
2949
+ self._compiled_cache = util.LRUCache(
2950
+ query_cache_size, size_alert=self._lru_size_alert
2951
+ )
2952
+ else:
2953
+ self._compiled_cache = None
2954
+ log.instance_logger(self, echoflag=echo)
2955
+ if execution_options:
2956
+ self.update_execution_options(**execution_options)
2957
+
2958
+ def _lru_size_alert(self, cache: util.LRUCache[Any, Any]) -> None:
2959
+ if self._should_log_info():
2960
+ self.logger.info(
2961
+ "Compiled cache size pruning from %d items to %d. "
2962
+ "Increase cache size to reduce the frequency of pruning.",
2963
+ len(cache),
2964
+ cache.capacity,
2965
+ )
2966
+
2967
+ @property
2968
+ def engine(self) -> Engine:
2969
+ """Returns this :class:`.Engine`.
2970
+
2971
+ Used for legacy schemes that accept :class:`.Connection` /
2972
+ :class:`.Engine` objects within the same variable.
2973
+
2974
+ """
2975
+ return self
2976
+
2977
+ def clear_compiled_cache(self) -> None:
2978
+ """Clear the compiled cache associated with the dialect.
2979
+
2980
+ This applies **only** to the built-in cache that is established
2981
+ via the :paramref:`_engine.create_engine.query_cache_size` parameter.
2982
+ It will not impact any dictionary caches that were passed via the
2983
+ :paramref:`.Connection.execution_options.compiled_cache` parameter.
2984
+
2985
+ .. versionadded:: 1.4
2986
+
2987
+ """
2988
+ if self._compiled_cache:
2989
+ self._compiled_cache.clear()
2990
+
2991
+ def update_execution_options(self, **opt: Any) -> None:
2992
+ r"""Update the default execution_options dictionary
2993
+ of this :class:`_engine.Engine`.
2994
+
2995
+ The given keys/values in \**opt are added to the
2996
+ default execution options that will be used for
2997
+ all connections. The initial contents of this dictionary
2998
+ can be sent via the ``execution_options`` parameter
2999
+ to :func:`_sa.create_engine`.
3000
+
3001
+ .. seealso::
3002
+
3003
+ :meth:`_engine.Connection.execution_options`
3004
+
3005
+ :meth:`_engine.Engine.execution_options`
3006
+
3007
+ """
3008
+ self.dispatch.set_engine_execution_options(self, opt)
3009
+ self._execution_options = self._execution_options.union(opt)
3010
+ self.dialect.set_engine_execution_options(self, opt)
3011
+
3012
+ @overload
3013
+ def execution_options(
3014
+ self,
3015
+ *,
3016
+ compiled_cache: Optional[CompiledCacheType] = ...,
3017
+ logging_token: str = ...,
3018
+ isolation_level: IsolationLevel = ...,
3019
+ insertmanyvalues_page_size: int = ...,
3020
+ schema_translate_map: Optional[SchemaTranslateMapType] = ...,
3021
+ **opt: Any,
3022
+ ) -> OptionEngine: ...
3023
+
3024
+ @overload
3025
+ def execution_options(self, **opt: Any) -> OptionEngine: ...
3026
+
3027
+ def execution_options(self, **opt: Any) -> OptionEngine:
3028
+ """Return a new :class:`_engine.Engine` that will provide
3029
+ :class:`_engine.Connection` objects with the given execution options.
3030
+
3031
+ The returned :class:`_engine.Engine` remains related to the original
3032
+ :class:`_engine.Engine` in that it shares the same connection pool and
3033
+ other state:
3034
+
3035
+ * The :class:`_pool.Pool` used by the new :class:`_engine.Engine`
3036
+ is the
3037
+ same instance. The :meth:`_engine.Engine.dispose`
3038
+ method will replace
3039
+ the connection pool instance for the parent engine as well
3040
+ as this one.
3041
+ * Event listeners are "cascaded" - meaning, the new
3042
+ :class:`_engine.Engine`
3043
+ inherits the events of the parent, and new events can be associated
3044
+ with the new :class:`_engine.Engine` individually.
3045
+ * The logging configuration and logging_name is copied from the parent
3046
+ :class:`_engine.Engine`.
3047
+
3048
+ The intent of the :meth:`_engine.Engine.execution_options` method is
3049
+ to implement schemes where multiple :class:`_engine.Engine`
3050
+ objects refer to the same connection pool, but are differentiated
3051
+ by options that affect some execution-level behavior for each
3052
+ engine. One such example is breaking into separate "reader" and
3053
+ "writer" :class:`_engine.Engine` instances, where one
3054
+ :class:`_engine.Engine`
3055
+ has a lower :term:`isolation level` setting configured or is even
3056
+ transaction-disabled using "autocommit". An example of this
3057
+ configuration is at :ref:`dbapi_autocommit_multiple`.
3058
+
3059
+ Another example is one that
3060
+ uses a custom option ``shard_id`` which is consumed by an event
3061
+ to change the current schema on a database connection::
3062
+
3063
+ from sqlalchemy import event
3064
+ from sqlalchemy.engine import Engine
3065
+
3066
+ primary_engine = create_engine("mysql+mysqldb://")
3067
+ shard1 = primary_engine.execution_options(shard_id="shard1")
3068
+ shard2 = primary_engine.execution_options(shard_id="shard2")
3069
+
3070
+ shards = {"default": "base", "shard_1": "db1", "shard_2": "db2"}
3071
+
3072
+
3073
+ @event.listens_for(Engine, "before_cursor_execute")
3074
+ def _switch_shard(conn, cursor, stmt, params, context, executemany):
3075
+ shard_id = conn.get_execution_options().get("shard_id", "default")
3076
+ current_shard = conn.info.get("current_shard", None)
3077
+
3078
+ if current_shard != shard_id:
3079
+ cursor.execute("use %s" % shards[shard_id])
3080
+ conn.info["current_shard"] = shard_id
3081
+
3082
+ The above recipe illustrates two :class:`_engine.Engine` objects that
3083
+ will each serve as factories for :class:`_engine.Connection` objects
3084
+ that have pre-established "shard_id" execution options present. A
3085
+ :meth:`_events.ConnectionEvents.before_cursor_execute` event handler
3086
+ then interprets this execution option to emit a MySQL ``use`` statement
3087
+ to switch databases before a statement execution, while at the same
3088
+ time keeping track of which database we've established using the
3089
+ :attr:`_engine.Connection.info` dictionary.
3090
+
3091
+ .. seealso::
3092
+
3093
+ :meth:`_engine.Connection.execution_options`
3094
+ - update execution options
3095
+ on a :class:`_engine.Connection` object.
3096
+
3097
+ :meth:`_engine.Engine.update_execution_options`
3098
+ - update the execution
3099
+ options for a given :class:`_engine.Engine` in place.
3100
+
3101
+ :meth:`_engine.Engine.get_execution_options`
3102
+
3103
+
3104
+ """ # noqa: E501
3105
+ return self._option_cls(self, opt)
3106
+
3107
+ def get_execution_options(self) -> _ExecuteOptions:
3108
+ """Get the non-SQL options which will take effect during execution.
3109
+
3110
+ .. seealso::
3111
+
3112
+ :meth:`_engine.Engine.execution_options`
3113
+ """
3114
+ return self._execution_options
3115
+
3116
+ @property
3117
+ def name(self) -> str:
3118
+ """String name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
3119
+ in use by this :class:`Engine`.
3120
+
3121
+ """
3122
+
3123
+ return self.dialect.name
3124
+
3125
+ @property
3126
+ def driver(self) -> str:
3127
+ """Driver name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
3128
+ in use by this :class:`Engine`.
3129
+
3130
+ """
3131
+
3132
+ return self.dialect.driver
3133
+
3134
+ echo = log.echo_property()
3135
+
3136
+ def __repr__(self) -> str:
3137
+ return "Engine(%r)" % (self.url,)
3138
+
3139
+ def dispose(self, close: bool = True) -> None:
3140
+ """Dispose of the connection pool used by this
3141
+ :class:`_engine.Engine`.
3142
+
3143
+ A new connection pool is created immediately after the old one has been
3144
+ disposed. The previous connection pool is disposed either actively, by
3145
+ closing out all currently checked-in connections in that pool, or
3146
+ passively, by losing references to it but otherwise not closing any
3147
+ connections. The latter strategy is more appropriate for an initializer
3148
+ in a forked Python process.
3149
+
3150
+ Event listeners associated with the old pool via :class:`.PoolEvents`
3151
+ are **transferred to the new pool**; this is to support the pattern
3152
+ by which :class:`.PoolEvents` are set up in terms of the owning
3153
+ :class:`.Engine` without the need to refer to the :class:`.Pool`
3154
+ directly.
3155
+
3156
+ :param close: if left at its default of ``True``, has the
3157
+ effect of fully closing all **currently checked in**
3158
+ database connections. Connections that are still checked out
3159
+ will **not** be closed, however they will no longer be associated
3160
+ with this :class:`_engine.Engine`,
3161
+ so when they are closed individually, eventually the
3162
+ :class:`_pool.Pool` which they are associated with will
3163
+ be garbage collected and they will be closed out fully, if
3164
+ not already closed on checkin.
3165
+
3166
+ If set to ``False``, the previous connection pool is de-referenced,
3167
+ and otherwise not touched in any way.
3168
+
3169
+ .. versionadded:: 1.4.33 Added the :paramref:`.Engine.dispose.close`
3170
+ parameter to allow the replacement of a connection pool in a child
3171
+ process without interfering with the connections used by the parent
3172
+ process.
3173
+
3174
+
3175
+ .. seealso::
3176
+
3177
+ :ref:`engine_disposal`
3178
+
3179
+ :ref:`pooling_multiprocessing`
3180
+
3181
+ :meth:`.ConnectionEvents.engine_disposed`
3182
+
3183
+ """
3184
+ if close:
3185
+ self.pool.dispose()
3186
+ self.pool = self.pool.recreate()
3187
+ self.dispatch.engine_disposed(self)
3188
+
3189
+ @contextlib.contextmanager
3190
+ def _optional_conn_ctx_manager(
3191
+ self, connection: Optional[Connection] = None
3192
+ ) -> Iterator[Connection]:
3193
+ if connection is None:
3194
+ with self.connect() as conn:
3195
+ yield conn
3196
+ else:
3197
+ yield connection
3198
+
3199
+ @contextlib.contextmanager
3200
+ def begin(self) -> Iterator[Connection]:
3201
+ """Return a context manager delivering a :class:`_engine.Connection`
3202
+ with a :class:`.Transaction` established.
3203
+
3204
+ E.g.::
3205
+
3206
+ with engine.begin() as conn:
3207
+ conn.execute(text("insert into table (x, y, z) values (1, 2, 3)"))
3208
+ conn.execute(text("my_special_procedure(5)"))
3209
+
3210
+ Upon successful operation, the :class:`.Transaction`
3211
+ is committed. If an error is raised, the :class:`.Transaction`
3212
+ is rolled back.
3213
+
3214
+ .. seealso::
3215
+
3216
+ :meth:`_engine.Engine.connect` - procure a
3217
+ :class:`_engine.Connection` from
3218
+ an :class:`_engine.Engine`.
3219
+
3220
+ :meth:`_engine.Connection.begin` - start a :class:`.Transaction`
3221
+ for a particular :class:`_engine.Connection`.
3222
+
3223
+ """ # noqa: E501
3224
+ with self.connect() as conn:
3225
+ with conn.begin():
3226
+ yield conn
3227
+
3228
+ def _run_ddl_visitor(
3229
+ self,
3230
+ visitorcallable: Type[InvokeDDLBase],
3231
+ element: SchemaVisitable,
3232
+ **kwargs: Any,
3233
+ ) -> None:
3234
+ with self.begin() as conn:
3235
+ conn._run_ddl_visitor(visitorcallable, element, **kwargs)
3236
+
3237
+ def connect(self) -> Connection:
3238
+ """Return a new :class:`_engine.Connection` object.
3239
+
3240
+ The :class:`_engine.Connection` acts as a Python context manager, so
3241
+ the typical use of this method looks like::
3242
+
3243
+ with engine.connect() as connection:
3244
+ connection.execute(text("insert into table values ('foo')"))
3245
+ connection.commit()
3246
+
3247
+ Where above, after the block is completed, the connection is "closed"
3248
+ and its underlying DBAPI resources are returned to the connection pool.
3249
+ This also has the effect of rolling back any transaction that
3250
+ was explicitly begun or was begun via autobegin, and will
3251
+ emit the :meth:`_events.ConnectionEvents.rollback` event if one was
3252
+ started and is still in progress.
3253
+
3254
+ .. seealso::
3255
+
3256
+ :meth:`_engine.Engine.begin`
3257
+
3258
+ """
3259
+
3260
+ return self._connection_cls(self)
3261
+
3262
+ def raw_connection(self) -> PoolProxiedConnection:
3263
+ """Return a "raw" DBAPI connection from the connection pool.
3264
+
3265
+ The returned object is a proxied version of the DBAPI
3266
+ connection object used by the underlying driver in use.
3267
+ The object will have all the same behavior as the real DBAPI
3268
+ connection, except that its ``close()`` method will result in the
3269
+ connection being returned to the pool, rather than being closed
3270
+ for real.
3271
+
3272
+ This method provides direct DBAPI connection access for
3273
+ special situations when the API provided by
3274
+ :class:`_engine.Connection`
3275
+ is not needed. When a :class:`_engine.Connection` object is already
3276
+ present, the DBAPI connection is available using
3277
+ the :attr:`_engine.Connection.connection` accessor.
3278
+
3279
+ .. seealso::
3280
+
3281
+ :ref:`dbapi_connections`
3282
+
3283
+ """
3284
+ return self.pool.connect()
3285
+
3286
+
3287
+ class OptionEngineMixin(log.Identified):
3288
+ _sa_propagate_class_events = False
3289
+
3290
+ dispatch: dispatcher[ConnectionEventsTarget]
3291
+ _compiled_cache: Optional[CompiledCacheType]
3292
+ dialect: Dialect
3293
+ pool: Pool
3294
+ url: URL
3295
+ hide_parameters: bool
3296
+ echo: log.echo_property
3297
+
3298
+ def __init__(
3299
+ self, proxied: Engine, execution_options: CoreExecuteOptionsParameter
3300
+ ):
3301
+ self._proxied = proxied
3302
+ self.url = proxied.url
3303
+ self.dialect = proxied.dialect
3304
+ self.logging_name = proxied.logging_name
3305
+ self.echo = proxied.echo
3306
+ self._compiled_cache = proxied._compiled_cache
3307
+ self.hide_parameters = proxied.hide_parameters
3308
+ log.instance_logger(self, echoflag=self.echo)
3309
+
3310
+ # note: this will propagate events that are assigned to the parent
3311
+ # engine after this OptionEngine is created. Since we share
3312
+ # the events of the parent we also disallow class-level events
3313
+ # to apply to the OptionEngine class directly.
3314
+ #
3315
+ # the other way this can work would be to transfer existing
3316
+ # events only, using:
3317
+ # self.dispatch._update(proxied.dispatch)
3318
+ #
3319
+ # that might be more appropriate however it would be a behavioral
3320
+ # change for logic that assigns events to the parent engine and
3321
+ # would like it to take effect for the already-created sub-engine.
3322
+ self.dispatch = self.dispatch._join(proxied.dispatch)
3323
+
3324
+ self._execution_options = proxied._execution_options
3325
+ self.update_execution_options(**execution_options)
3326
+
3327
+ def update_execution_options(self, **opt: Any) -> None:
3328
+ raise NotImplementedError()
3329
+
3330
+ if not typing.TYPE_CHECKING:
3331
+ # https://github.com/python/typing/discussions/1095
3332
+
3333
+ @property
3334
+ def pool(self) -> Pool:
3335
+ return self._proxied.pool
3336
+
3337
+ @pool.setter
3338
+ def pool(self, pool: Pool) -> None:
3339
+ self._proxied.pool = pool
3340
+
3341
+ @property
3342
+ def _has_events(self) -> bool:
3343
+ return self._proxied._has_events or self.__dict__.get(
3344
+ "_has_events", False
3345
+ )
3346
+
3347
+ @_has_events.setter
3348
+ def _has_events(self, value: bool) -> None:
3349
+ self.__dict__["_has_events"] = value
3350
+
3351
+
3352
+ class OptionEngine(OptionEngineMixin, Engine):
3353
+ def update_execution_options(self, **opt: Any) -> None:
3354
+ Engine.update_execution_options(self, **opt)
3355
+
3356
+
3357
+ Engine._option_cls = OptionEngine