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