1# engine/events.py
2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8
9from __future__ import annotations
10
11import typing
12from typing import Any
13from typing import Dict
14from typing import Optional
15from typing import Tuple
16from typing import Type
17from typing import Union
18
19from .base import Connection
20from .base import Engine
21from .interfaces import ConnectionEventsTarget
22from .interfaces import DBAPIConnection
23from .interfaces import DBAPICursor
24from .interfaces import Dialect
25from .. import event
26from .. import exc
27from ..util.typing import Literal
28
29if typing.TYPE_CHECKING:
30 from .interfaces import _CoreMultiExecuteParams
31 from .interfaces import _CoreSingleExecuteParams
32 from .interfaces import _DBAPIAnyExecuteParams
33 from .interfaces import _DBAPIMultiExecuteParams
34 from .interfaces import _DBAPISingleExecuteParams
35 from .interfaces import _ExecuteOptions
36 from .interfaces import ExceptionContext
37 from .interfaces import ExecutionContext
38 from .result import Result
39 from ..pool import ConnectionPoolEntry
40 from ..sql import Executable
41 from ..sql.elements import BindParameter
42
43
44class ConnectionEvents(event.Events[ConnectionEventsTarget]):
45 """Available events for
46 :class:`_engine.Connection` and :class:`_engine.Engine`.
47
48 The methods here define the name of an event as well as the names of
49 members that are passed to listener functions.
50
51 An event listener can be associated with any
52 :class:`_engine.Connection` or :class:`_engine.Engine`
53 class or instance, such as an :class:`_engine.Engine`, e.g.::
54
55 from sqlalchemy import event, create_engine
56
57
58 def before_cursor_execute(
59 conn, cursor, statement, parameters, context, executemany
60 ):
61 log.info("Received statement: %s", statement)
62
63
64 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
65 event.listen(engine, "before_cursor_execute", before_cursor_execute)
66
67 or with a specific :class:`_engine.Connection`::
68
69 with engine.begin() as conn:
70
71 @event.listens_for(conn, "before_cursor_execute")
72 def before_cursor_execute(
73 conn, cursor, statement, parameters, context, executemany
74 ):
75 log.info("Received statement: %s", statement)
76
77 When the methods are called with a `statement` parameter, such as in
78 :meth:`.after_cursor_execute` or :meth:`.before_cursor_execute`,
79 the statement is the exact SQL string that was prepared for transmission
80 to the DBAPI ``cursor`` in the connection's :class:`.Dialect`.
81
82 The :meth:`.before_execute` and :meth:`.before_cursor_execute`
83 events can also be established with the ``retval=True`` flag, which
84 allows modification of the statement and parameters to be sent
85 to the database. The :meth:`.before_cursor_execute` event is
86 particularly useful here to add ad-hoc string transformations, such
87 as comments, to all executions::
88
89 from sqlalchemy.engine import Engine
90 from sqlalchemy import event
91
92
93 @event.listens_for(Engine, "before_cursor_execute", retval=True)
94 def comment_sql_calls(
95 conn, cursor, statement, parameters, context, executemany
96 ):
97 statement = statement + " -- some comment"
98 return statement, parameters
99
100 .. note:: :class:`_events.ConnectionEvents` can be established on any
101 combination of :class:`_engine.Engine`, :class:`_engine.Connection`,
102 as well
103 as instances of each of those classes. Events across all
104 four scopes will fire off for a given instance of
105 :class:`_engine.Connection`. However, for performance reasons, the
106 :class:`_engine.Connection` object determines at instantiation time
107 whether or not its parent :class:`_engine.Engine` has event listeners
108 established. Event listeners added to the :class:`_engine.Engine`
109 class or to an instance of :class:`_engine.Engine`
110 *after* the instantiation
111 of a dependent :class:`_engine.Connection` instance will usually
112 *not* be available on that :class:`_engine.Connection` instance.
113 The newly
114 added listeners will instead take effect for
115 :class:`_engine.Connection`
116 instances created subsequent to those event listeners being
117 established on the parent :class:`_engine.Engine` class or instance.
118
119 :param retval=False: Applies to the :meth:`.before_execute` and
120 :meth:`.before_cursor_execute` events only. When True, the
121 user-defined event function must have a return value, which
122 is a tuple of parameters that replace the given statement
123 and parameters. See those methods for a description of
124 specific return arguments.
125
126 """ # noqa
127
128 _target_class_doc = "SomeEngine"
129 _dispatch_target = ConnectionEventsTarget
130
131 @classmethod
132 def _accept_with(
133 cls,
134 target: Union[ConnectionEventsTarget, Type[ConnectionEventsTarget]],
135 identifier: str,
136 ) -> Optional[Union[ConnectionEventsTarget, Type[ConnectionEventsTarget]]]:
137 default_dispatch = super()._accept_with(target, identifier)
138 if default_dispatch is None and hasattr(
139 target, "_no_async_engine_events"
140 ):
141 target._no_async_engine_events()
142
143 return default_dispatch
144
145 @classmethod
146 def _listen(
147 cls,
148 event_key: event._EventKey[ConnectionEventsTarget],
149 *,
150 retval: bool = False,
151 **kw: Any,
152 ) -> None:
153 target, identifier, fn = (
154 event_key.dispatch_target,
155 event_key.identifier,
156 event_key._listen_fn,
157 )
158 target._has_events = True
159
160 if not retval:
161 if identifier == "before_execute":
162 orig_fn = fn
163
164 def wrap_before_execute( # type: ignore
165 conn, clauseelement, multiparams, params, execution_options
166 ):
167 orig_fn(
168 conn,
169 clauseelement,
170 multiparams,
171 params,
172 execution_options,
173 )
174 return clauseelement, multiparams, params
175
176 fn = wrap_before_execute
177 elif identifier == "before_cursor_execute":
178 orig_fn = fn
179
180 def wrap_before_cursor_execute( # type: ignore
181 conn, cursor, statement, parameters, context, executemany
182 ):
183 orig_fn(
184 conn,
185 cursor,
186 statement,
187 parameters,
188 context,
189 executemany,
190 )
191 return statement, parameters
192
193 fn = wrap_before_cursor_execute
194 elif retval and identifier not in (
195 "before_execute",
196 "before_cursor_execute",
197 ):
198 raise exc.ArgumentError(
199 "Only the 'before_execute', "
200 "'before_cursor_execute' and 'handle_error' engine "
201 "event listeners accept the 'retval=True' "
202 "argument."
203 )
204 event_key.with_wrapper(fn).base_listen()
205
206 @event._legacy_signature(
207 "1.4",
208 ["conn", "clauseelement", "multiparams", "params"],
209 lambda conn, clauseelement, multiparams, params, execution_options: (
210 conn,
211 clauseelement,
212 multiparams,
213 params,
214 ),
215 )
216 def before_execute(
217 self,
218 conn: Connection,
219 clauseelement: Executable,
220 multiparams: _CoreMultiExecuteParams,
221 params: _CoreSingleExecuteParams,
222 execution_options: _ExecuteOptions,
223 ) -> Optional[
224 Tuple[Executable, _CoreMultiExecuteParams, _CoreSingleExecuteParams]
225 ]:
226 """Intercept high level execute() events, receiving uncompiled
227 SQL constructs and other objects prior to rendering into SQL.
228
229 This event is good for debugging SQL compilation issues as well
230 as early manipulation of the parameters being sent to the database,
231 as the parameter lists will be in a consistent format here.
232
233 This event can be optionally established with the ``retval=True``
234 flag. The ``clauseelement``, ``multiparams``, and ``params``
235 arguments should be returned as a three-tuple in this case::
236
237 @event.listens_for(Engine, "before_execute", retval=True)
238 def before_execute(conn, clauseelement, multiparams, params):
239 # do something with clauseelement, multiparams, params
240 return clauseelement, multiparams, params
241
242 :param conn: :class:`_engine.Connection` object
243 :param clauseelement: SQL expression construct, :class:`.Compiled`
244 instance, or string statement passed to
245 :meth:`_engine.Connection.execute`.
246 :param multiparams: Multiple parameter sets, a list of dictionaries.
247 :param params: Single parameter set, a single dictionary.
248 :param execution_options: dictionary of execution
249 options passed along with the statement, if any. This is a merge
250 of all options that will be used, including those of the statement,
251 the connection, and those passed in to the method itself for
252 the 2.0 style of execution.
253
254 .. versionadded: 1.4
255
256 .. seealso::
257
258 :meth:`.before_cursor_execute`
259
260 """
261
262 @event._legacy_signature(
263 "1.4",
264 ["conn", "clauseelement", "multiparams", "params", "result"],
265 lambda conn, clauseelement, multiparams, params, execution_options, result: ( # noqa
266 conn,
267 clauseelement,
268 multiparams,
269 params,
270 result,
271 ),
272 )
273 def after_execute(
274 self,
275 conn: Connection,
276 clauseelement: Executable,
277 multiparams: _CoreMultiExecuteParams,
278 params: _CoreSingleExecuteParams,
279 execution_options: _ExecuteOptions,
280 result: Result[Any],
281 ) -> None:
282 """Intercept high level execute() events after execute.
283
284
285 :param conn: :class:`_engine.Connection` object
286 :param clauseelement: SQL expression construct, :class:`.Compiled`
287 instance, or string statement passed to
288 :meth:`_engine.Connection.execute`.
289 :param multiparams: Multiple parameter sets, a list of dictionaries.
290 :param params: Single parameter set, a single dictionary.
291 :param execution_options: dictionary of execution
292 options passed along with the statement, if any. This is a merge
293 of all options that will be used, including those of the statement,
294 the connection, and those passed in to the method itself for
295 the 2.0 style of execution.
296
297 .. versionadded: 1.4
298
299 :param result: :class:`_engine.CursorResult` generated by the
300 execution.
301
302 """
303
304 def before_cursor_execute(
305 self,
306 conn: Connection,
307 cursor: DBAPICursor,
308 statement: str,
309 parameters: _DBAPIAnyExecuteParams,
310 context: Optional[ExecutionContext],
311 executemany: bool,
312 ) -> Optional[Tuple[str, _DBAPIAnyExecuteParams]]:
313 """Intercept low-level cursor execute() events before execution,
314 receiving the string SQL statement and DBAPI-specific parameter list to
315 be invoked against a cursor.
316
317 This event is a good choice for logging as well as late modifications
318 to the SQL string. It's less ideal for parameter modifications except
319 for those which are specific to a target backend.
320
321 This event can be optionally established with the ``retval=True``
322 flag. The ``statement`` and ``parameters`` arguments should be
323 returned as a two-tuple in this case::
324
325 @event.listens_for(Engine, "before_cursor_execute", retval=True)
326 def before_cursor_execute(
327 conn, cursor, statement, parameters, context, executemany
328 ):
329 # do something with statement, parameters
330 return statement, parameters
331
332 See the example at :class:`_events.ConnectionEvents`.
333
334 :param conn: :class:`_engine.Connection` object
335 :param cursor: DBAPI cursor object
336 :param statement: string SQL statement, as to be passed to the DBAPI
337 :param parameters: Dictionary, tuple, or list of parameters being
338 passed to the ``execute()`` or ``executemany()`` method of the
339 DBAPI ``cursor``. In some cases may be ``None``.
340 :param context: :class:`.ExecutionContext` object in use. May
341 be ``None``.
342 :param executemany: boolean, if ``True``, this is an ``executemany()``
343 call, if ``False``, this is an ``execute()`` call.
344
345 .. seealso::
346
347 :meth:`.before_execute`
348
349 :meth:`.after_cursor_execute`
350
351 """
352
353 def after_cursor_execute(
354 self,
355 conn: Connection,
356 cursor: DBAPICursor,
357 statement: str,
358 parameters: _DBAPIAnyExecuteParams,
359 context: Optional[ExecutionContext],
360 executemany: bool,
361 ) -> None:
362 """Intercept low-level cursor execute() events after execution.
363
364 :param conn: :class:`_engine.Connection` object
365 :param cursor: DBAPI cursor object. Will have results pending
366 if the statement was a SELECT, but these should not be consumed
367 as they will be needed by the :class:`_engine.CursorResult`.
368 :param statement: string SQL statement, as passed to the DBAPI
369 :param parameters: Dictionary, tuple, or list of parameters being
370 passed to the ``execute()`` or ``executemany()`` method of the
371 DBAPI ``cursor``. In some cases may be ``None``.
372 :param context: :class:`.ExecutionContext` object in use. May
373 be ``None``.
374 :param executemany: boolean, if ``True``, this is an ``executemany()``
375 call, if ``False``, this is an ``execute()`` call.
376
377 """
378
379 @event._legacy_signature(
380 "2.0", ["conn", "branch"], converter=lambda conn: (conn, False)
381 )
382 def engine_connect(self, conn: Connection) -> None:
383 """Intercept the creation of a new :class:`_engine.Connection`.
384
385 This event is called typically as the direct result of calling
386 the :meth:`_engine.Engine.connect` method.
387
388 It differs from the :meth:`_events.PoolEvents.connect` method, which
389 refers to the actual connection to a database at the DBAPI level;
390 a DBAPI connection may be pooled and reused for many operations.
391 In contrast, this event refers only to the production of a higher level
392 :class:`_engine.Connection` wrapper around such a DBAPI connection.
393
394 It also differs from the :meth:`_events.PoolEvents.checkout` event
395 in that it is specific to the :class:`_engine.Connection` object,
396 not the
397 DBAPI connection that :meth:`_events.PoolEvents.checkout` deals with,
398 although
399 this DBAPI connection is available here via the
400 :attr:`_engine.Connection.connection` attribute.
401 But note there can in fact
402 be multiple :meth:`_events.PoolEvents.checkout`
403 events within the lifespan
404 of a single :class:`_engine.Connection` object, if that
405 :class:`_engine.Connection`
406 is invalidated and re-established.
407
408 :param conn: :class:`_engine.Connection` object.
409
410 .. seealso::
411
412 :meth:`_events.PoolEvents.checkout`
413 the lower-level pool checkout event
414 for an individual DBAPI connection
415
416 """
417
418 def set_connection_execution_options(
419 self, conn: Connection, opts: Dict[str, Any]
420 ) -> None:
421 """Intercept when the :meth:`_engine.Connection.execution_options`
422 method is called.
423
424 This method is called after the new :class:`_engine.Connection`
425 has been
426 produced, with the newly updated execution options collection, but
427 before the :class:`.Dialect` has acted upon any of those new options.
428
429 Note that this method is not called when a new
430 :class:`_engine.Connection`
431 is produced which is inheriting execution options from its parent
432 :class:`_engine.Engine`; to intercept this condition, use the
433 :meth:`_events.ConnectionEvents.engine_connect` event.
434
435 :param conn: The newly copied :class:`_engine.Connection` object
436
437 :param opts: dictionary of options that were passed to the
438 :meth:`_engine.Connection.execution_options` method.
439 This dictionary may be modified in place to affect the ultimate
440 options which take effect.
441
442 .. versionadded:: 2.0 the ``opts`` dictionary may be modified
443 in place.
444
445
446 .. seealso::
447
448 :meth:`_events.ConnectionEvents.set_engine_execution_options`
449 - event
450 which is called when :meth:`_engine.Engine.execution_options`
451 is called.
452
453
454 """
455
456 def set_engine_execution_options(
457 self, engine: Engine, opts: Dict[str, Any]
458 ) -> None:
459 """Intercept when the :meth:`_engine.Engine.execution_options`
460 method is called.
461
462 The :meth:`_engine.Engine.execution_options` method produces a shallow
463 copy of the :class:`_engine.Engine` which stores the new options.
464 That new
465 :class:`_engine.Engine` is passed here.
466 A particular application of this
467 method is to add a :meth:`_events.ConnectionEvents.engine_connect`
468 event
469 handler to the given :class:`_engine.Engine`
470 which will perform some per-
471 :class:`_engine.Connection` task specific to these execution options.
472
473 :param conn: The newly copied :class:`_engine.Engine` object
474
475 :param opts: dictionary of options that were passed to the
476 :meth:`_engine.Connection.execution_options` method.
477 This dictionary may be modified in place to affect the ultimate
478 options which take effect.
479
480 .. versionadded:: 2.0 the ``opts`` dictionary may be modified
481 in place.
482
483 .. seealso::
484
485 :meth:`_events.ConnectionEvents.set_connection_execution_options`
486 - event
487 which is called when :meth:`_engine.Connection.execution_options`
488 is
489 called.
490
491 """
492
493 def engine_disposed(self, engine: Engine) -> None:
494 """Intercept when the :meth:`_engine.Engine.dispose` method is called.
495
496 The :meth:`_engine.Engine.dispose` method instructs the engine to
497 "dispose" of it's connection pool (e.g. :class:`_pool.Pool`), and
498 replaces it with a new one. Disposing of the old pool has the
499 effect that existing checked-in connections are closed. The new
500 pool does not establish any new connections until it is first used.
501
502 This event can be used to indicate that resources related to the
503 :class:`_engine.Engine` should also be cleaned up,
504 keeping in mind that the
505 :class:`_engine.Engine`
506 can still be used for new requests in which case
507 it re-acquires connection resources.
508
509 """
510
511 def begin(self, conn: Connection) -> None:
512 """Intercept begin() events.
513
514 :param conn: :class:`_engine.Connection` object
515
516 """
517
518 def rollback(self, conn: Connection) -> None:
519 """Intercept rollback() events, as initiated by a
520 :class:`.Transaction`.
521
522 Note that the :class:`_pool.Pool` also "auto-rolls back"
523 a DBAPI connection upon checkin, if the ``reset_on_return``
524 flag is set to its default value of ``'rollback'``.
525 To intercept this
526 rollback, use the :meth:`_events.PoolEvents.reset` hook.
527
528 :param conn: :class:`_engine.Connection` object
529
530 .. seealso::
531
532 :meth:`_events.PoolEvents.reset`
533
534 """
535
536 def commit(self, conn: Connection) -> None:
537 """Intercept commit() events, as initiated by a
538 :class:`.Transaction`.
539
540 Note that the :class:`_pool.Pool` may also "auto-commit"
541 a DBAPI connection upon checkin, if the ``reset_on_return``
542 flag is set to the value ``'commit'``. To intercept this
543 commit, use the :meth:`_events.PoolEvents.reset` hook.
544
545 :param conn: :class:`_engine.Connection` object
546 """
547
548 def savepoint(self, conn: Connection, name: str) -> None:
549 """Intercept savepoint() events.
550
551 :param conn: :class:`_engine.Connection` object
552 :param name: specified name used for the savepoint.
553
554 """
555
556 def rollback_savepoint(
557 self, conn: Connection, name: str, context: None
558 ) -> None:
559 """Intercept rollback_savepoint() events.
560
561 :param conn: :class:`_engine.Connection` object
562 :param name: specified name used for the savepoint.
563 :param context: not used
564
565 """
566 # TODO: deprecate "context"
567
568 def release_savepoint(
569 self, conn: Connection, name: str, context: None
570 ) -> None:
571 """Intercept release_savepoint() events.
572
573 :param conn: :class:`_engine.Connection` object
574 :param name: specified name used for the savepoint.
575 :param context: not used
576
577 """
578 # TODO: deprecate "context"
579
580 def begin_twophase(self, conn: Connection, xid: Any) -> None:
581 """Intercept begin_twophase() events.
582
583 :param conn: :class:`_engine.Connection` object
584 :param xid: two-phase XID identifier
585
586 """
587
588 def prepare_twophase(self, conn: Connection, xid: Any) -> None:
589 """Intercept prepare_twophase() events.
590
591 :param conn: :class:`_engine.Connection` object
592 :param xid: two-phase XID identifier
593 """
594
595 def rollback_twophase(
596 self, conn: Connection, xid: Any, is_prepared: bool
597 ) -> None:
598 """Intercept rollback_twophase() events.
599
600 :param conn: :class:`_engine.Connection` object
601 :param xid: two-phase XID identifier
602 :param is_prepared: boolean, indicates if
603 :meth:`.TwoPhaseTransaction.prepare` was called.
604
605 """
606
607 def commit_twophase(
608 self, conn: Connection, xid: Any, is_prepared: bool
609 ) -> None:
610 """Intercept commit_twophase() events.
611
612 :param conn: :class:`_engine.Connection` object
613 :param xid: two-phase XID identifier
614 :param is_prepared: boolean, indicates if
615 :meth:`.TwoPhaseTransaction.prepare` was called.
616
617 """
618
619
620class DialectEvents(event.Events[Dialect]):
621 """event interface for execution-replacement functions.
622
623 These events allow direct instrumentation and replacement
624 of key dialect functions which interact with the DBAPI.
625
626 .. note::
627
628 :class:`.DialectEvents` hooks should be considered **semi-public**
629 and experimental.
630 These hooks are not for general use and are only for those situations
631 where intricate re-statement of DBAPI mechanics must be injected onto
632 an existing dialect. For general-use statement-interception events,
633 please use the :class:`_events.ConnectionEvents` interface.
634
635 .. seealso::
636
637 :meth:`_events.ConnectionEvents.before_cursor_execute`
638
639 :meth:`_events.ConnectionEvents.before_execute`
640
641 :meth:`_events.ConnectionEvents.after_cursor_execute`
642
643 :meth:`_events.ConnectionEvents.after_execute`
644
645 """
646
647 _target_class_doc = "SomeEngine"
648 _dispatch_target = Dialect
649
650 @classmethod
651 def _listen(
652 cls,
653 event_key: event._EventKey[Dialect],
654 *,
655 retval: bool = False,
656 **kw: Any,
657 ) -> None:
658 target = event_key.dispatch_target
659
660 target._has_events = True
661 event_key.base_listen()
662
663 @classmethod
664 def _accept_with(
665 cls,
666 target: Union[Engine, Type[Engine], Dialect, Type[Dialect]],
667 identifier: str,
668 ) -> Optional[Union[Dialect, Type[Dialect]]]:
669 if isinstance(target, type):
670 if issubclass(target, Engine):
671 return Dialect
672 elif issubclass(target, Dialect):
673 return target
674 elif isinstance(target, Engine):
675 return target.dialect
676 elif isinstance(target, Dialect):
677 return target
678 elif isinstance(target, Connection) and identifier == "handle_error":
679 raise exc.InvalidRequestError(
680 "The handle_error() event hook as of SQLAlchemy 2.0 is "
681 "established on the Dialect, and may only be applied to the "
682 "Engine as a whole or to a specific Dialect as a whole, "
683 "not on a per-Connection basis."
684 )
685 elif hasattr(target, "_no_async_engine_events"):
686 target._no_async_engine_events()
687 else:
688 return None
689
690 def handle_error(
691 self, exception_context: ExceptionContext
692 ) -> Optional[BaseException]:
693 r"""Intercept all exceptions processed by the
694 :class:`_engine.Dialect`, typically but not limited to those
695 emitted within the scope of a :class:`_engine.Connection`.
696
697 .. versionchanged:: 2.0 the :meth:`.DialectEvents.handle_error` event
698 is moved to the :class:`.DialectEvents` class, moved from the
699 :class:`.ConnectionEvents` class, so that it may also participate in
700 the "pre ping" operation configured with the
701 :paramref:`_sa.create_engine.pool_pre_ping` parameter. The event
702 remains registered by using the :class:`_engine.Engine` as the event
703 target, however note that using the :class:`_engine.Connection` as
704 an event target for :meth:`.DialectEvents.handle_error` is no longer
705 supported.
706
707 This includes all exceptions emitted by the DBAPI as well as
708 within SQLAlchemy's statement invocation process, including
709 encoding errors and other statement validation errors. Other areas
710 in which the event is invoked include transaction begin and end,
711 result row fetching, cursor creation.
712
713 Note that :meth:`.handle_error` may support new kinds of exceptions
714 and new calling scenarios at *any time*. Code which uses this
715 event must expect new calling patterns to be present in minor
716 releases.
717
718 To support the wide variety of members that correspond to an exception,
719 as well as to allow extensibility of the event without backwards
720 incompatibility, the sole argument received is an instance of
721 :class:`.ExceptionContext`. This object contains data members
722 representing detail about the exception.
723
724 Use cases supported by this hook include:
725
726 * read-only, low-level exception handling for logging and
727 debugging purposes
728 * Establishing whether a DBAPI connection error message indicates
729 that the database connection needs to be reconnected, including
730 for the "pre_ping" handler used by **some** dialects
731 * Establishing or disabling whether a connection or the owning
732 connection pool is invalidated or expired in response to a
733 specific exception
734 * exception re-writing
735
736 The hook is called while the cursor from the failed operation
737 (if any) is still open and accessible. Special cleanup operations
738 can be called on this cursor; SQLAlchemy will attempt to close
739 this cursor subsequent to this hook being invoked.
740
741 As of SQLAlchemy 2.0, the "pre_ping" handler enabled using the
742 :paramref:`_sa.create_engine.pool_pre_ping` parameter will also
743 participate in the :meth:`.handle_error` process, **for those dialects
744 that rely upon disconnect codes to detect database liveness**. Note
745 that some dialects such as psycopg, psycopg2, and most MySQL dialects
746 make use of a native ``ping()`` method supplied by the DBAPI which does
747 not make use of disconnect codes.
748
749 .. versionchanged:: 2.0.0 The :meth:`.DialectEvents.handle_error`
750 event hook participates in connection pool "pre-ping" operations.
751 Within this usage, the :attr:`.ExceptionContext.engine` attribute
752 will be ``None``, however the :class:`.Dialect` in use is always
753 available via the :attr:`.ExceptionContext.dialect` attribute.
754
755 .. versionchanged:: 2.0.5 Added :attr:`.ExceptionContext.is_pre_ping`
756 attribute which will be set to ``True`` when the
757 :meth:`.DialectEvents.handle_error` event hook is triggered within
758 a connection pool pre-ping operation.
759
760 .. versionchanged:: 2.0.5 An issue was repaired that allows for the
761 PostgreSQL ``psycopg`` and ``psycopg2`` drivers, as well as all
762 MySQL drivers, to properly participate in the
763 :meth:`.DialectEvents.handle_error` event hook during
764 connection pool "pre-ping" operations; previously, the
765 implementation was non-working for these drivers.
766
767
768 A handler function has two options for replacing
769 the SQLAlchemy-constructed exception into one that is user
770 defined. It can either raise this new exception directly, in
771 which case all further event listeners are bypassed and the
772 exception will be raised, after appropriate cleanup as taken
773 place::
774
775 @event.listens_for(Engine, "handle_error")
776 def handle_exception(context):
777 if isinstance(
778 context.original_exception, psycopg2.OperationalError
779 ) and "failed" in str(context.original_exception):
780 raise MySpecialException("failed operation")
781
782 .. warning:: Because the
783 :meth:`_events.DialectEvents.handle_error`
784 event specifically provides for exceptions to be re-thrown as
785 the ultimate exception raised by the failed statement,
786 **stack traces will be misleading** if the user-defined event
787 handler itself fails and throws an unexpected exception;
788 the stack trace may not illustrate the actual code line that
789 failed! It is advised to code carefully here and use
790 logging and/or inline debugging if unexpected exceptions are
791 occurring.
792
793 Alternatively, a "chained" style of event handling can be
794 used, by configuring the handler with the ``retval=True``
795 modifier and returning the new exception instance from the
796 function. In this case, event handling will continue onto the
797 next handler. The "chained" exception is available using
798 :attr:`.ExceptionContext.chained_exception`::
799
800 @event.listens_for(Engine, "handle_error", retval=True)
801 def handle_exception(context):
802 if (
803 context.chained_exception is not None
804 and "special" in context.chained_exception.message
805 ):
806 return MySpecialException(
807 "failed", cause=context.chained_exception
808 )
809
810 Handlers that return ``None`` may be used within the chain; when
811 a handler returns ``None``, the previous exception instance,
812 if any, is maintained as the current exception that is passed onto the
813 next handler.
814
815 When a custom exception is raised or returned, SQLAlchemy raises
816 this new exception as-is, it is not wrapped by any SQLAlchemy
817 object. If the exception is not a subclass of
818 :class:`sqlalchemy.exc.StatementError`,
819 certain features may not be available; currently this includes
820 the ORM's feature of adding a detail hint about "autoflush" to
821 exceptions raised within the autoflush process.
822
823 :param context: an :class:`.ExceptionContext` object. See this
824 class for details on all available members.
825
826
827 .. seealso::
828
829 :ref:`pool_new_disconnect_codes`
830
831 """
832
833 def do_connect(
834 self,
835 dialect: Dialect,
836 conn_rec: ConnectionPoolEntry,
837 cargs: Tuple[Any, ...],
838 cparams: Dict[str, Any],
839 ) -> Optional[DBAPIConnection]:
840 """Receive connection arguments before a connection is made.
841
842 This event is useful in that it allows the handler to manipulate the
843 cargs and/or cparams collections that control how the DBAPI
844 ``connect()`` function will be called. ``cargs`` will always be a
845 Python list that can be mutated in-place, and ``cparams`` a Python
846 dictionary that may also be mutated::
847
848 e = create_engine("postgresql+psycopg2://user@host/dbname")
849
850
851 @event.listens_for(e, "do_connect")
852 def receive_do_connect(dialect, conn_rec, cargs, cparams):
853 cparams["password"] = "some_password"
854
855 The event hook may also be used to override the call to ``connect()``
856 entirely, by returning a non-``None`` DBAPI connection object::
857
858 e = create_engine("postgresql+psycopg2://user@host/dbname")
859
860
861 @event.listens_for(e, "do_connect")
862 def receive_do_connect(dialect, conn_rec, cargs, cparams):
863 return psycopg2.connect(*cargs, **cparams)
864
865 .. seealso::
866
867 :ref:`custom_dbapi_args`
868
869 """
870
871 def do_executemany(
872 self,
873 cursor: DBAPICursor,
874 statement: str,
875 parameters: _DBAPIMultiExecuteParams,
876 context: ExecutionContext,
877 ) -> Optional[Literal[True]]:
878 """Receive a cursor to have executemany() called.
879
880 Return the value True to halt further events from invoking,
881 and to indicate that the cursor execution has already taken
882 place within the event handler.
883
884 """
885
886 def do_execute_no_params(
887 self, cursor: DBAPICursor, statement: str, context: ExecutionContext
888 ) -> Optional[Literal[True]]:
889 """Receive a cursor to have execute() with no parameters called.
890
891 Return the value True to halt further events from invoking,
892 and to indicate that the cursor execution has already taken
893 place within the event handler.
894
895 """
896
897 def do_execute(
898 self,
899 cursor: DBAPICursor,
900 statement: str,
901 parameters: _DBAPISingleExecuteParams,
902 context: ExecutionContext,
903 ) -> Optional[Literal[True]]:
904 """Receive a cursor to have execute() called.
905
906 Return the value True to halt further events from invoking,
907 and to indicate that the cursor execution has already taken
908 place within the event handler.
909
910 """
911
912 def do_setinputsizes(
913 self,
914 inputsizes: Dict[BindParameter[Any], Any],
915 cursor: DBAPICursor,
916 statement: str,
917 parameters: _DBAPIAnyExecuteParams,
918 context: ExecutionContext,
919 ) -> None:
920 """Receive the setinputsizes dictionary for possible modification.
921
922 This event is emitted in the case where the dialect makes use of the
923 DBAPI ``cursor.setinputsizes()`` method which passes information about
924 parameter binding for a particular statement. The given
925 ``inputsizes`` dictionary will contain :class:`.BindParameter` objects
926 as keys, linked to DBAPI-specific type objects as values; for
927 parameters that are not bound, they are added to the dictionary with
928 ``None`` as the value, which means the parameter will not be included
929 in the ultimate setinputsizes call. The event may be used to inspect
930 and/or log the datatypes that are being bound, as well as to modify the
931 dictionary in place. Parameters can be added, modified, or removed
932 from this dictionary. Callers will typically want to inspect the
933 :attr:`.BindParameter.type` attribute of the given bind objects in
934 order to make decisions about the DBAPI object.
935
936 After the event, the ``inputsizes`` dictionary is converted into
937 an appropriate datastructure to be passed to ``cursor.setinputsizes``;
938 either a list for a positional bound parameter execution style,
939 or a dictionary of string parameter keys to DBAPI type objects for
940 a named bound parameter execution style.
941
942 The setinputsizes hook overall is only used for dialects which include
943 the flag ``use_setinputsizes=True``. Dialects which use this
944 include python-oracledb, cx_Oracle, pg8000, asyncpg, and pyodbc
945 dialects.
946
947 .. note::
948
949 For use with pyodbc, the ``use_setinputsizes`` flag
950 must be passed to the dialect, e.g.::
951
952 create_engine("mssql+pyodbc://...", use_setinputsizes=True)
953
954 .. seealso::
955
956 :ref:`mssql_pyodbc_setinputsizes`
957
958 .. versionadded:: 1.2.9
959
960 .. seealso::
961
962 :ref:`cx_oracle_setinputsizes`
963
964 """
965 pass