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