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