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

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

112 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 

28 

29if typing.TYPE_CHECKING: 

30 from .interfaces import _CoreMultiExecuteParams 

31 from .interfaces import _CoreSingleExecuteParams 

32 from .interfaces import _DBAPIAnyExecuteParams 

33 from .interfaces import _DBAPIMultiExecuteParams 

34 from .interfaces import _DBAPISingleExecuteParams 

35 from .interfaces import _ExecuteOptions 

36 from .interfaces import ExceptionContext 

37 from .interfaces import ExecutionContext 

38 from .result import Result 

39 from ..pool import ConnectionPoolEntry 

40 from ..sql import Executable 

41 from ..sql.elements import BindParameter 

42 

43 

44class ConnectionEvents(event.Events[ConnectionEventsTarget]): 

45 """Available events for 

46 :class:`_engine.Connection` and :class:`_engine.Engine`. 

47 

48 The methods here define the name of an event as well as the names of 

49 members that are passed to listener functions. 

50 

51 An event listener can be associated with any 

52 :class:`_engine.Connection` or :class:`_engine.Engine` 

53 class or instance, such as an :class:`_engine.Engine`, e.g.:: 

54 

55 from sqlalchemy import event, create_engine 

56 

57 

58 def before_cursor_execute( 

59 conn, cursor, statement, parameters, context, executemany 

60 ): 

61 log.info("Received statement: %s", statement) 

62 

63 

64 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test") 

65 event.listen(engine, "before_cursor_execute", before_cursor_execute) 

66 

67 or with a specific :class:`_engine.Connection`:: 

68 

69 with engine.begin() as conn: 

70 

71 @event.listens_for(conn, "before_cursor_execute") 

72 def before_cursor_execute( 

73 conn, cursor, statement, parameters, context, executemany 

74 ): 

75 log.info("Received statement: %s", statement) 

76 

77 When the methods are called with a `statement` parameter, such as in 

78 :meth:`.after_cursor_execute` or :meth:`.before_cursor_execute`, 

79 the statement is the exact SQL string that was prepared for transmission 

80 to the DBAPI ``cursor`` in the connection's :class:`.Dialect`. 

81 

82 The :meth:`.before_execute` and :meth:`.before_cursor_execute` 

83 events can also be established with the ``retval=True`` flag, which 

84 allows modification of the statement and parameters to be sent 

85 to the database. The :meth:`.before_cursor_execute` event is 

86 particularly useful here to add ad-hoc string transformations, such 

87 as comments, to all executions:: 

88 

89 from sqlalchemy.engine import Engine 

90 from sqlalchemy import event 

91 

92 

93 @event.listens_for(Engine, "before_cursor_execute", retval=True) 

94 def comment_sql_calls( 

95 conn, cursor, statement, parameters, context, executemany 

96 ): 

97 statement = statement + " -- some comment" 

98 return statement, parameters 

99 

100 .. note:: :class:`_events.ConnectionEvents` can be established on any 

101 combination of :class:`_engine.Engine`, :class:`_engine.Connection`, 

102 as well 

103 as instances of each of those classes. Events across all 

104 four scopes will fire off for a given instance of 

105 :class:`_engine.Connection`. However, for performance reasons, the 

106 :class:`_engine.Connection` object determines at instantiation time 

107 whether or not its parent :class:`_engine.Engine` has event listeners 

108 established. Event listeners added to the :class:`_engine.Engine` 

109 class or to an instance of :class:`_engine.Engine` 

110 *after* the instantiation 

111 of a dependent :class:`_engine.Connection` instance will usually 

112 *not* be available on that :class:`_engine.Connection` instance. 

113 The newly 

114 added listeners will instead take effect for 

115 :class:`_engine.Connection` 

116 instances created subsequent to those event listeners being 

117 established on the parent :class:`_engine.Engine` class or instance. 

118 

119 :param retval=False: Applies to the :meth:`.before_execute` and 

120 :meth:`.before_cursor_execute` events only. When True, the 

121 user-defined event function must have a return value, which 

122 is a tuple of parameters that replace the given statement 

123 and parameters. See those methods for a description of 

124 specific return arguments. 

125 

126 """ # noqa 

127 

128 _target_class_doc = "SomeEngine" 

129 _dispatch_target = ConnectionEventsTarget 

130 

131 @classmethod 

132 def _accept_with( 

133 cls, 

134 target: Union[ConnectionEventsTarget, Type[ConnectionEventsTarget]], 

135 identifier: str, 

136 ) -> Optional[Union[ConnectionEventsTarget, Type[ConnectionEventsTarget]]]: 

137 default_dispatch = super()._accept_with(target, identifier) 

138 if default_dispatch is None and hasattr( 

139 target, "_no_async_engine_events" 

140 ): 

141 target._no_async_engine_events() 

142 

143 return default_dispatch 

144 

145 @classmethod 

146 def _listen( 

147 cls, 

148 event_key: event._EventKey[ConnectionEventsTarget], 

149 *, 

150 retval: bool = False, 

151 **kw: Any, 

152 ) -> None: 

153 target, identifier, fn = ( 

154 event_key.dispatch_target, 

155 event_key.identifier, 

156 event_key._listen_fn, 

157 ) 

158 target._has_events = True 

159 

160 if not retval: 

161 if identifier == "before_execute": 

162 orig_fn = fn 

163 

164 def wrap_before_execute( # type: ignore 

165 conn, clauseelement, multiparams, params, execution_options 

166 ): 

167 orig_fn( 

168 conn, 

169 clauseelement, 

170 multiparams, 

171 params, 

172 execution_options, 

173 ) 

174 return clauseelement, multiparams, params 

175 

176 fn = wrap_before_execute 

177 elif identifier == "before_cursor_execute": 

178 orig_fn = fn 

179 

180 def wrap_before_cursor_execute( # type: ignore 

181 conn, cursor, statement, parameters, context, executemany 

182 ): 

183 orig_fn( 

184 conn, 

185 cursor, 

186 statement, 

187 parameters, 

188 context, 

189 executemany, 

190 ) 

191 return statement, parameters 

192 

193 fn = wrap_before_cursor_execute 

194 elif retval and identifier not in ( 

195 "before_execute", 

196 "before_cursor_execute", 

197 ): 

198 raise exc.ArgumentError( 

199 "Only the 'before_execute', " 

200 "'before_cursor_execute' and 'handle_error' engine " 

201 "event listeners accept the 'retval=True' " 

202 "argument." 

203 ) 

204 event_key.with_wrapper(fn).base_listen() 

205 

206 @event._legacy_signature( 

207 "1.4", 

208 ["conn", "clauseelement", "multiparams", "params"], 

209 lambda conn, clauseelement, multiparams, params, execution_options: ( 

210 conn, 

211 clauseelement, 

212 multiparams, 

213 params, 

214 ), 

215 ) 

216 def before_execute( 

217 self, 

218 conn: Connection, 

219 clauseelement: Executable, 

220 multiparams: _CoreMultiExecuteParams, 

221 params: _CoreSingleExecuteParams, 

222 execution_options: _ExecuteOptions, 

223 ) -> Optional[ 

224 Tuple[Executable, _CoreMultiExecuteParams, _CoreSingleExecuteParams] 

225 ]: 

226 """Intercept high level execute() events, receiving uncompiled 

227 SQL constructs and other objects prior to rendering into SQL. 

228 

229 This event is good for debugging SQL compilation issues as well 

230 as early manipulation of the parameters being sent to the database, 

231 as the parameter lists will be in a consistent format here. 

232 

233 This event can be optionally established with the ``retval=True`` 

234 flag. The ``clauseelement``, ``multiparams``, and ``params`` 

235 arguments should be returned as a three-tuple in this case:: 

236 

237 @event.listens_for(Engine, "before_execute", retval=True) 

238 def before_execute(conn, clauseelement, multiparams, params): 

239 # do something with clauseelement, multiparams, params 

240 return clauseelement, multiparams, params 

241 

242 :param conn: :class:`_engine.Connection` object 

243 :param clauseelement: SQL expression construct, :class:`.Compiled` 

244 instance, or string statement passed to 

245 :meth:`_engine.Connection.execute`. 

246 :param multiparams: Multiple parameter sets, a list of dictionaries. 

247 :param params: Single parameter set, a single dictionary. 

248 :param execution_options: dictionary of execution 

249 options passed along with the statement, if any. This is a merge 

250 of all options that will be used, including those of the statement, 

251 the connection, and those passed in to the method itself for 

252 the 2.0 style of execution. 

253 

254 .. versionadded: 1.4 

255 

256 .. seealso:: 

257 

258 :meth:`.before_cursor_execute` 

259 

260 """ 

261 

262 @event._legacy_signature( 

263 "1.4", 

264 ["conn", "clauseelement", "multiparams", "params", "result"], 

265 lambda conn, clauseelement, multiparams, params, execution_options, result: ( # noqa 

266 conn, 

267 clauseelement, 

268 multiparams, 

269 params, 

270 result, 

271 ), 

272 ) 

273 def after_execute( 

274 self, 

275 conn: Connection, 

276 clauseelement: Executable, 

277 multiparams: _CoreMultiExecuteParams, 

278 params: _CoreSingleExecuteParams, 

279 execution_options: _ExecuteOptions, 

280 result: Result[Any], 

281 ) -> None: 

282 """Intercept high level execute() events after execute. 

283 

284 

285 :param conn: :class:`_engine.Connection` object 

286 :param clauseelement: SQL expression construct, :class:`.Compiled` 

287 instance, or string statement passed to 

288 :meth:`_engine.Connection.execute`. 

289 :param multiparams: Multiple parameter sets, a list of dictionaries. 

290 :param params: Single parameter set, a single dictionary. 

291 :param execution_options: dictionary of execution 

292 options passed along with the statement, if any. This is a merge 

293 of all options that will be used, including those of the statement, 

294 the connection, and those passed in to the method itself for 

295 the 2.0 style of execution. 

296 

297 .. versionadded: 1.4 

298 

299 :param result: :class:`_engine.CursorResult` generated by the 

300 execution. 

301 

302 """ 

303 

304 def before_cursor_execute( 

305 self, 

306 conn: Connection, 

307 cursor: DBAPICursor, 

308 statement: str, 

309 parameters: _DBAPIAnyExecuteParams, 

310 context: Optional[ExecutionContext], 

311 executemany: bool, 

312 ) -> Optional[Tuple[str, _DBAPIAnyExecuteParams]]: 

313 """Intercept low-level cursor execute() events before execution, 

314 receiving the string SQL statement and DBAPI-specific parameter list to 

315 be invoked against a cursor. 

316 

317 This event is a good choice for logging as well as late modifications 

318 to the SQL string. It's less ideal for parameter modifications except 

319 for those which are specific to a target backend. 

320 

321 This event can be optionally established with the ``retval=True`` 

322 flag. The ``statement`` and ``parameters`` arguments should be 

323 returned as a two-tuple in this case:: 

324 

325 @event.listens_for(Engine, "before_cursor_execute", retval=True) 

326 def before_cursor_execute( 

327 conn, cursor, statement, parameters, context, executemany 

328 ): 

329 # do something with statement, parameters 

330 return statement, parameters 

331 

332 See the example at :class:`_events.ConnectionEvents`. 

333 

334 :param conn: :class:`_engine.Connection` object 

335 :param cursor: DBAPI cursor object 

336 :param statement: string SQL statement, as to be passed to the DBAPI 

337 :param parameters: Dictionary, tuple, or list of parameters being 

338 passed to the ``execute()`` or ``executemany()`` method of the 

339 DBAPI ``cursor``. In some cases may be ``None``. 

340 :param context: :class:`.ExecutionContext` object in use. May 

341 be ``None``. 

342 :param executemany: boolean, if ``True``, this is an ``executemany()`` 

343 call, if ``False``, this is an ``execute()`` call. 

344 

345 .. seealso:: 

346 

347 :meth:`.before_execute` 

348 

349 :meth:`.after_cursor_execute` 

350 

351 """ 

352 

353 def after_cursor_execute( 

354 self, 

355 conn: Connection, 

356 cursor: DBAPICursor, 

357 statement: str, 

358 parameters: _DBAPIAnyExecuteParams, 

359 context: Optional[ExecutionContext], 

360 executemany: bool, 

361 ) -> None: 

362 """Intercept low-level cursor execute() events after execution. 

363 

364 :param conn: :class:`_engine.Connection` object 

365 :param cursor: DBAPI cursor object. Will have results pending 

366 if the statement was a SELECT, but these should not be consumed 

367 as they will be needed by the :class:`_engine.CursorResult`. 

368 :param statement: string SQL statement, as passed to the DBAPI 

369 :param parameters: Dictionary, tuple, or list of parameters being 

370 passed to the ``execute()`` or ``executemany()`` method of the 

371 DBAPI ``cursor``. In some cases may be ``None``. 

372 :param context: :class:`.ExecutionContext` object in use. May 

373 be ``None``. 

374 :param executemany: boolean, if ``True``, this is an ``executemany()`` 

375 call, if ``False``, this is an ``execute()`` call. 

376 

377 """ 

378 

379 @event._legacy_signature( 

380 "2.0", ["conn", "branch"], converter=lambda conn: (conn, False) 

381 ) 

382 def engine_connect(self, conn: Connection) -> None: 

383 """Intercept the creation of a new :class:`_engine.Connection`. 

384 

385 This event is called typically as the direct result of calling 

386 the :meth:`_engine.Engine.connect` method. 

387 

388 It differs from the :meth:`_events.PoolEvents.connect` method, which 

389 refers to the actual connection to a database at the DBAPI level; 

390 a DBAPI connection may be pooled and reused for many operations. 

391 In contrast, this event refers only to the production of a higher level 

392 :class:`_engine.Connection` wrapper around such a DBAPI connection. 

393 

394 It also differs from the :meth:`_events.PoolEvents.checkout` event 

395 in that it is specific to the :class:`_engine.Connection` object, 

396 not the 

397 DBAPI connection that :meth:`_events.PoolEvents.checkout` deals with, 

398 although 

399 this DBAPI connection is available here via the 

400 :attr:`_engine.Connection.connection` attribute. 

401 But note there can in fact 

402 be multiple :meth:`_events.PoolEvents.checkout` 

403 events within the lifespan 

404 of a single :class:`_engine.Connection` object, if that 

405 :class:`_engine.Connection` 

406 is invalidated and re-established. 

407 

408 :param conn: :class:`_engine.Connection` object. 

409 

410 .. seealso:: 

411 

412 :meth:`_events.PoolEvents.checkout` 

413 the lower-level pool checkout event 

414 for an individual DBAPI connection 

415 

416 """ 

417 

418 def set_connection_execution_options( 

419 self, conn: Connection, opts: Dict[str, Any] 

420 ) -> None: 

421 """Intercept when the :meth:`_engine.Connection.execution_options` 

422 method is called. 

423 

424 This method is called after the new :class:`_engine.Connection` 

425 has been 

426 produced, with the newly updated execution options collection, but 

427 before the :class:`.Dialect` has acted upon any of those new options. 

428 

429 Note that this method is not called when a new 

430 :class:`_engine.Connection` 

431 is produced which is inheriting execution options from its parent 

432 :class:`_engine.Engine`; to intercept this condition, use the 

433 :meth:`_events.ConnectionEvents.engine_connect` event. 

434 

435 :param conn: The newly copied :class:`_engine.Connection` object 

436 

437 :param opts: dictionary of options that were passed to the 

438 :meth:`_engine.Connection.execution_options` method. 

439 This dictionary may be modified in place to affect the ultimate 

440 options which take effect. 

441 

442 .. versionadded:: 2.0 the ``opts`` dictionary may be modified 

443 in place. 

444 

445 

446 .. seealso:: 

447 

448 :meth:`_events.ConnectionEvents.set_engine_execution_options` 

449 - event 

450 which is called when :meth:`_engine.Engine.execution_options` 

451 is called. 

452 

453 

454 """ 

455 

456 def set_engine_execution_options( 

457 self, engine: Engine, opts: Dict[str, Any] 

458 ) -> None: 

459 """Intercept when the :meth:`_engine.Engine.execution_options` 

460 method is called. 

461 

462 The :meth:`_engine.Engine.execution_options` method produces a shallow 

463 copy of the :class:`_engine.Engine` which stores the new options. 

464 That new 

465 :class:`_engine.Engine` is passed here. 

466 A particular application of this 

467 method is to add a :meth:`_events.ConnectionEvents.engine_connect` 

468 event 

469 handler to the given :class:`_engine.Engine` 

470 which will perform some per- 

471 :class:`_engine.Connection` task specific to these execution options. 

472 

473 :param conn: The newly copied :class:`_engine.Engine` object 

474 

475 :param opts: dictionary of options that were passed to the 

476 :meth:`_engine.Connection.execution_options` method. 

477 This dictionary may be modified in place to affect the ultimate 

478 options which take effect. 

479 

480 .. versionadded:: 2.0 the ``opts`` dictionary may be modified 

481 in place. 

482 

483 .. seealso:: 

484 

485 :meth:`_events.ConnectionEvents.set_connection_execution_options` 

486 - event 

487 which is called when :meth:`_engine.Connection.execution_options` 

488 is 

489 called. 

490 

491 """ 

492 

493 def engine_disposed(self, engine: Engine) -> None: 

494 """Intercept when the :meth:`_engine.Engine.dispose` method is called. 

495 

496 The :meth:`_engine.Engine.dispose` method instructs the engine to 

497 "dispose" of it's connection pool (e.g. :class:`_pool.Pool`), and 

498 replaces it with a new one. Disposing of the old pool has the 

499 effect that existing checked-in connections are closed. The new 

500 pool does not establish any new connections until it is first used. 

501 

502 This event can be used to indicate that resources related to the 

503 :class:`_engine.Engine` should also be cleaned up, 

504 keeping in mind that the 

505 :class:`_engine.Engine` 

506 can still be used for new requests in which case 

507 it re-acquires connection resources. 

508 

509 """ 

510 

511 def begin(self, conn: Connection) -> None: 

512 """Intercept begin() events. 

513 

514 :param conn: :class:`_engine.Connection` object 

515 

516 """ 

517 

518 def rollback(self, conn: Connection) -> None: 

519 """Intercept rollback() events, as initiated by a 

520 :class:`.Transaction`. 

521 

522 Note that the :class:`_pool.Pool` also "auto-rolls back" 

523 a DBAPI connection upon checkin, if the ``reset_on_return`` 

524 flag is set to its default value of ``'rollback'``. 

525 To intercept this 

526 rollback, use the :meth:`_events.PoolEvents.reset` hook. 

527 

528 :param conn: :class:`_engine.Connection` object 

529 

530 .. seealso:: 

531 

532 :meth:`_events.PoolEvents.reset` 

533 

534 """ 

535 

536 def commit(self, conn: Connection) -> None: 

537 """Intercept commit() events, as initiated by a 

538 :class:`.Transaction`. 

539 

540 Note that the :class:`_pool.Pool` may also "auto-commit" 

541 a DBAPI connection upon checkin, if the ``reset_on_return`` 

542 flag is set to the value ``'commit'``. To intercept this 

543 commit, use the :meth:`_events.PoolEvents.reset` hook. 

544 

545 :param conn: :class:`_engine.Connection` object 

546 """ 

547 

548 def savepoint(self, conn: Connection, name: str) -> None: 

549 """Intercept savepoint() events. 

550 

551 :param conn: :class:`_engine.Connection` object 

552 :param name: specified name used for the savepoint. 

553 

554 """ 

555 

556 def rollback_savepoint( 

557 self, conn: Connection, name: str, context: None 

558 ) -> None: 

559 """Intercept rollback_savepoint() events. 

560 

561 :param conn: :class:`_engine.Connection` object 

562 :param name: specified name used for the savepoint. 

563 :param context: not used 

564 

565 """ 

566 # TODO: deprecate "context" 

567 

568 def release_savepoint( 

569 self, conn: Connection, name: str, context: None 

570 ) -> None: 

571 """Intercept release_savepoint() events. 

572 

573 :param conn: :class:`_engine.Connection` object 

574 :param name: specified name used for the savepoint. 

575 :param context: not used 

576 

577 """ 

578 # TODO: deprecate "context" 

579 

580 def begin_twophase(self, conn: Connection, xid: Any) -> None: 

581 """Intercept begin_twophase() events. 

582 

583 :param conn: :class:`_engine.Connection` object 

584 :param xid: two-phase XID identifier 

585 

586 """ 

587 

588 def prepare_twophase(self, conn: Connection, xid: Any) -> None: 

589 """Intercept prepare_twophase() events. 

590 

591 :param conn: :class:`_engine.Connection` object 

592 :param xid: two-phase XID identifier 

593 """ 

594 

595 def rollback_twophase( 

596 self, conn: Connection, xid: Any, is_prepared: bool 

597 ) -> None: 

598 """Intercept rollback_twophase() events. 

599 

600 :param conn: :class:`_engine.Connection` object 

601 :param xid: two-phase XID identifier 

602 :param is_prepared: boolean, indicates if 

603 :meth:`.TwoPhaseTransaction.prepare` was called. 

604 

605 """ 

606 

607 def commit_twophase( 

608 self, conn: Connection, xid: Any, is_prepared: bool 

609 ) -> None: 

610 """Intercept commit_twophase() events. 

611 

612 :param conn: :class:`_engine.Connection` object 

613 :param xid: two-phase XID identifier 

614 :param is_prepared: boolean, indicates if 

615 :meth:`.TwoPhaseTransaction.prepare` was called. 

616 

617 """ 

618 

619 

620class DialectEvents(event.Events[Dialect]): 

621 """event interface for execution-replacement functions. 

622 

623 These events allow direct instrumentation and replacement 

624 of key dialect functions which interact with the DBAPI. 

625 

626 .. note:: 

627 

628 :class:`.DialectEvents` hooks should be considered **semi-public** 

629 and experimental. 

630 These hooks are not for general use and are only for those situations 

631 where intricate re-statement of DBAPI mechanics must be injected onto 

632 an existing dialect. For general-use statement-interception events, 

633 please use the :class:`_events.ConnectionEvents` interface. 

634 

635 .. seealso:: 

636 

637 :meth:`_events.ConnectionEvents.before_cursor_execute` 

638 

639 :meth:`_events.ConnectionEvents.before_execute` 

640 

641 :meth:`_events.ConnectionEvents.after_cursor_execute` 

642 

643 :meth:`_events.ConnectionEvents.after_execute` 

644 

645 """ 

646 

647 _target_class_doc = "SomeEngine" 

648 _dispatch_target = Dialect 

649 

650 @classmethod 

651 def _listen( 

652 cls, 

653 event_key: event._EventKey[Dialect], 

654 *, 

655 retval: bool = False, 

656 **kw: Any, 

657 ) -> None: 

658 target = event_key.dispatch_target 

659 

660 target._has_events = True 

661 event_key.base_listen() 

662 

663 @classmethod 

664 def _accept_with( 

665 cls, 

666 target: Union[Engine, Type[Engine], Dialect, Type[Dialect]], 

667 identifier: str, 

668 ) -> Optional[Union[Dialect, Type[Dialect]]]: 

669 if isinstance(target, type): 

670 if issubclass(target, Engine): 

671 return Dialect 

672 elif issubclass(target, Dialect): 

673 return target 

674 elif isinstance(target, Engine): 

675 return target.dialect 

676 elif isinstance(target, Dialect): 

677 return target 

678 elif isinstance(target, Connection) and identifier == "handle_error": 

679 raise exc.InvalidRequestError( 

680 "The handle_error() event hook as of SQLAlchemy 2.0 is " 

681 "established on the Dialect, and may only be applied to the " 

682 "Engine as a whole or to a specific Dialect as a whole, " 

683 "not on a per-Connection basis." 

684 ) 

685 elif hasattr(target, "_no_async_engine_events"): 

686 target._no_async_engine_events() 

687 else: 

688 return None 

689 

690 def handle_error( 

691 self, exception_context: ExceptionContext 

692 ) -> Optional[BaseException]: 

693 r"""Intercept all exceptions processed by the 

694 :class:`_engine.Dialect`, typically but not limited to those 

695 emitted within the scope of a :class:`_engine.Connection`. 

696 

697 .. versionchanged:: 2.0 the :meth:`.DialectEvents.handle_error` event 

698 is moved to the :class:`.DialectEvents` class, moved from the 

699 :class:`.ConnectionEvents` class, so that it may also participate in 

700 the "pre ping" operation configured with the 

701 :paramref:`_sa.create_engine.pool_pre_ping` parameter. The event 

702 remains registered by using the :class:`_engine.Engine` as the event 

703 target, however note that using the :class:`_engine.Connection` as 

704 an event target for :meth:`.DialectEvents.handle_error` is no longer 

705 supported. 

706 

707 This includes all exceptions emitted by the DBAPI as well as 

708 within SQLAlchemy's statement invocation process, including 

709 encoding errors and other statement validation errors. Other areas 

710 in which the event is invoked include transaction begin and end, 

711 result row fetching, cursor creation. 

712 

713 Note that :meth:`.handle_error` may support new kinds of exceptions 

714 and new calling scenarios at *any time*. Code which uses this 

715 event must expect new calling patterns to be present in minor 

716 releases. 

717 

718 To support the wide variety of members that correspond to an exception, 

719 as well as to allow extensibility of the event without backwards 

720 incompatibility, the sole argument received is an instance of 

721 :class:`.ExceptionContext`. This object contains data members 

722 representing detail about the exception. 

723 

724 Use cases supported by this hook include: 

725 

726 * read-only, low-level exception handling for logging and 

727 debugging purposes 

728 * Establishing whether a DBAPI connection error message indicates 

729 that the database connection needs to be reconnected, including 

730 for the "pre_ping" handler used by **some** dialects 

731 * Establishing or disabling whether a connection or the owning 

732 connection pool is invalidated or expired in response to a 

733 specific exception 

734 * exception re-writing 

735 

736 The hook is called while the cursor from the failed operation 

737 (if any) is still open and accessible. Special cleanup operations 

738 can be called on this cursor; SQLAlchemy will attempt to close 

739 this cursor subsequent to this hook being invoked. 

740 

741 As of SQLAlchemy 2.0, the "pre_ping" handler enabled using the 

742 :paramref:`_sa.create_engine.pool_pre_ping` parameter will also 

743 participate in the :meth:`.handle_error` process, **for those dialects 

744 that rely upon disconnect codes to detect database liveness**. Note 

745 that some dialects such as psycopg, psycopg2, and most MySQL dialects 

746 make use of a native ``ping()`` method supplied by the DBAPI which does 

747 not make use of disconnect codes. 

748 

749 .. versionchanged:: 2.0.0 The :meth:`.DialectEvents.handle_error` 

750 event hook participates in connection pool "pre-ping" operations. 

751 Within this usage, the :attr:`.ExceptionContext.engine` attribute 

752 will be ``None``, however the :class:`.Dialect` in use is always 

753 available via the :attr:`.ExceptionContext.dialect` attribute. 

754 

755 .. versionchanged:: 2.0.5 Added :attr:`.ExceptionContext.is_pre_ping` 

756 attribute which will be set to ``True`` when the 

757 :meth:`.DialectEvents.handle_error` event hook is triggered within 

758 a connection pool pre-ping operation. 

759 

760 .. versionchanged:: 2.0.5 An issue was repaired that allows for the 

761 PostgreSQL ``psycopg`` and ``psycopg2`` drivers, as well as all 

762 MySQL drivers, to properly participate in the 

763 :meth:`.DialectEvents.handle_error` event hook during 

764 connection pool "pre-ping" operations; previously, the 

765 implementation was non-working for these drivers. 

766 

767 

768 A handler function has two options for replacing 

769 the SQLAlchemy-constructed exception into one that is user 

770 defined. It can either raise this new exception directly, in 

771 which case all further event listeners are bypassed and the 

772 exception will be raised, after appropriate cleanup as taken 

773 place:: 

774 

775 @event.listens_for(Engine, "handle_error") 

776 def handle_exception(context): 

777 if isinstance( 

778 context.original_exception, psycopg2.OperationalError 

779 ) and "failed" in str(context.original_exception): 

780 raise MySpecialException("failed operation") 

781 

782 .. warning:: Because the 

783 :meth:`_events.DialectEvents.handle_error` 

784 event specifically provides for exceptions to be re-thrown as 

785 the ultimate exception raised by the failed statement, 

786 **stack traces will be misleading** if the user-defined event 

787 handler itself fails and throws an unexpected exception; 

788 the stack trace may not illustrate the actual code line that 

789 failed! It is advised to code carefully here and use 

790 logging and/or inline debugging if unexpected exceptions are 

791 occurring. 

792 

793 Alternatively, a "chained" style of event handling can be 

794 used, by configuring the handler with the ``retval=True`` 

795 modifier and returning the new exception instance from the 

796 function. In this case, event handling will continue onto the 

797 next handler. The "chained" exception is available using 

798 :attr:`.ExceptionContext.chained_exception`:: 

799 

800 @event.listens_for(Engine, "handle_error", retval=True) 

801 def handle_exception(context): 

802 if ( 

803 context.chained_exception is not None 

804 and "special" in context.chained_exception.message 

805 ): 

806 return MySpecialException( 

807 "failed", cause=context.chained_exception 

808 ) 

809 

810 Handlers that return ``None`` may be used within the chain; when 

811 a handler returns ``None``, the previous exception instance, 

812 if any, is maintained as the current exception that is passed onto the 

813 next handler. 

814 

815 When a custom exception is raised or returned, SQLAlchemy raises 

816 this new exception as-is, it is not wrapped by any SQLAlchemy 

817 object. If the exception is not a subclass of 

818 :class:`sqlalchemy.exc.StatementError`, 

819 certain features may not be available; currently this includes 

820 the ORM's feature of adding a detail hint about "autoflush" to 

821 exceptions raised within the autoflush process. 

822 

823 :param context: an :class:`.ExceptionContext` object. See this 

824 class for details on all available members. 

825 

826 

827 .. seealso:: 

828 

829 :ref:`pool_new_disconnect_codes` 

830 

831 """ 

832 

833 def do_connect( 

834 self, 

835 dialect: Dialect, 

836 conn_rec: ConnectionPoolEntry, 

837 cargs: Tuple[Any, ...], 

838 cparams: Dict[str, Any], 

839 ) -> Optional[DBAPIConnection]: 

840 """Receive connection arguments before a connection is made. 

841 

842 This event is useful in that it allows the handler to manipulate the 

843 cargs and/or cparams collections that control how the DBAPI 

844 ``connect()`` function will be called. ``cargs`` will always be a 

845 Python list that can be mutated in-place, and ``cparams`` a Python 

846 dictionary that may also be mutated:: 

847 

848 e = create_engine("postgresql+psycopg2://user@host/dbname") 

849 

850 

851 @event.listens_for(e, "do_connect") 

852 def receive_do_connect(dialect, conn_rec, cargs, cparams): 

853 cparams["password"] = "some_password" 

854 

855 The event hook may also be used to override the call to ``connect()`` 

856 entirely, by returning a non-``None`` DBAPI connection object:: 

857 

858 e = create_engine("postgresql+psycopg2://user@host/dbname") 

859 

860 

861 @event.listens_for(e, "do_connect") 

862 def receive_do_connect(dialect, conn_rec, cargs, cparams): 

863 return psycopg2.connect(*cargs, **cparams) 

864 

865 .. seealso:: 

866 

867 :ref:`custom_dbapi_args` 

868 

869 """ 

870 

871 def do_executemany( 

872 self, 

873 cursor: DBAPICursor, 

874 statement: str, 

875 parameters: _DBAPIMultiExecuteParams, 

876 context: ExecutionContext, 

877 ) -> Optional[Literal[True]]: 

878 """Receive a cursor to have executemany() called. 

879 

880 Return the value True to halt further events from invoking, 

881 and to indicate that the cursor execution has already taken 

882 place within the event handler. 

883 

884 """ 

885 

886 def do_execute_no_params( 

887 self, cursor: DBAPICursor, statement: str, context: ExecutionContext 

888 ) -> Optional[Literal[True]]: 

889 """Receive a cursor to have execute() with no parameters called. 

890 

891 Return the value True to halt further events from invoking, 

892 and to indicate that the cursor execution has already taken 

893 place within the event handler. 

894 

895 """ 

896 

897 def do_execute( 

898 self, 

899 cursor: DBAPICursor, 

900 statement: str, 

901 parameters: _DBAPISingleExecuteParams, 

902 context: ExecutionContext, 

903 ) -> Optional[Literal[True]]: 

904 """Receive a cursor to have execute() called. 

905 

906 Return the value True to halt further events from invoking, 

907 and to indicate that the cursor execution has already taken 

908 place within the event handler. 

909 

910 """ 

911 

912 def do_setinputsizes( 

913 self, 

914 inputsizes: Dict[BindParameter[Any], Any], 

915 cursor: DBAPICursor, 

916 statement: str, 

917 parameters: _DBAPIAnyExecuteParams, 

918 context: ExecutionContext, 

919 ) -> None: 

920 """Receive the setinputsizes dictionary for possible modification. 

921 

922 This event is emitted in the case where the dialect makes use of the 

923 DBAPI ``cursor.setinputsizes()`` method which passes information about 

924 parameter binding for a particular statement. The given 

925 ``inputsizes`` dictionary will contain :class:`.BindParameter` objects 

926 as keys, linked to DBAPI-specific type objects as values; for 

927 parameters that are not bound, they are added to the dictionary with 

928 ``None`` as the value, which means the parameter will not be included 

929 in the ultimate setinputsizes call. The event may be used to inspect 

930 and/or log the datatypes that are being bound, as well as to modify the 

931 dictionary in place. Parameters can be added, modified, or removed 

932 from this dictionary. Callers will typically want to inspect the 

933 :attr:`.BindParameter.type` attribute of the given bind objects in 

934 order to make decisions about the DBAPI object. 

935 

936 After the event, the ``inputsizes`` dictionary is converted into 

937 an appropriate datastructure to be passed to ``cursor.setinputsizes``; 

938 either a list for a positional bound parameter execution style, 

939 or a dictionary of string parameter keys to DBAPI type objects for 

940 a named bound parameter execution style. 

941 

942 The setinputsizes hook overall is only used for dialects which include 

943 the flag ``use_setinputsizes=True``. Dialects which use this 

944 include python-oracledb, cx_Oracle, pg8000, asyncpg, and pyodbc 

945 dialects. 

946 

947 .. note:: 

948 

949 For use with pyodbc, the ``use_setinputsizes`` flag 

950 must be passed to the dialect, e.g.:: 

951 

952 create_engine("mssql+pyodbc://...", use_setinputsizes=True) 

953 

954 .. seealso:: 

955 

956 :ref:`mssql_pyodbc_setinputsizes` 

957 

958 .. versionadded:: 1.2.9 

959 

960 .. seealso:: 

961 

962 :ref:`cx_oracle_setinputsizes` 

963 

964 """ 

965 pass