Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/events.py: 54%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

114 statements  

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 Optional 

15from typing import Tuple 

16from typing import Type 

17from typing import Union 

18 

19from .base import Connection 

20from .base import Engine 

21from .interfaces import ConnectionEventsTarget 

22from .interfaces import DBAPIConnection 

23from .interfaces import DBAPICursor 

24from .interfaces import Dialect 

25from .. import event 

26from .. import exc 

27from ..util.typing import Literal 

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