1# pool/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 
    7from __future__ import annotations 
    8 
    9import typing 
    10from typing import Any 
    11from typing import Optional 
    12from typing import Type 
    13from typing import Union 
    14 
    15from .base import ConnectionPoolEntry 
    16from .base import Pool 
    17from .base import PoolProxiedConnection 
    18from .base import PoolResetState 
    19from .. import event 
    20from .. import util 
    21 
    22if typing.TYPE_CHECKING: 
    23    from ..engine import Engine 
    24    from ..engine.interfaces import DBAPIConnection 
    25 
    26 
    27class PoolEvents(event.Events[Pool]): 
    28    """Available events for :class:`_pool.Pool`. 
    29 
    30    The methods here define the name of an event as well 
    31    as the names of members that are passed to listener 
    32    functions. 
    33 
    34    e.g.:: 
    35 
    36        from sqlalchemy import event 
    37 
    38 
    39        def my_on_checkout(dbapi_conn, connection_rec, connection_proxy): 
    40            "handle an on checkout event" 
    41 
    42 
    43        event.listen(Pool, "checkout", my_on_checkout) 
    44 
    45    In addition to accepting the :class:`_pool.Pool` class and 
    46    :class:`_pool.Pool` instances, :class:`_events.PoolEvents` also accepts 
    47    :class:`_engine.Engine` objects and the :class:`_engine.Engine` class as 
    48    targets, which will be resolved to the ``.pool`` attribute of the 
    49    given engine or the :class:`_pool.Pool` class:: 
    50 
    51        engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test") 
    52 
    53        # will associate with engine.pool 
    54        event.listen(engine, "checkout", my_on_checkout) 
    55 
    56    """  # noqa: E501 
    57 
    58    _target_class_doc = "SomeEngineOrPool" 
    59    _dispatch_target = Pool 
    60 
    61    @util.preload_module("sqlalchemy.engine") 
    62    @classmethod 
    63    def _accept_with( 
    64        cls, 
    65        target: Union[Pool, Type[Pool], Engine, Type[Engine]], 
    66        identifier: str, 
    67    ) -> Optional[Union[Pool, Type[Pool]]]: 
    68        if not typing.TYPE_CHECKING: 
    69            Engine = util.preloaded.engine.Engine 
    70 
    71        if isinstance(target, type): 
    72            if issubclass(target, Engine): 
    73                return Pool 
    74            else: 
    75                assert issubclass(target, Pool) 
    76                return target 
    77        elif isinstance(target, Engine): 
    78            return target.pool 
    79        elif isinstance(target, Pool): 
    80            return target 
    81        elif hasattr(target, "_no_async_engine_events"): 
    82            target._no_async_engine_events() 
    83        else: 
    84            return None 
    85 
    86    @classmethod 
    87    def _listen( 
    88        cls, 
    89        event_key: event._EventKey[Pool], 
    90        **kw: Any, 
    91    ) -> None: 
    92        target = event_key.dispatch_target 
    93 
    94        kw.setdefault("asyncio", target._is_asyncio) 
    95 
    96        event_key.base_listen(**kw) 
    97 
    98    def connect( 
    99        self, 
    100        dbapi_connection: DBAPIConnection, 
    101        connection_record: ConnectionPoolEntry, 
    102    ) -> None: 
    103        """Called at the moment a particular DBAPI connection is first 
    104        created for a given :class:`_pool.Pool`. 
    105 
    106        This event allows one to capture the point directly after which 
    107        the DBAPI module-level ``.connect()`` method has been used in order 
    108        to produce a new DBAPI connection. 
    109 
    110        :param dbapi_connection: a DBAPI connection. 
    111         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    112 
    113        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    114         the DBAPI connection. 
    115 
    116        """ 
    117 
    118    def first_connect( 
    119        self, 
    120        dbapi_connection: DBAPIConnection, 
    121        connection_record: ConnectionPoolEntry, 
    122    ) -> None: 
    123        """Called exactly once for the first time a DBAPI connection is 
    124        checked out from a particular :class:`_pool.Pool`. 
    125 
    126        The rationale for :meth:`_events.PoolEvents.first_connect` 
    127        is to determine 
    128        information about a particular series of database connections based 
    129        on the settings used for all connections.  Since a particular 
    130        :class:`_pool.Pool` 
    131        refers to a single "creator" function (which in terms 
    132        of a :class:`_engine.Engine` 
    133        refers to the URL and connection options used), 
    134        it is typically valid to make observations about a single connection 
    135        that can be safely assumed to be valid about all subsequent 
    136        connections, such as the database version, the server and client 
    137        encoding settings, collation settings, and many others. 
    138 
    139        :param dbapi_connection: a DBAPI connection. 
    140         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    141 
    142        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    143         the DBAPI connection. 
    144 
    145        """ 
    146 
    147    def checkout( 
    148        self, 
    149        dbapi_connection: DBAPIConnection, 
    150        connection_record: ConnectionPoolEntry, 
    151        connection_proxy: PoolProxiedConnection, 
    152    ) -> None: 
    153        """Called when a connection is retrieved from the Pool. 
    154 
    155        :param dbapi_connection: a DBAPI connection. 
    156         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    157 
    158        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    159         the DBAPI connection. 
    160 
    161        :param connection_proxy: the :class:`.PoolProxiedConnection` object 
    162          which will proxy the public interface of the DBAPI connection for the 
    163          lifespan of the checkout. 
    164 
    165        If you raise a :class:`~sqlalchemy.exc.DisconnectionError`, the current 
    166        connection will be disposed and a fresh connection retrieved. 
    167        Processing of all checkout listeners will abort and restart 
    168        using the new connection. 
    169 
    170        .. seealso:: :meth:`_events.ConnectionEvents.engine_connect` 
    171           - a similar event 
    172           which occurs upon creation of a new :class:`_engine.Connection`. 
    173 
    174        """ 
    175 
    176    def checkin( 
    177        self, 
    178        dbapi_connection: Optional[DBAPIConnection], 
    179        connection_record: ConnectionPoolEntry, 
    180    ) -> None: 
    181        """Called when a connection returns to the pool. 
    182 
    183        Note that the connection may be closed, and may be None if the 
    184        connection has been invalidated.  ``checkin`` will not be called 
    185        for detached connections.  (They do not return to the pool.) 
    186 
    187        :param dbapi_connection: a DBAPI connection. 
    188         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    189 
    190        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    191         the DBAPI connection. 
    192 
    193        """ 
    194 
    195    @event._legacy_signature( 
    196        "2.0", 
    197        ["dbapi_connection", "connection_record"], 
    198        lambda dbapi_connection, connection_record, reset_state: ( 
    199            dbapi_connection, 
    200            connection_record, 
    201        ), 
    202    ) 
    203    def reset( 
    204        self, 
    205        dbapi_connection: DBAPIConnection, 
    206        connection_record: ConnectionPoolEntry, 
    207        reset_state: PoolResetState, 
    208    ) -> None: 
    209        """Called before the "reset" action occurs for a pooled connection. 
    210 
    211        This event represents 
    212        when the ``rollback()`` method is called on the DBAPI connection 
    213        before it is returned to the pool or discarded. 
    214        A custom "reset" strategy may be implemented using this event hook, 
    215        which may also be combined with disabling the default "reset" 
    216        behavior using the :paramref:`_pool.Pool.reset_on_return` parameter. 
    217 
    218        The primary difference between the :meth:`_events.PoolEvents.reset` and 
    219        :meth:`_events.PoolEvents.checkin` events are that 
    220        :meth:`_events.PoolEvents.reset` is called not just for pooled 
    221        connections that are being returned to the pool, but also for 
    222        connections that were detached using the 
    223        :meth:`_engine.Connection.detach` method as well as asyncio connections 
    224        that are being discarded due to garbage collection taking place on 
    225        connections before the connection was checked in. 
    226 
    227        Note that the event **is not** invoked for connections that were 
    228        invalidated using :meth:`_engine.Connection.invalidate`.    These 
    229        events may be intercepted using the :meth:`.PoolEvents.soft_invalidate` 
    230        and :meth:`.PoolEvents.invalidate` event hooks, and all "connection 
    231        close" events may be intercepted using :meth:`.PoolEvents.close`. 
    232 
    233        The :meth:`_events.PoolEvents.reset` event is usually followed by the 
    234        :meth:`_events.PoolEvents.checkin` event, except in those 
    235        cases where the connection is discarded immediately after reset. 
    236 
    237        :param dbapi_connection: a DBAPI connection. 
    238         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    239 
    240        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    241         the DBAPI connection. 
    242 
    243        :param reset_state: :class:`.PoolResetState` instance which provides 
    244         information about the circumstances under which the connection 
    245         is being reset. 
    246 
    247         .. versionadded:: 2.0 
    248 
    249        .. seealso:: 
    250 
    251            :ref:`pool_reset_on_return` 
    252 
    253            :meth:`_events.ConnectionEvents.rollback` 
    254 
    255            :meth:`_events.ConnectionEvents.commit` 
    256 
    257        """ 
    258 
    259    def invalidate( 
    260        self, 
    261        dbapi_connection: DBAPIConnection, 
    262        connection_record: ConnectionPoolEntry, 
    263        exception: Optional[BaseException], 
    264    ) -> None: 
    265        """Called when a DBAPI connection is to be "invalidated". 
    266 
    267        This event is called any time the 
    268        :meth:`.ConnectionPoolEntry.invalidate` method is invoked, either from 
    269        API usage or via "auto-invalidation", without the ``soft`` flag. 
    270 
    271        The event occurs before a final attempt to call ``.close()`` on the 
    272        connection occurs. 
    273 
    274        :param dbapi_connection: a DBAPI connection. 
    275         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    276 
    277        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    278         the DBAPI connection. 
    279 
    280        :param exception: the exception object corresponding to the reason 
    281         for this invalidation, if any.  May be ``None``. 
    282 
    283        .. seealso:: 
    284 
    285            :ref:`pool_connection_invalidation` 
    286 
    287        """ 
    288 
    289    def soft_invalidate( 
    290        self, 
    291        dbapi_connection: DBAPIConnection, 
    292        connection_record: ConnectionPoolEntry, 
    293        exception: Optional[BaseException], 
    294    ) -> None: 
    295        """Called when a DBAPI connection is to be "soft invalidated". 
    296 
    297        This event is called any time the 
    298        :meth:`.ConnectionPoolEntry.invalidate` 
    299        method is invoked with the ``soft`` flag. 
    300 
    301        Soft invalidation refers to when the connection record that tracks 
    302        this connection will force a reconnect after the current connection 
    303        is checked in.   It does not actively close the dbapi_connection 
    304        at the point at which it is called. 
    305 
    306        :param dbapi_connection: a DBAPI connection. 
    307         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    308 
    309        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    310         the DBAPI connection. 
    311 
    312        :param exception: the exception object corresponding to the reason 
    313         for this invalidation, if any.  May be ``None``. 
    314 
    315        """ 
    316 
    317    def close( 
    318        self, 
    319        dbapi_connection: DBAPIConnection, 
    320        connection_record: ConnectionPoolEntry, 
    321    ) -> None: 
    322        """Called when a DBAPI connection is closed. 
    323 
    324        The event is emitted before the close occurs. 
    325 
    326        The close of a connection can fail; typically this is because 
    327        the connection is already closed.  If the close operation fails, 
    328        the connection is discarded. 
    329 
    330        The :meth:`.close` event corresponds to a connection that's still 
    331        associated with the pool. To intercept close events for detached 
    332        connections use :meth:`.close_detached`. 
    333 
    334        :param dbapi_connection: a DBAPI connection. 
    335         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    336 
    337        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    338         the DBAPI connection. 
    339 
    340        """ 
    341 
    342    def detach( 
    343        self, 
    344        dbapi_connection: DBAPIConnection, 
    345        connection_record: ConnectionPoolEntry, 
    346    ) -> None: 
    347        """Called when a DBAPI connection is "detached" from a pool. 
    348 
    349        This event is emitted after the detach occurs.  The connection 
    350        is no longer associated with the given connection record. 
    351 
    352        :param dbapi_connection: a DBAPI connection. 
    353         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    354 
    355        :param connection_record: the :class:`.ConnectionPoolEntry` managing 
    356         the DBAPI connection. 
    357 
    358        """ 
    359 
    360    def close_detached(self, dbapi_connection: DBAPIConnection) -> None: 
    361        """Called when a detached DBAPI connection is closed. 
    362 
    363        The event is emitted before the close occurs. 
    364 
    365        The close of a connection can fail; typically this is because 
    366        the connection is already closed.  If the close operation fails, 
    367        the connection is discarded. 
    368 
    369        :param dbapi_connection: a DBAPI connection. 
    370         The :attr:`.ConnectionPoolEntry.dbapi_connection` attribute. 
    371 
    372        """