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