1# sqlalchemy/interfaces.py
2# Copyright (C) 2007-2021 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4# Copyright (C) 2007 Jason Kirtland jek@discorporate.us
5#
6# This module is part of SQLAlchemy and is released under
7# the MIT License: http://www.opensource.org/licenses/mit-license.php
8
9"""Deprecated core event interfaces.
10
11
12.. deprecated:: 0.7
13 As of SQLAlchemy 0.7, the new event system described in
14 :ref:`event_toplevel` replaces the extension/proxy/listener system,
15 providing a consistent interface to all events without the need for
16 subclassing.
17
18"""
19
20from . import event
21from . import util
22
23
24class PoolListener(object):
25 """Hooks into the lifecycle of connections in a :class:`_pool.Pool`.
26
27 .. deprecated:: 0.7
28
29 :class:`.PoolListener` is deprecated and will be removed in a future
30 release. Please refer to :func:`.event.listen` in conjunction with
31 the :class:`_events.PoolEvents` listener interface.
32
33 Usage::
34
35 class MyListener(PoolListener):
36 def connect(self, dbapi_con, con_record):
37 '''perform connect operations'''
38 # etc.
39
40 # create a new pool with a listener
41 p = QueuePool(..., listeners=[MyListener()])
42
43 # add a listener after the fact
44 p.add_listener(MyListener())
45
46 # usage with create_engine()
47 e = create_engine("url://", listeners=[MyListener()])
48
49 All of the standard connection :class:`~sqlalchemy.pool.Pool` types can
50 accept event listeners for key connection lifecycle events:
51 creation, pool check-out and check-in. There are no events fired
52 when a connection closes.
53
54 For any given DB-API connection, there will be one ``connect``
55 event, `n` number of ``checkout`` events, and either `n` or `n - 1`
56 ``checkin`` events. (If a ``Connection`` is detached from its
57 pool via the ``detach()`` method, it won't be checked back in.)
58
59 These are low-level events for low-level objects: raw Python
60 DB-API connections, without the conveniences of the SQLAlchemy
61 ``Connection`` wrapper, ``Dialect`` services or ``ClauseElement``
62 execution. If you execute SQL through the connection, explicitly
63 closing all cursors and other resources is recommended.
64
65 Events also receive a ``_ConnectionRecord``, a long-lived internal
66 ``Pool`` object that basically represents a "slot" in the
67 connection pool. ``_ConnectionRecord`` objects have one public
68 attribute of note: ``info``, a dictionary whose contents are
69 scoped to the lifetime of the DB-API connection managed by the
70 record. You can use this shared storage area however you like.
71
72 There is no need to subclass ``PoolListener`` to handle events.
73 Any class that implements one or more of these methods can be used
74 as a pool listener. The ``Pool`` will inspect the methods
75 provided by a listener object and add the listener to one or more
76 internal event queues based on its capabilities. In terms of
77 efficiency and function call overhead, you're much better off only
78 providing implementations for the hooks you'll be using.
79
80 """
81
82 @classmethod
83 def _adapt_listener(cls, self, listener):
84 """Adapt a :class:`.PoolListener` to individual
85 :class:`event.Dispatch` events.
86
87 """
88
89 methods = ["connect", "first_connect", "checkout", "checkin"]
90 listener = util.as_interface(listener, methods=methods)
91
92 for meth in methods:
93 me_meth = getattr(PoolListener, meth)
94 ls_meth = getattr(listener, meth, None)
95
96 if ls_meth is not None and not util.methods_equivalent(
97 me_meth, ls_meth
98 ):
99 util.warn_deprecated(
100 "PoolListener.%s is deprecated. The "
101 "PoolListener class will be removed in a future "
102 "release. Please transition to the @event interface, "
103 "using @event.listens_for(Engine, '%s')." % (meth, meth)
104 )
105
106 if hasattr(listener, "connect"):
107 event.listen(self, "connect", listener.connect)
108 if hasattr(listener, "first_connect"):
109 event.listen(self, "first_connect", listener.first_connect)
110 if hasattr(listener, "checkout"):
111 event.listen(self, "checkout", listener.checkout)
112 if hasattr(listener, "checkin"):
113 event.listen(self, "checkin", listener.checkin)
114
115 def connect(self, dbapi_con, con_record):
116 """Called once for each new DB-API connection or Pool's ``creator()``.
117
118 dbapi_con
119 A newly connected raw DB-API connection (not a SQLAlchemy
120 ``Connection`` wrapper).
121
122 con_record
123 The ``_ConnectionRecord`` that persistently manages the connection
124
125 """
126
127 def first_connect(self, dbapi_con, con_record):
128 """Called exactly once for the first DB-API connection.
129
130 dbapi_con
131 A newly connected raw DB-API connection (not a SQLAlchemy
132 ``Connection`` wrapper).
133
134 con_record
135 The ``_ConnectionRecord`` that persistently manages the connection
136
137 """
138
139 def checkout(self, dbapi_con, con_record, con_proxy):
140 """Called when a connection is retrieved from the Pool.
141
142 dbapi_con
143 A raw DB-API connection
144
145 con_record
146 The ``_ConnectionRecord`` that persistently manages the connection
147
148 con_proxy
149 The ``_ConnectionFairy`` which manages the connection for the span of
150 the current checkout.
151
152 If you raise an ``exc.DisconnectionError``, the current
153 connection will be disposed and a fresh connection retrieved.
154 Processing of all checkout listeners will abort and restart
155 using the new connection.
156 """
157
158 def checkin(self, dbapi_con, con_record):
159 """Called when a connection returns to the pool.
160
161 Note that the connection may be closed, and may be None if the
162 connection has been invalidated. ``checkin`` will not be called
163 for detached connections. (They do not return to the pool.)
164
165 dbapi_con
166 A raw DB-API connection
167
168 con_record
169 The ``_ConnectionRecord`` that persistently manages the connection
170
171 """
172
173
174class ConnectionProxy(object):
175 """Allows interception of statement execution by Connections.
176
177 .. deprecated:: 0.7
178
179 :class:`.ConnectionProxy` is deprecated and will be removed in a future
180 release. Please refer to :func:`.event.listen` in conjunction with
181 the :class:`_events.ConnectionEvents` listener interface.
182
183 Either or both of the ``execute()`` and ``cursor_execute()``
184 may be implemented to intercept compiled statement and
185 cursor level executions, e.g.::
186
187 class MyProxy(ConnectionProxy):
188 def execute(self, conn, execute, clauseelement,
189 *multiparams, **params):
190 print "compiled statement:", clauseelement
191 return execute(clauseelement, *multiparams, **params)
192
193 def cursor_execute(self, execute, cursor, statement,
194 parameters, context, executemany):
195 print "raw statement:", statement
196 return execute(cursor, statement, parameters, context)
197
198 The ``execute`` argument is a function that will fulfill the default
199 execution behavior for the operation. The signature illustrated
200 in the example should be used.
201
202 The proxy is installed into an :class:`~sqlalchemy.engine.Engine` via
203 the ``proxy`` argument::
204
205 e = create_engine('someurl://', proxy=MyProxy())
206
207 """
208
209 @classmethod
210 def _adapt_listener(cls, self, listener):
211
212 methods = [
213 "execute",
214 "cursor_execute",
215 "begin",
216 "rollback",
217 "commit",
218 "savepoint",
219 "rollback_savepoint",
220 "release_savepoint",
221 "begin_twophase",
222 "prepare_twophase",
223 "rollback_twophase",
224 "commit_twophase",
225 ]
226 for meth in methods:
227 me_meth = getattr(ConnectionProxy, meth)
228 ls_meth = getattr(listener, meth)
229
230 if not util.methods_equivalent(me_meth, ls_meth):
231 util.warn_deprecated(
232 "ConnectionProxy.%s is deprecated. The "
233 "ConnectionProxy class will be removed in a future "
234 "release. Please transition to the @event interface, "
235 "using @event.listens_for(Engine, '%s')." % (meth, meth)
236 )
237
238 def adapt_execute(conn, clauseelement, multiparams, params):
239 def execute_wrapper(clauseelement, *multiparams, **params):
240 return clauseelement, multiparams, params
241
242 return listener.execute(
243 conn, execute_wrapper, clauseelement, *multiparams, **params
244 )
245
246 event.listen(self, "before_execute", adapt_execute)
247
248 def adapt_cursor_execute(
249 conn, cursor, statement, parameters, context, executemany
250 ):
251 def execute_wrapper(cursor, statement, parameters, context):
252 return statement, parameters
253
254 return listener.cursor_execute(
255 execute_wrapper,
256 cursor,
257 statement,
258 parameters,
259 context,
260 executemany,
261 )
262
263 event.listen(self, "before_cursor_execute", adapt_cursor_execute)
264
265 def do_nothing_callback(*arg, **kw):
266 pass
267
268 def adapt_listener(fn):
269 def go(conn, *arg, **kw):
270 fn(conn, do_nothing_callback, *arg, **kw)
271
272 return util.update_wrapper(go, fn)
273
274 event.listen(self, "begin", adapt_listener(listener.begin))
275 event.listen(self, "rollback", adapt_listener(listener.rollback))
276 event.listen(self, "commit", adapt_listener(listener.commit))
277 event.listen(self, "savepoint", adapt_listener(listener.savepoint))
278 event.listen(
279 self,
280 "rollback_savepoint",
281 adapt_listener(listener.rollback_savepoint),
282 )
283 event.listen(
284 self,
285 "release_savepoint",
286 adapt_listener(listener.release_savepoint),
287 )
288 event.listen(
289 self, "begin_twophase", adapt_listener(listener.begin_twophase)
290 )
291 event.listen(
292 self, "prepare_twophase", adapt_listener(listener.prepare_twophase)
293 )
294 event.listen(
295 self,
296 "rollback_twophase",
297 adapt_listener(listener.rollback_twophase),
298 )
299 event.listen(
300 self, "commit_twophase", adapt_listener(listener.commit_twophase)
301 )
302
303 def execute(self, conn, execute, clauseelement, *multiparams, **params):
304 """Intercept high level execute() events."""
305
306 return execute(clauseelement, *multiparams, **params)
307
308 def cursor_execute(
309 self, execute, cursor, statement, parameters, context, executemany
310 ):
311 """Intercept low-level cursor execute() events."""
312
313 return execute(cursor, statement, parameters, context)
314
315 def begin(self, conn, begin):
316 """Intercept begin() events."""
317
318 return begin()
319
320 def rollback(self, conn, rollback):
321 """Intercept rollback() events."""
322
323 return rollback()
324
325 def commit(self, conn, commit):
326 """Intercept commit() events."""
327
328 return commit()
329
330 def savepoint(self, conn, savepoint, name=None):
331 """Intercept savepoint() events."""
332
333 return savepoint(name=name)
334
335 def rollback_savepoint(self, conn, rollback_savepoint, name, context):
336 """Intercept rollback_savepoint() events."""
337
338 return rollback_savepoint(name, context)
339
340 def release_savepoint(self, conn, release_savepoint, name, context):
341 """Intercept release_savepoint() events."""
342
343 return release_savepoint(name, context)
344
345 def begin_twophase(self, conn, begin_twophase, xid):
346 """Intercept begin_twophase() events."""
347
348 return begin_twophase(xid)
349
350 def prepare_twophase(self, conn, prepare_twophase, xid):
351 """Intercept prepare_twophase() events."""
352
353 return prepare_twophase(xid)
354
355 def rollback_twophase(self, conn, rollback_twophase, xid, is_prepared):
356 """Intercept rollback_twophase() events."""
357
358 return rollback_twophase(xid, is_prepared)
359
360 def commit_twophase(self, conn, commit_twophase, xid, is_prepared):
361 """Intercept commit_twophase() events."""
362
363 return commit_twophase(xid, is_prepared)