Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

113 statements  

1# engine/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 

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 def before_cursor_execute(conn, cursor, statement, parameters, context, 

60 executemany): 

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

62 

63 engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/test') 

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

65 

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

67 

68 with engine.begin() as conn: 

69 @event.listens_for(conn, 'before_cursor_execute') 

70 def before_cursor_execute(conn, cursor, statement, parameters, 

71 context, executemany): 

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

73 

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

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

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

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

78 

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

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

81 allows modification of the statement and parameters to be sent 

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

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

84 as comments, to all executions:: 

85 

86 from sqlalchemy.engine import Engine 

87 from sqlalchemy import event 

88 

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

90 def comment_sql_calls(conn, cursor, statement, parameters, 

91 context, executemany): 

92 statement = statement + " -- some comment" 

93 return statement, parameters 

94 

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

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

97 as well 

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

99 four scopes will fire off for a given instance of 

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

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

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

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

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

105 *after* the instantiation 

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

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

108 The newly 

109 added listeners will instead take effect for 

110 :class:`_engine.Connection` 

111 instances created subsequent to those event listeners being 

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

113 

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

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

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

117 is a tuple of parameters that replace the given statement 

118 and parameters. See those methods for a description of 

119 specific return arguments. 

120 

121 """ # noqa 

122 

123 _target_class_doc = "SomeEngine" 

124 _dispatch_target = ConnectionEventsTarget 

125 

126 @classmethod 

127 def _accept_with( 

128 cls, 

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

130 identifier: str, 

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

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

133 if default_dispatch is None and hasattr( 

134 target, "_no_async_engine_events" 

135 ): 

136 target._no_async_engine_events() 

137 

138 return default_dispatch 

139 

140 @classmethod 

141 def _listen( 

142 cls, 

143 event_key: event._EventKey[ConnectionEventsTarget], 

144 *, 

145 retval: bool = False, 

146 **kw: Any, 

147 ) -> None: 

148 target, identifier, fn = ( 

149 event_key.dispatch_target, 

150 event_key.identifier, 

151 event_key._listen_fn, 

152 ) 

153 target._has_events = True 

154 

155 if not retval: 

156 if identifier == "before_execute": 

157 orig_fn = fn 

158 

159 def wrap_before_execute( # type: ignore 

160 conn, clauseelement, multiparams, params, execution_options 

161 ): 

162 orig_fn( 

163 conn, 

164 clauseelement, 

165 multiparams, 

166 params, 

167 execution_options, 

168 ) 

169 return clauseelement, multiparams, params 

170 

171 fn = wrap_before_execute 

172 elif identifier == "before_cursor_execute": 

173 orig_fn = fn 

174 

175 def wrap_before_cursor_execute( # type: ignore 

176 conn, cursor, statement, parameters, context, executemany 

177 ): 

178 orig_fn( 

179 conn, 

180 cursor, 

181 statement, 

182 parameters, 

183 context, 

184 executemany, 

185 ) 

186 return statement, parameters 

187 

188 fn = wrap_before_cursor_execute 

189 elif retval and identifier not in ( 

190 "before_execute", 

191 "before_cursor_execute", 

192 ): 

193 raise exc.ArgumentError( 

194 "Only the 'before_execute', " 

195 "'before_cursor_execute' and 'handle_error' engine " 

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

197 "argument." 

198 ) 

199 event_key.with_wrapper(fn).base_listen() 

200 

201 @event._legacy_signature( 

202 "1.4", 

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

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

205 conn, 

206 clauseelement, 

207 multiparams, 

208 params, 

209 ), 

210 ) 

211 def before_execute( 

212 self, 

213 conn: Connection, 

214 clauseelement: Executable, 

215 multiparams: _CoreMultiExecuteParams, 

216 params: _CoreSingleExecuteParams, 

217 execution_options: _ExecuteOptions, 

218 ) -> Optional[ 

219 Tuple[Executable, _CoreMultiExecuteParams, _CoreSingleExecuteParams] 

220 ]: 

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

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

223 

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

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

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

227 

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

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

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

231 

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

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

234 # do something with clauseelement, multiparams, params 

235 return clauseelement, multiparams, params 

236 

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

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

239 instance, or string statement passed to 

240 :meth:`_engine.Connection.execute`. 

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

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

243 :param execution_options: dictionary of execution 

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

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

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

247 the 2.0 style of execution. 

248 

249 .. versionadded: 1.4 

250 

251 .. seealso:: 

252 

253 :meth:`.before_cursor_execute` 

254 

255 """ 

256 

257 @event._legacy_signature( 

258 "1.4", 

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

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

261 conn, 

262 clauseelement, 

263 multiparams, 

264 params, 

265 result, 

266 ), 

267 ) 

268 def after_execute( 

269 self, 

270 conn: Connection, 

271 clauseelement: Executable, 

272 multiparams: _CoreMultiExecuteParams, 

273 params: _CoreSingleExecuteParams, 

274 execution_options: _ExecuteOptions, 

275 result: Result[Unpack[TupleAny]], 

276 ) -> None: 

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

278 

279 

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

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

282 instance, or string statement passed to 

283 :meth:`_engine.Connection.execute`. 

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

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

286 :param execution_options: dictionary of execution 

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

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

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

290 the 2.0 style of execution. 

291 

292 .. versionadded: 1.4 

293 

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

295 execution. 

296 

297 """ 

298 

299 def before_cursor_execute( 

300 self, 

301 conn: Connection, 

302 cursor: DBAPICursor, 

303 statement: str, 

304 parameters: _DBAPIAnyExecuteParams, 

305 context: Optional[ExecutionContext], 

306 executemany: bool, 

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

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

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

310 be invoked against a cursor. 

311 

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

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

314 for those which are specific to a target backend. 

315 

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

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

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

319 

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

321 def before_cursor_execute(conn, cursor, statement, 

322 parameters, context, executemany): 

323 # do something with statement, parameters 

324 return statement, parameters 

325 

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

327 

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

329 :param cursor: DBAPI cursor object 

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

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

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

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

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

335 be ``None``. 

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

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

338 

339 .. seealso:: 

340 

341 :meth:`.before_execute` 

342 

343 :meth:`.after_cursor_execute` 

344 

345 """ 

346 

347 def after_cursor_execute( 

348 self, 

349 conn: Connection, 

350 cursor: DBAPICursor, 

351 statement: str, 

352 parameters: _DBAPIAnyExecuteParams, 

353 context: Optional[ExecutionContext], 

354 executemany: bool, 

355 ) -> None: 

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

357 

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

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

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

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

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

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

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

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

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

367 be ``None``. 

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

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

370 

371 """ 

372 

373 @event._legacy_signature( 

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

375 ) 

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

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

378 

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

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

381 

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

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

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

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

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

387 

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

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

390 not the 

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

392 although 

393 this DBAPI connection is available here via the 

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

395 But note there can in fact 

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

397 events within the lifespan 

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

399 :class:`_engine.Connection` 

400 is invalidated and re-established. 

401 

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

403 

404 .. seealso:: 

405 

406 :meth:`_events.PoolEvents.checkout` 

407 the lower-level pool checkout event 

408 for an individual DBAPI connection 

409 

410 """ 

411 

412 def set_connection_execution_options( 

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

414 ) -> None: 

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

416 method is called. 

417 

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

419 has been 

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

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

422 

423 Note that this method is not called when a new 

424 :class:`_engine.Connection` 

425 is produced which is inheriting execution options from its parent 

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

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

428 

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

430 

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

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

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

434 options which take effect. 

435 

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

437 in place. 

438 

439 

440 .. seealso:: 

441 

442 :meth:`_events.ConnectionEvents.set_engine_execution_options` 

443 - event 

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

445 is called. 

446 

447 

448 """ 

449 

450 def set_engine_execution_options( 

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

452 ) -> None: 

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

454 method is called. 

455 

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

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

458 That new 

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

460 A particular application of this 

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

462 event 

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

464 which will perform some per- 

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

466 

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

468 

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

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

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

472 options which take effect. 

473 

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

475 in place. 

476 

477 .. seealso:: 

478 

479 :meth:`_events.ConnectionEvents.set_connection_execution_options` 

480 - event 

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

482 is 

483 called. 

484 

485 """ 

486 

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

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

489 

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

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

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

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

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

495 

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

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

498 keeping in mind that the 

499 :class:`_engine.Engine` 

500 can still be used for new requests in which case 

501 it re-acquires connection resources. 

502 

503 """ 

504 

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

506 """Intercept begin() events. 

507 

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

509 

510 """ 

511 

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

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

514 :class:`.Transaction`. 

515 

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

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

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

519 To intercept this 

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

521 

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

523 

524 .. seealso:: 

525 

526 :meth:`_events.PoolEvents.reset` 

527 

528 """ 

529 

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

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

532 :class:`.Transaction`. 

533 

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

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

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

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

538 

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

540 """ 

541 

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

543 """Intercept savepoint() events. 

544 

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

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

547 

548 """ 

549 

550 def rollback_savepoint( 

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

552 ) -> None: 

553 """Intercept rollback_savepoint() events. 

554 

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

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

557 :param context: not used 

558 

559 """ 

560 # TODO: deprecate "context" 

561 

562 def release_savepoint( 

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

564 ) -> None: 

565 """Intercept release_savepoint() events. 

566 

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

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

569 :param context: not used 

570 

571 """ 

572 # TODO: deprecate "context" 

573 

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

575 """Intercept begin_twophase() events. 

576 

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

578 :param xid: two-phase XID identifier 

579 

580 """ 

581 

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

583 """Intercept prepare_twophase() events. 

584 

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

586 :param xid: two-phase XID identifier 

587 """ 

588 

589 def rollback_twophase( 

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

591 ) -> None: 

592 """Intercept rollback_twophase() events. 

593 

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

595 :param xid: two-phase XID identifier 

596 :param is_prepared: boolean, indicates if 

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

598 

599 """ 

600 

601 def commit_twophase( 

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

603 ) -> None: 

604 """Intercept commit_twophase() events. 

605 

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

607 :param xid: two-phase XID identifier 

608 :param is_prepared: boolean, indicates if 

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

610 

611 """ 

612 

613 

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

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

616 

617 These events allow direct instrumentation and replacement 

618 of key dialect functions which interact with the DBAPI. 

619 

620 .. note:: 

621 

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

623 and experimental. 

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

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

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

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

628 

629 .. seealso:: 

630 

631 :meth:`_events.ConnectionEvents.before_cursor_execute` 

632 

633 :meth:`_events.ConnectionEvents.before_execute` 

634 

635 :meth:`_events.ConnectionEvents.after_cursor_execute` 

636 

637 :meth:`_events.ConnectionEvents.after_execute` 

638 

639 """ 

640 

641 _target_class_doc = "SomeEngine" 

642 _dispatch_target = Dialect 

643 

644 @classmethod 

645 def _listen( 

646 cls, 

647 event_key: event._EventKey[Dialect], 

648 *, 

649 retval: bool = False, 

650 **kw: Any, 

651 ) -> None: 

652 target = event_key.dispatch_target 

653 

654 target._has_events = True 

655 event_key.base_listen() 

656 

657 @classmethod 

658 def _accept_with( 

659 cls, 

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

661 identifier: str, 

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

663 if isinstance(target, type): 

664 if issubclass(target, Engine): 

665 return Dialect 

666 elif issubclass(target, Dialect): 

667 return target 

668 elif isinstance(target, Engine): 

669 return target.dialect 

670 elif isinstance(target, Dialect): 

671 return target 

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

673 raise exc.InvalidRequestError( 

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

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

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

677 "not on a per-Connection basis." 

678 ) 

679 elif hasattr(target, "_no_async_engine_events"): 

680 target._no_async_engine_events() 

681 else: 

682 return None 

683 

684 def handle_error( 

685 self, exception_context: ExceptionContext 

686 ) -> Optional[BaseException]: 

687 r"""Intercept all exceptions processed by the 

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

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

690 

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

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

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

694 the "pre ping" operation configured with the 

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

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

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

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

699 supported. 

700 

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

702 within SQLAlchemy's statement invocation process, including 

703 encoding errors and other statement validation errors. Other areas 

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

705 result row fetching, cursor creation. 

706 

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

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

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

710 releases. 

711 

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

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

714 incompatibility, the sole argument received is an instance of 

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

716 representing detail about the exception. 

717 

718 Use cases supported by this hook include: 

719 

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

721 debugging purposes 

722 * Establishing whether a DBAPI connection error message indicates 

723 that the database connection needs to be reconnected, including 

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

725 * Establishing or disabling whether a connection or the owning 

726 connection pool is invalidated or expired in response to a 

727 specific exception 

728 * exception re-writing 

729 

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

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

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

733 this cursor subsequent to this hook being invoked. 

734 

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

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

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

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

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

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

741 not make use of disconnect codes. 

742 

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

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

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

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

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

748 

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

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

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

752 a connection pool pre-ping operation. 

753 

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

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

756 MySQL drivers, to properly participate in the 

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

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

759 implementation was non-working for these drivers. 

760 

761 

762 A handler function has two options for replacing 

763 the SQLAlchemy-constructed exception into one that is user 

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

765 which case all further event listeners are bypassed and the 

766 exception will be raised, after appropriate cleanup as taken 

767 place:: 

768 

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

770 def handle_exception(context): 

771 if isinstance(context.original_exception, 

772 psycopg2.OperationalError) and \ 

773 "failed" in str(context.original_exception): 

774 raise MySpecialException("failed operation") 

775 

776 .. warning:: Because the 

777 :meth:`_events.DialectEvents.handle_error` 

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

779 the ultimate exception raised by the failed statement, 

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

781 handler itself fails and throws an unexpected exception; 

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

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

784 logging and/or inline debugging if unexpected exceptions are 

785 occurring. 

786 

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

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

789 modifier and returning the new exception instance from the 

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

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

792 :attr:`.ExceptionContext.chained_exception`:: 

793 

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

795 def handle_exception(context): 

796 if context.chained_exception is not None and \ 

797 "special" in context.chained_exception.message: 

798 return MySpecialException("failed", 

799 cause=context.chained_exception) 

800 

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

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

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

804 next handler. 

805 

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

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

808 object. If the exception is not a subclass of 

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

810 certain features may not be available; currently this includes 

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

812 exceptions raised within the autoflush process. 

813 

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

815 class for details on all available members. 

816 

817 

818 .. seealso:: 

819 

820 :ref:`pool_new_disconnect_codes` 

821 

822 """ 

823 

824 def do_connect( 

825 self, 

826 dialect: Dialect, 

827 conn_rec: ConnectionPoolEntry, 

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

829 cparams: Dict[str, Any], 

830 ) -> Optional[DBAPIConnection]: 

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

832 

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

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

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

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

837 dictionary that may also be mutated:: 

838 

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

840 

841 @event.listens_for(e, 'do_connect') 

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

843 cparams["password"] = "some_password" 

844 

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

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

847 

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

849 

850 @event.listens_for(e, 'do_connect') 

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

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

853 

854 .. seealso:: 

855 

856 :ref:`custom_dbapi_args` 

857 

858 """ 

859 

860 def do_executemany( 

861 self, 

862 cursor: DBAPICursor, 

863 statement: str, 

864 parameters: _DBAPIMultiExecuteParams, 

865 context: ExecutionContext, 

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

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

868 

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

870 and to indicate that the cursor execution has already taken 

871 place within the event handler. 

872 

873 """ 

874 

875 def do_execute_no_params( 

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

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

878 """Receive a cursor to have execute() with no parameters 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( 

887 self, 

888 cursor: DBAPICursor, 

889 statement: str, 

890 parameters: _DBAPISingleExecuteParams, 

891 context: ExecutionContext, 

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

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

894 

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

896 and to indicate that the cursor execution has already taken 

897 place within the event handler. 

898 

899 """ 

900 

901 def do_setinputsizes( 

902 self, 

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

904 cursor: DBAPICursor, 

905 statement: str, 

906 parameters: _DBAPIAnyExecuteParams, 

907 context: ExecutionContext, 

908 ) -> None: 

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

910 

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

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

913 parameter binding for a particular statement. The given 

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

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

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

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

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

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

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

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

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

923 order to make decisions about the DBAPI object. 

924 

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

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

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

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

929 a named bound parameter execution style. 

930 

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

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

933 include cx_Oracle, pg8000, asyncpg, and pyodbc dialects. 

934 

935 .. note:: 

936 

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

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

939 

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

941 

942 .. seealso:: 

943 

944 :ref:`mssql_pyodbc_setinputsizes` 

945 

946 .. versionadded:: 1.2.9 

947 

948 .. seealso:: 

949 

950 :ref:`cx_oracle_setinputsizes` 

951 

952 """ 

953 pass