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