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