Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/SQLAlchemy-1.3.25.dev0-py3.11-linux-x86_64.egg/sqlalchemy/pool/base.py: 55%

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

374 statements  

1# sqlalchemy/pool.py 

2# Copyright (C) 2005-2021 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: http://www.opensource.org/licenses/mit-license.php 

7 

8 

9"""Base constructs for connection pools. 

10 

11""" 

12 

13from collections import deque 

14import time 

15import weakref 

16 

17from .. import event 

18from .. import exc 

19from .. import interfaces 

20from .. import log 

21from .. import util 

22from ..util import threading 

23 

24 

25reset_rollback = util.symbol("reset_rollback") 

26reset_commit = util.symbol("reset_commit") 

27reset_none = util.symbol("reset_none") 

28 

29 

30class _ConnDialect(object): 

31 

32 """partial implementation of :class:`.Dialect` 

33 which provides DBAPI connection methods. 

34 

35 When a :class:`_pool.Pool` is combined with an :class:`_engine.Engine`, 

36 the :class:`_engine.Engine` replaces this with its own 

37 :class:`.Dialect`. 

38 

39 """ 

40 

41 def do_rollback(self, dbapi_connection): 

42 dbapi_connection.rollback() 

43 

44 def do_commit(self, dbapi_connection): 

45 dbapi_connection.commit() 

46 

47 def do_close(self, dbapi_connection): 

48 dbapi_connection.close() 

49 

50 def do_ping(self, dbapi_connection): 

51 raise NotImplementedError( 

52 "The ping feature requires that a dialect is " 

53 "passed to the connection pool." 

54 ) 

55 

56 

57class Pool(log.Identified): 

58 

59 """Abstract base class for connection pools.""" 

60 

61 _dialect = _ConnDialect() 

62 

63 @util.deprecated_params( 

64 use_threadlocal=( 

65 "1.3", 

66 "The :paramref:`_pool.Pool.use_threadlocal` parameter is " 

67 "deprecated and will be removed in a future release.", 

68 ), 

69 listeners=( 

70 "0.7", 

71 ":class:`.PoolListener` is deprecated in favor of the " 

72 ":class:`_events.PoolEvents` listener interface. The " 

73 ":paramref:`_pool.Pool.listeners` parameter will be removed in a " 

74 "future release.", 

75 ), 

76 ) 

77 def __init__( 

78 self, 

79 creator, 

80 recycle=-1, 

81 echo=None, 

82 use_threadlocal=False, 

83 logging_name=None, 

84 reset_on_return=True, 

85 listeners=None, 

86 events=None, 

87 dialect=None, 

88 pre_ping=False, 

89 _dispatch=None, 

90 ): 

91 """ 

92 Construct a Pool. 

93 

94 :param creator: a callable function that returns a DB-API 

95 connection object. The function will be called with 

96 parameters. 

97 

98 :param recycle: If set to a value other than -1, number of 

99 seconds between connection recycling, which means upon 

100 checkout, if this timeout is surpassed the connection will be 

101 closed and replaced with a newly opened connection. Defaults to -1. 

102 

103 :param logging_name: String identifier which will be used within 

104 the "name" field of logging records generated within the 

105 "sqlalchemy.pool" logger. Defaults to a hexstring of the object's 

106 id. 

107 

108 :param echo: if True, the connection pool will log 

109 informational output such as when connections are invalidated 

110 as well as when connections are recycled to the default log handler, 

111 which defaults to ``sys.stdout`` for output.. If set to the string 

112 ``"debug"``, the logging will include pool checkouts and checkins. 

113 

114 The :paramref:`_pool.Pool.echo` parameter can also be set from the 

115 :func:`_sa.create_engine` call by using the 

116 :paramref:`_sa.create_engine.echo_pool` parameter. 

117 

118 .. seealso:: 

119 

120 :ref:`dbengine_logging` - further detail on how to configure 

121 logging. 

122 

123 :param use_threadlocal: If set to True, repeated calls to 

124 :meth:`connect` within the same application thread will be 

125 guaranteed to return the same connection object that is already 

126 checked out. This is a legacy use case and the flag has no 

127 effect when using the pool with a :class:`_engine.Engine` object. 

128 

129 :param reset_on_return: Determine steps to take on 

130 connections as they are returned to the pool. 

131 reset_on_return can have any of these values: 

132 

133 * ``"rollback"`` - call rollback() on the connection, 

134 to release locks and transaction resources. 

135 This is the default value. The vast majority 

136 of use cases should leave this value set. 

137 * ``True`` - same as 'rollback', this is here for 

138 backwards compatibility. 

139 * ``"commit"`` - call commit() on the connection, 

140 to release locks and transaction resources. 

141 A commit here may be desirable for databases that 

142 cache query plans if a commit is emitted, 

143 such as Microsoft SQL Server. However, this 

144 value is more dangerous than 'rollback' because 

145 any data changes present on the transaction 

146 are committed unconditionally. 

147 * ``None`` - don't do anything on the connection. 

148 This setting should generally only be made on a database 

149 that has no transaction support at all, 

150 namely MySQL MyISAM; when used on this backend, performance 

151 can be improved as the "rollback" call is still expensive on 

152 MySQL. It is **strongly recommended** that this setting not be 

153 used for transaction-supporting databases in conjunction with 

154 a persistent pool such as :class:`.QueuePool`, as it opens 

155 the possibility for connections still in a transaction to be 

156 idle in the pool. The setting may be appropriate in the 

157 case of :class:`.NullPool` or special circumstances where 

158 the connection pool in use is not being used to maintain connection 

159 lifecycle. 

160 

161 * ``False`` - same as None, this is here for 

162 backwards compatibility. 

163 

164 :param events: a list of 2-tuples, each of the form 

165 ``(callable, target)`` which will be passed to :func:`.event.listen` 

166 upon construction. Provided here so that event listeners 

167 can be assigned via :func:`_sa.create_engine` before dialect-level 

168 listeners are applied. 

169 

170 :param listeners: A list of :class:`.PoolListener`-like objects or 

171 dictionaries of callables that receive events when DB-API 

172 connections are created, checked out and checked in to the 

173 pool. 

174 

175 :param dialect: a :class:`.Dialect` that will handle the job 

176 of calling rollback(), close(), or commit() on DBAPI connections. 

177 If omitted, a built-in "stub" dialect is used. Applications that 

178 make use of :func:`_sa.create_engine` should not use this parameter 

179 as it is handled by the engine creation strategy. 

180 

181 .. versionadded:: 1.1 - ``dialect`` is now a public parameter 

182 to the :class:`_pool.Pool`. 

183 

184 :param pre_ping: if True, the pool will emit a "ping" (typically 

185 "SELECT 1", but is dialect-specific) on the connection 

186 upon checkout, to test if the connection is alive or not. If not, 

187 the connection is transparently re-connected and upon success, all 

188 other pooled connections established prior to that timestamp are 

189 invalidated. Requires that a dialect is passed as well to 

190 interpret the disconnection error. 

191 

192 .. versionadded:: 1.2 

193 

194 """ 

195 if logging_name: 

196 self.logging_name = self._orig_logging_name = logging_name 

197 else: 

198 self._orig_logging_name = None 

199 

200 log.instance_logger(self, echoflag=echo) 

201 self._threadconns = threading.local() 

202 self._creator = creator 

203 self._recycle = recycle 

204 self._invalidate_time = 0 

205 self._use_threadlocal = use_threadlocal 

206 self._pre_ping = pre_ping 

207 self._reset_on_return = util.symbol.parse_user_argument( 

208 reset_on_return, 

209 { 

210 reset_rollback: ["rollback", True], 

211 reset_none: ["none", None, False], 

212 reset_commit: ["commit"], 

213 }, 

214 "reset_on_return", 

215 resolve_symbol_names=False, 

216 ) 

217 

218 self.echo = echo 

219 

220 if _dispatch: 

221 self.dispatch._update(_dispatch, only_propagate=False) 

222 if dialect: 

223 self._dialect = dialect 

224 if events: 

225 for fn, target in events: 

226 event.listen(self, target, fn) 

227 if listeners: 

228 for l in listeners: 

229 self.add_listener(l) 

230 

231 @property 

232 def _creator(self): 

233 return self.__dict__["_creator"] 

234 

235 @_creator.setter 

236 def _creator(self, creator): 

237 self.__dict__["_creator"] = creator 

238 self._invoke_creator = self._should_wrap_creator(creator) 

239 

240 def _should_wrap_creator(self, creator): 

241 """Detect if creator accepts a single argument, or is sent 

242 as a legacy style no-arg function. 

243 

244 """ 

245 

246 try: 

247 argspec = util.get_callable_argspec(self._creator, no_self=True) 

248 except TypeError: 

249 return lambda crec: creator() 

250 

251 defaulted = argspec[3] is not None and len(argspec[3]) or 0 

252 positionals = len(argspec[0]) - defaulted 

253 

254 # look for the exact arg signature that DefaultStrategy 

255 # sends us 

256 if (argspec[0], argspec[3]) == (["connection_record"], (None,)): 

257 return creator 

258 # or just a single positional 

259 elif positionals == 1: 

260 return creator 

261 # all other cases, just wrap and assume legacy "creator" callable 

262 # thing 

263 else: 

264 return lambda crec: creator() 

265 

266 def _close_connection(self, connection): 

267 self.logger.debug("Closing connection %r", connection) 

268 

269 try: 

270 self._dialect.do_close(connection) 

271 except Exception: 

272 self.logger.error( 

273 "Exception closing connection %r", connection, exc_info=True 

274 ) 

275 

276 @util.deprecated( 

277 "0.7", 

278 "The :meth:`_pool.Pool.add_listener` method is deprecated and " 

279 "will be removed in a future release. Please use the " 

280 ":class:`_events.PoolEvents` listener interface.", 

281 ) 

282 def add_listener(self, listener): 

283 """Add a :class:`.PoolListener`-like object to this pool. 

284 

285 ``listener`` may be an object that implements some or all of 

286 PoolListener, or a dictionary of callables containing implementations 

287 of some or all of the named methods in PoolListener. 

288 

289 """ 

290 interfaces.PoolListener._adapt_listener(self, listener) 

291 

292 def unique_connection(self): 

293 """Produce a DBAPI connection that is not referenced by any 

294 thread-local context. 

295 

296 This method is equivalent to :meth:`_pool.Pool.connect` when the 

297 :paramref:`_pool.Pool.use_threadlocal` flag is not set to True. 

298 When :paramref:`_pool.Pool.use_threadlocal` is True, the 

299 :meth:`_pool.Pool.unique_connection` 

300 method provides a means of bypassing 

301 the threadlocal context. 

302 

303 """ 

304 return _ConnectionFairy._checkout(self) 

305 

306 def _create_connection(self): 

307 """Called by subclasses to create a new ConnectionRecord.""" 

308 

309 return _ConnectionRecord(self) 

310 

311 def _invalidate(self, connection, exception=None, _checkin=True): 

312 """Mark all connections established within the generation 

313 of the given connection as invalidated. 

314 

315 If this pool's last invalidate time is before when the given 

316 connection was created, update the timestamp til now. Otherwise, 

317 no action is performed. 

318 

319 Connections with a start time prior to this pool's invalidation 

320 time will be recycled upon next checkout. 

321 """ 

322 rec = getattr(connection, "_connection_record", None) 

323 if not rec or self._invalidate_time < rec.starttime: 

324 self._invalidate_time = time.time() 

325 if _checkin and getattr(connection, "is_valid", False): 

326 connection.invalidate(exception) 

327 

328 def recreate(self): 

329 """Return a new :class:`_pool.Pool`, of the same class as this one 

330 and configured with identical creation arguments. 

331 

332 This method is used in conjunction with :meth:`dispose` 

333 to close out an entire :class:`_pool.Pool` and create a new one in 

334 its place. 

335 

336 """ 

337 

338 raise NotImplementedError() 

339 

340 def dispose(self): 

341 """Dispose of this pool. 

342 

343 This method leaves the possibility of checked-out connections 

344 remaining open, as it only affects connections that are 

345 idle in the pool. 

346 

347 .. seealso:: 

348 

349 :meth:`Pool.recreate` 

350 

351 """ 

352 

353 raise NotImplementedError() 

354 

355 def connect(self): 

356 """Return a DBAPI connection from the pool. 

357 

358 The connection is instrumented such that when its 

359 ``close()`` method is called, the connection will be returned to 

360 the pool. 

361 

362 """ 

363 if not self._use_threadlocal: 

364 return _ConnectionFairy._checkout(self) 

365 

366 try: 

367 rec = self._threadconns.current() 

368 except AttributeError: 

369 pass 

370 else: 

371 if rec is not None: 

372 return rec._checkout_existing() 

373 

374 return _ConnectionFairy._checkout(self, self._threadconns) 

375 

376 def _return_conn(self, record): 

377 """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`. 

378 

379 This method is called when an instrumented DBAPI connection 

380 has its ``close()`` method called. 

381 

382 """ 

383 if self._use_threadlocal: 

384 try: 

385 del self._threadconns.current 

386 except AttributeError: 

387 pass 

388 self._do_return_conn(record) 

389 

390 def _do_get(self): 

391 """Implementation for :meth:`get`, supplied by subclasses.""" 

392 

393 raise NotImplementedError() 

394 

395 def _do_return_conn(self, conn): 

396 """Implementation for :meth:`return_conn`, supplied by subclasses.""" 

397 

398 raise NotImplementedError() 

399 

400 def status(self): 

401 raise NotImplementedError() 

402 

403 

404class _ConnectionRecord(object): 

405 

406 """Internal object which maintains an individual DBAPI connection 

407 referenced by a :class:`_pool.Pool`. 

408 

409 The :class:`._ConnectionRecord` object always exists for any particular 

410 DBAPI connection whether or not that DBAPI connection has been 

411 "checked out". This is in contrast to the :class:`._ConnectionFairy` 

412 which is only a public facade to the DBAPI connection while it is checked 

413 out. 

414 

415 A :class:`._ConnectionRecord` may exist for a span longer than that 

416 of a single DBAPI connection. For example, if the 

417 :meth:`._ConnectionRecord.invalidate` 

418 method is called, the DBAPI connection associated with this 

419 :class:`._ConnectionRecord` 

420 will be discarded, but the :class:`._ConnectionRecord` may be used again, 

421 in which case a new DBAPI connection is produced when the 

422 :class:`_pool.Pool` 

423 next uses this record. 

424 

425 The :class:`._ConnectionRecord` is delivered along with connection 

426 pool events, including :meth:`_events.PoolEvents.connect` and 

427 :meth:`_events.PoolEvents.checkout`, however :class:`._ConnectionRecord` 

428 still 

429 remains an internal object whose API and internals may change. 

430 

431 .. seealso:: 

432 

433 :class:`._ConnectionFairy` 

434 

435 """ 

436 

437 def __init__(self, pool, connect=True): 

438 self.__pool = pool 

439 if connect: 

440 self.__connect(first_connect_check=True) 

441 self.finalize_callback = deque() 

442 

443 fairy_ref = None 

444 

445 starttime = None 

446 

447 connection = None 

448 """A reference to the actual DBAPI connection being tracked. 

449 

450 May be ``None`` if this :class:`._ConnectionRecord` has been marked 

451 as invalidated; a new DBAPI connection may replace it if the owning 

452 pool calls upon this :class:`._ConnectionRecord` to reconnect. 

453 

454 """ 

455 

456 _soft_invalidate_time = 0 

457 

458 @util.memoized_property 

459 def info(self): 

460 """The ``.info`` dictionary associated with the DBAPI connection. 

461 

462 This dictionary is shared among the :attr:`._ConnectionFairy.info` 

463 and :attr:`_engine.Connection.info` accessors. 

464 

465 .. note:: 

466 

467 The lifespan of this dictionary is linked to the 

468 DBAPI connection itself, meaning that it is **discarded** each time 

469 the DBAPI connection is closed and/or invalidated. The 

470 :attr:`._ConnectionRecord.record_info` dictionary remains 

471 persistent throughout the lifespan of the 

472 :class:`._ConnectionRecord` container. 

473 

474 """ 

475 return {} 

476 

477 @util.memoized_property 

478 def record_info(self): 

479 """An "info' dictionary associated with the connection record 

480 itself. 

481 

482 Unlike the :attr:`._ConnectionRecord.info` dictionary, which is linked 

483 to the lifespan of the DBAPI connection, this dictionary is linked 

484 to the lifespan of the :class:`._ConnectionRecord` container itself 

485 and will remain persistent throughout the life of the 

486 :class:`._ConnectionRecord`. 

487 

488 .. versionadded:: 1.1 

489 

490 """ 

491 return {} 

492 

493 @classmethod 

494 def checkout(cls, pool): 

495 rec = pool._do_get() 

496 try: 

497 dbapi_connection = rec.get_connection() 

498 except Exception as err: 

499 with util.safe_reraise(): 

500 rec._checkin_failed(err) 

501 echo = pool._should_log_debug() 

502 fairy = _ConnectionFairy(dbapi_connection, rec, echo) 

503 rec.fairy_ref = weakref.ref( 

504 fairy, 

505 lambda ref: _finalize_fairy 

506 and _finalize_fairy(None, rec, pool, ref, echo), 

507 ) 

508 _refs.add(rec) 

509 if echo: 

510 pool.logger.debug( 

511 "Connection %r checked out from pool", dbapi_connection 

512 ) 

513 return fairy 

514 

515 def _checkin_failed(self, err): 

516 self.invalidate(e=err) 

517 self.checkin(_no_fairy_ref=True) 

518 

519 def checkin(self, _no_fairy_ref=False): 

520 if self.fairy_ref is None and not _no_fairy_ref: 

521 util.warn("Double checkin attempted on %s" % self) 

522 return 

523 self.fairy_ref = None 

524 connection = self.connection 

525 pool = self.__pool 

526 while self.finalize_callback: 

527 finalizer = self.finalize_callback.pop() 

528 finalizer(connection) 

529 if pool.dispatch.checkin: 

530 pool.dispatch.checkin(connection, self) 

531 pool._return_conn(self) 

532 

533 @property 

534 def in_use(self): 

535 return self.fairy_ref is not None 

536 

537 @property 

538 def last_connect_time(self): 

539 return self.starttime 

540 

541 def close(self): 

542 if self.connection is not None: 

543 self.__close() 

544 

545 def invalidate(self, e=None, soft=False): 

546 """Invalidate the DBAPI connection held by this :class:`._ConnectionRecord`. 

547 

548 This method is called for all connection invalidations, including 

549 when the :meth:`._ConnectionFairy.invalidate` or 

550 :meth:`_engine.Connection.invalidate` methods are called, 

551 as well as when any 

552 so-called "automatic invalidation" condition occurs. 

553 

554 :param e: an exception object indicating a reason for the invalidation. 

555 

556 :param soft: if True, the connection isn't closed; instead, this 

557 connection will be recycled on next checkout. 

558 

559 .. versionadded:: 1.0.3 

560 

561 .. seealso:: 

562 

563 :ref:`pool_connection_invalidation` 

564 

565 """ 

566 # already invalidated 

567 if self.connection is None: 

568 return 

569 if soft: 

570 self.__pool.dispatch.soft_invalidate(self.connection, self, e) 

571 else: 

572 self.__pool.dispatch.invalidate(self.connection, self, e) 

573 if e is not None: 

574 self.__pool.logger.info( 

575 "%sInvalidate connection %r (reason: %s:%s)", 

576 "Soft " if soft else "", 

577 self.connection, 

578 e.__class__.__name__, 

579 e, 

580 ) 

581 else: 

582 self.__pool.logger.info( 

583 "%sInvalidate connection %r", 

584 "Soft " if soft else "", 

585 self.connection, 

586 ) 

587 if soft: 

588 self._soft_invalidate_time = time.time() 

589 else: 

590 self.__close() 

591 self.connection = None 

592 

593 def get_connection(self): 

594 recycle = False 

595 

596 # NOTE: the various comparisons here are assuming that measurable time 

597 # passes between these state changes. however, time.time() is not 

598 # guaranteed to have sub-second precision. comparisons of 

599 # "invalidation time" to "starttime" should perhaps use >= so that the 

600 # state change can take place assuming no measurable time has passed, 

601 # however this does not guarantee correct behavior here as if time 

602 # continues to not pass, it will try to reconnect repeatedly until 

603 # these timestamps diverge, so in that sense using > is safer. Per 

604 # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be 

605 # within 16 milliseconds accuracy, so unit tests for connection 

606 # invalidation need a sleep of at least this long between initial start 

607 # time and invalidation for the logic below to work reliably. 

608 if self.connection is None: 

609 self.info.clear() 

610 self.__connect() 

611 elif ( 

612 self.__pool._recycle > -1 

613 and time.time() - self.starttime > self.__pool._recycle 

614 ): 

615 self.__pool.logger.info( 

616 "Connection %r exceeded timeout; recycling", self.connection 

617 ) 

618 recycle = True 

619 elif self.__pool._invalidate_time > self.starttime: 

620 self.__pool.logger.info( 

621 "Connection %r invalidated due to pool invalidation; " 

622 + "recycling", 

623 self.connection, 

624 ) 

625 recycle = True 

626 elif self._soft_invalidate_time > self.starttime: 

627 self.__pool.logger.info( 

628 "Connection %r invalidated due to local soft invalidation; " 

629 + "recycling", 

630 self.connection, 

631 ) 

632 recycle = True 

633 

634 if recycle: 

635 self.__close() 

636 self.info.clear() 

637 

638 self.__connect() 

639 return self.connection 

640 

641 def __close(self): 

642 self.finalize_callback.clear() 

643 if self.__pool.dispatch.close: 

644 self.__pool.dispatch.close(self.connection, self) 

645 self.__pool._close_connection(self.connection) 

646 self.connection = None 

647 

648 def __connect(self, first_connect_check=False): 

649 pool = self.__pool 

650 

651 # ensure any existing connection is removed, so that if 

652 # creator fails, this attribute stays None 

653 self.connection = None 

654 try: 

655 self.starttime = time.time() 

656 connection = pool._invoke_creator(self) 

657 pool.logger.debug("Created new connection %r", connection) 

658 self.connection = connection 

659 except Exception as e: 

660 with util.safe_reraise(): 

661 pool.logger.debug("Error on connect(): %s", e) 

662 else: 

663 if first_connect_check: 

664 pool.dispatch.first_connect.for_modify( 

665 pool.dispatch 

666 ).exec_once_unless_exception(self.connection, self) 

667 if pool.dispatch.connect: 

668 pool.dispatch.connect(self.connection, self) 

669 

670 

671def _finalize_fairy( 

672 connection, connection_record, pool, ref, echo, fairy=None 

673): 

674 """Cleanup for a :class:`._ConnectionFairy` whether or not it's already 

675 been garbage collected. 

676 

677 """ 

678 _refs.discard(connection_record) 

679 

680 if ref is not None: 

681 if connection_record.fairy_ref is not ref: 

682 return 

683 assert connection is None 

684 connection = connection_record.connection 

685 

686 if connection is not None: 

687 if connection_record and echo: 

688 pool.logger.debug( 

689 "Connection %r being returned to pool", connection 

690 ) 

691 

692 try: 

693 fairy = fairy or _ConnectionFairy( 

694 connection, connection_record, echo 

695 ) 

696 assert fairy.connection is connection 

697 fairy._reset(pool) 

698 

699 # Immediately close detached instances 

700 if not connection_record: 

701 if pool.dispatch.close_detached: 

702 pool.dispatch.close_detached(connection) 

703 pool._close_connection(connection) 

704 except BaseException as e: 

705 pool.logger.error( 

706 "Exception during reset or similar", exc_info=True 

707 ) 

708 if connection_record: 

709 connection_record.invalidate(e=e) 

710 if not isinstance(e, Exception): 

711 raise 

712 

713 if connection_record and connection_record.fairy_ref is not None: 

714 connection_record.checkin() 

715 

716 

717_refs = set() 

718 

719 

720class _ConnectionFairy(object): 

721 

722 """Proxies a DBAPI connection and provides return-on-dereference 

723 support. 

724 

725 This is an internal object used by the :class:`_pool.Pool` implementation 

726 to provide context management to a DBAPI connection delivered by 

727 that :class:`_pool.Pool`. 

728 

729 The name "fairy" is inspired by the fact that the 

730 :class:`._ConnectionFairy` object's lifespan is transitory, as it lasts 

731 only for the length of a specific DBAPI connection being checked out from 

732 the pool, and additionally that as a transparent proxy, it is mostly 

733 invisible. 

734 

735 .. seealso:: 

736 

737 :class:`._ConnectionRecord` 

738 

739 """ 

740 

741 def __init__(self, dbapi_connection, connection_record, echo): 

742 self.connection = dbapi_connection 

743 self._connection_record = connection_record 

744 self._echo = echo 

745 

746 connection = None 

747 """A reference to the actual DBAPI connection being tracked.""" 

748 

749 _connection_record = None 

750 """A reference to the :class:`._ConnectionRecord` object associated 

751 with the DBAPI connection. 

752 

753 This is currently an internal accessor which is subject to change. 

754 

755 """ 

756 

757 _reset_agent = None 

758 """Refer to an object with a ``.commit()`` and ``.rollback()`` method; 

759 if non-None, the "reset-on-return" feature will call upon this object 

760 rather than directly against the dialect-level do_rollback() and 

761 do_commit() methods. 

762 

763 In practice, a :class:`_engine.Connection` assigns a :class:`.Transaction` 

764 object 

765 to this variable when one is in scope so that the :class:`.Transaction` 

766 takes the job of committing or rolling back on return if 

767 :meth:`_engine.Connection.close` is called while the :class:`.Transaction` 

768 still exists. 

769 

770 This is essentially an "event handler" of sorts but is simplified as an 

771 instance variable both for performance/simplicity as well as that there 

772 can only be one "reset agent" at a time. 

773 """ 

774 

775 @classmethod 

776 def _checkout(cls, pool, threadconns=None, fairy=None): 

777 if not fairy: 

778 fairy = _ConnectionRecord.checkout(pool) 

779 

780 fairy._pool = pool 

781 fairy._counter = 0 

782 

783 if threadconns is not None: 

784 threadconns.current = weakref.ref(fairy) 

785 

786 if fairy.connection is None: 

787 raise exc.InvalidRequestError("This connection is closed") 

788 fairy._counter += 1 

789 

790 if ( 

791 not pool.dispatch.checkout and not pool._pre_ping 

792 ) or fairy._counter != 1: 

793 return fairy 

794 

795 # Pool listeners can trigger a reconnection on checkout, as well 

796 # as the pre-pinger. 

797 # there are three attempts made here, but note that if the database 

798 # is not accessible from a connection standpoint, those won't proceed 

799 # here. 

800 attempts = 2 

801 while attempts > 0: 

802 try: 

803 if pool._pre_ping: 

804 if fairy._echo: 

805 pool.logger.debug( 

806 "Pool pre-ping on connection %s", fairy.connection 

807 ) 

808 

809 result = pool._dialect.do_ping(fairy.connection) 

810 if not result: 

811 if fairy._echo: 

812 pool.logger.debug( 

813 "Pool pre-ping on connection %s failed, " 

814 "will invalidate pool", 

815 fairy.connection, 

816 ) 

817 raise exc.InvalidatePoolError() 

818 

819 pool.dispatch.checkout( 

820 fairy.connection, fairy._connection_record, fairy 

821 ) 

822 return fairy 

823 except exc.DisconnectionError as e: 

824 if e.invalidate_pool: 

825 pool.logger.info( 

826 "Disconnection detected on checkout, " 

827 "invalidating all pooled connections prior to " 

828 "current timestamp (reason: %r)", 

829 e, 

830 ) 

831 fairy._connection_record.invalidate(e) 

832 pool._invalidate(fairy, e, _checkin=False) 

833 else: 

834 pool.logger.info( 

835 "Disconnection detected on checkout, " 

836 "invalidating individual connection %s (reason: %r)", 

837 fairy.connection, 

838 e, 

839 ) 

840 fairy._connection_record.invalidate(e) 

841 try: 

842 fairy.connection = ( 

843 fairy._connection_record.get_connection() 

844 ) 

845 except Exception as err: 

846 with util.safe_reraise(): 

847 fairy._connection_record._checkin_failed(err) 

848 

849 attempts -= 1 

850 

851 pool.logger.info("Reconnection attempts exhausted on checkout") 

852 fairy.invalidate() 

853 raise exc.InvalidRequestError("This connection is closed") 

854 

855 def _checkout_existing(self): 

856 return _ConnectionFairy._checkout(self._pool, fairy=self) 

857 

858 def _checkin(self): 

859 _finalize_fairy( 

860 self.connection, 

861 self._connection_record, 

862 self._pool, 

863 None, 

864 self._echo, 

865 fairy=self, 

866 ) 

867 self.connection = None 

868 self._connection_record = None 

869 

870 _close = _checkin 

871 

872 def _reset(self, pool): 

873 if pool.dispatch.reset: 

874 pool.dispatch.reset(self, self._connection_record) 

875 if pool._reset_on_return is reset_rollback: 

876 if self._echo: 

877 pool.logger.debug( 

878 "Connection %s rollback-on-return%s", 

879 self.connection, 

880 ", via agent" if self._reset_agent else "", 

881 ) 

882 if self._reset_agent: 

883 if not self._reset_agent.is_active: 

884 util.warn( 

885 "Reset agent is not active. " 

886 "This should not occur unless there was already " 

887 "a connectivity error in progress." 

888 ) 

889 pool._dialect.do_rollback(self) 

890 else: 

891 self._reset_agent.rollback() 

892 else: 

893 pool._dialect.do_rollback(self) 

894 elif pool._reset_on_return is reset_commit: 

895 if self._echo: 

896 pool.logger.debug( 

897 "Connection %s commit-on-return%s", 

898 self.connection, 

899 ", via agent" if self._reset_agent else "", 

900 ) 

901 if self._reset_agent: 

902 if not self._reset_agent.is_active: 

903 util.warn( 

904 "Reset agent is not active. " 

905 "This should not occur unless there was already " 

906 "a connectivity error in progress." 

907 ) 

908 pool._dialect.do_commit(self) 

909 else: 

910 self._reset_agent.commit() 

911 else: 

912 pool._dialect.do_commit(self) 

913 

914 @property 

915 def _logger(self): 

916 return self._pool.logger 

917 

918 @property 

919 def is_valid(self): 

920 """Return True if this :class:`._ConnectionFairy` still refers 

921 to an active DBAPI connection.""" 

922 

923 return self.connection is not None 

924 

925 @util.memoized_property 

926 def info(self): 

927 """Info dictionary associated with the underlying DBAPI connection 

928 referred to by this :class:`.ConnectionFairy`, allowing user-defined 

929 data to be associated with the connection. 

930 

931 The data here will follow along with the DBAPI connection including 

932 after it is returned to the connection pool and used again 

933 in subsequent instances of :class:`._ConnectionFairy`. It is shared 

934 with the :attr:`._ConnectionRecord.info` and 

935 :attr:`_engine.Connection.info` 

936 accessors. 

937 

938 The dictionary associated with a particular DBAPI connection is 

939 discarded when the connection itself is discarded. 

940 

941 """ 

942 return self._connection_record.info 

943 

944 @property 

945 def record_info(self): 

946 """Info dictionary associated with the :class:`._ConnectionRecord 

947 container referred to by this :class:`.ConnectionFairy`. 

948 

949 Unlike the :attr:`._ConnectionFairy.info` dictionary, the lifespan 

950 of this dictionary is persistent across connections that are 

951 disconnected and/or invalidated within the lifespan of a 

952 :class:`._ConnectionRecord`. 

953 

954 .. versionadded:: 1.1 

955 

956 """ 

957 if self._connection_record: 

958 return self._connection_record.record_info 

959 else: 

960 return None 

961 

962 def invalidate(self, e=None, soft=False): 

963 """Mark this connection as invalidated. 

964 

965 This method can be called directly, and is also called as a result 

966 of the :meth:`_engine.Connection.invalidate` method. When invoked, 

967 the DBAPI connection is immediately closed and discarded from 

968 further use by the pool. The invalidation mechanism proceeds 

969 via the :meth:`._ConnectionRecord.invalidate` internal method. 

970 

971 :param e: an exception object indicating a reason for the invalidation. 

972 

973 :param soft: if True, the connection isn't closed; instead, this 

974 connection will be recycled on next checkout. 

975 

976 .. versionadded:: 1.0.3 

977 

978 .. seealso:: 

979 

980 :ref:`pool_connection_invalidation` 

981 

982 """ 

983 

984 if self.connection is None: 

985 util.warn("Can't invalidate an already-closed connection.") 

986 return 

987 if self._connection_record: 

988 self._connection_record.invalidate(e=e, soft=soft) 

989 if not soft: 

990 self.connection = None 

991 self._checkin() 

992 

993 def cursor(self, *args, **kwargs): 

994 """Return a new DBAPI cursor for the underlying connection. 

995 

996 This method is a proxy for the ``connection.cursor()`` DBAPI 

997 method. 

998 

999 """ 

1000 return self.connection.cursor(*args, **kwargs) 

1001 

1002 def __getattr__(self, key): 

1003 return getattr(self.connection, key) 

1004 

1005 def detach(self): 

1006 """Separate this connection from its Pool. 

1007 

1008 This means that the connection will no longer be returned to the 

1009 pool when closed, and will instead be literally closed. The 

1010 containing ConnectionRecord is separated from the DB-API connection, 

1011 and will create a new connection when next used. 

1012 

1013 Note that any overall connection limiting constraints imposed by a 

1014 Pool implementation may be violated after a detach, as the detached 

1015 connection is removed from the pool's knowledge and control. 

1016 """ 

1017 

1018 if self._connection_record is not None: 

1019 rec = self._connection_record 

1020 _refs.remove(rec) 

1021 rec.fairy_ref = None 

1022 rec.connection = None 

1023 # TODO: should this be _return_conn? 

1024 self._pool._do_return_conn(self._connection_record) 

1025 self.info = self.info.copy() 

1026 self._connection_record = None 

1027 

1028 if self._pool.dispatch.detach: 

1029 self._pool.dispatch.detach(self.connection, rec) 

1030 

1031 def close(self): 

1032 self._counter -= 1 

1033 if self._counter == 0: 

1034 self._checkin()