Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/pool/base.py: 32%

406 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1# sqlalchemy/pool.py 

2# Copyright (C) 2005-2023 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 

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 log 

20from .. import util 

21 

22 

23reset_rollback = util.symbol("reset_rollback") 

24reset_commit = util.symbol("reset_commit") 

25reset_none = util.symbol("reset_none") 

26 

27 

28class _ConnDialect(object): 

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

30 which provides DBAPI connection methods. 

31 

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

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

34 :class:`.Dialect`. 

35 

36 """ 

37 

38 is_async = False 

39 has_terminate = False 

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_terminate(self, dbapi_connection): 

48 dbapi_connection.close() 

49 

50 def do_close(self, dbapi_connection): 

51 dbapi_connection.close() 

52 

53 def do_ping(self, dbapi_connection): 

54 raise NotImplementedError( 

55 "The ping feature requires that a dialect is " 

56 "passed to the connection pool." 

57 ) 

58 

59 def get_driver_connection(self, connection): 

60 return connection 

61 

62 

63class _AsyncConnDialect(_ConnDialect): 

64 is_async = True 

65 

66 

67class Pool(log.Identified): 

68 

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

70 

71 _dialect = _ConnDialect() 

72 

73 def __init__( 

74 self, 

75 creator, 

76 recycle=-1, 

77 echo=None, 

78 logging_name=None, 

79 reset_on_return=True, 

80 events=None, 

81 dialect=None, 

82 pre_ping=False, 

83 _dispatch=None, 

84 ): 

85 """ 

86 Construct a Pool. 

87 

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

89 connection object. The function will be called with 

90 parameters. 

91 

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

93 seconds between connection recycling, which means upon 

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

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

96 

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

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

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

100 id. 

101 

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

103 informational output such as when connections are invalidated 

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

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

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

107 

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

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

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

111 

112 .. seealso:: 

113 

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

115 logging. 

116 

117 :param reset_on_return: Determine steps to take on 

118 connections as they are returned to the pool, which were 

119 not otherwise handled by a :class:`_engine.Connection`. 

120 Available from :func:`_sa.create_engine` via the 

121 :paramref:`_sa.create_engine.pool_reset_on_return` parameter. 

122 

123 :paramref:`_pool.Pool.reset_on_return` can have any of these values: 

124 

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

126 to release locks and transaction resources. 

127 This is the default value. The vast majority 

128 of use cases should leave this value set. 

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

130 to release locks and transaction resources. 

131 A commit here may be desirable for databases that 

132 cache query plans if a commit is emitted, 

133 such as Microsoft SQL Server. However, this 

134 value is more dangerous than 'rollback' because 

135 any data changes present on the transaction 

136 are committed unconditionally. 

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

138 This setting may be appropriate if the database / DBAPI 

139 works in pure "autocommit" mode at all times, or if 

140 a custom reset handler is established using the 

141 :meth:`.PoolEvents.reset` event handler. 

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

143 backwards compatibility. 

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

145 backwards compatibility. 

146 

147 For further customization of reset on return, the 

148 :meth:`.PoolEvents.reset` event hook may be used which can perform 

149 any connection activity desired on reset. (requires version 1.4.43 

150 or greater) 

151 

152 .. seealso:: 

153 

154 :ref:`pool_reset_on_return` 

155 

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

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

158 upon construction. Provided here so that event listeners 

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

160 listeners are applied. 

161 

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

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

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

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

166 as it is handled by the engine creation strategy. 

167 

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

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

170 

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

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

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

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

175 other pooled connections established prior to that timestamp are 

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

177 interpret the disconnection error. 

178 

179 .. versionadded:: 1.2 

180 

181 """ 

182 if logging_name: 

183 self.logging_name = self._orig_logging_name = logging_name 

184 else: 

185 self._orig_logging_name = None 

186 

187 log.instance_logger(self, echoflag=echo) 

188 self._creator = creator 

189 self._recycle = recycle 

190 self._invalidate_time = 0 

191 self._pre_ping = pre_ping 

192 self._reset_on_return = util.symbol.parse_user_argument( 

193 reset_on_return, 

194 { 

195 reset_rollback: ["rollback", True], 

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

197 reset_commit: ["commit"], 

198 }, 

199 "reset_on_return", 

200 resolve_symbol_names=False, 

201 ) 

202 

203 self.echo = echo 

204 

205 if _dispatch: 

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

207 if dialect: 

208 self._dialect = dialect 

209 if events: 

210 for fn, target in events: 

211 event.listen(self, target, fn) 

212 

213 @util.hybridproperty 

214 def _is_asyncio(self): 

215 return self._dialect.is_async 

216 

217 @property 

218 def _creator(self): 

219 return self.__dict__["_creator"] 

220 

221 @_creator.setter 

222 def _creator(self, creator): 

223 self.__dict__["_creator"] = creator 

224 self._invoke_creator = self._should_wrap_creator(creator) 

225 

226 def _should_wrap_creator(self, creator): 

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

228 as a legacy style no-arg function. 

229 

230 """ 

231 

232 try: 

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

234 except TypeError: 

235 return lambda crec: creator() 

236 

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

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

239 

240 # look for the exact arg signature that DefaultStrategy 

241 # sends us 

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

243 return creator 

244 # or just a single positional 

245 elif positionals == 1: 

246 return creator 

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

248 # thing 

249 else: 

250 return lambda crec: creator() 

251 

252 def _close_connection(self, connection, terminate=False): 

253 self.logger.debug( 

254 "%s connection %r", 

255 "Hard-closing" if terminate else "Closing", 

256 connection, 

257 ) 

258 try: 

259 if terminate: 

260 self._dialect.do_terminate(connection) 

261 else: 

262 self._dialect.do_close(connection) 

263 except BaseException as e: 

264 self.logger.error( 

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

266 ) 

267 if not isinstance(e, Exception): 

268 raise 

269 

270 def _create_connection(self): 

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

272 

273 return _ConnectionRecord(self) 

274 

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

276 """Mark all connections established within the generation 

277 of the given connection as invalidated. 

278 

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

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

281 no action is performed. 

282 

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

284 time will be recycled upon next checkout. 

285 """ 

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

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

288 self._invalidate_time = time.time() 

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

290 connection.invalidate(exception) 

291 

292 def recreate(self): 

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

294 and configured with identical creation arguments. 

295 

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

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

298 its place. 

299 

300 """ 

301 

302 raise NotImplementedError() 

303 

304 def dispose(self): 

305 """Dispose of this pool. 

306 

307 This method leaves the possibility of checked-out connections 

308 remaining open, as it only affects connections that are 

309 idle in the pool. 

310 

311 .. seealso:: 

312 

313 :meth:`Pool.recreate` 

314 

315 """ 

316 

317 raise NotImplementedError() 

318 

319 def connect(self): 

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

321 

322 The connection is instrumented such that when its 

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

324 the pool. 

325 

326 """ 

327 return _ConnectionFairy._checkout(self) 

328 

329 def _return_conn(self, record): 

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

331 

332 This method is called when an instrumented DBAPI connection 

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

334 

335 """ 

336 self._do_return_conn(record) 

337 

338 def _do_get(self): 

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

340 

341 raise NotImplementedError() 

342 

343 def _do_return_conn(self, conn): 

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

345 

346 raise NotImplementedError() 

347 

348 def status(self): 

349 raise NotImplementedError() 

350 

351 

352class _ConnectionRecord(object): 

353 

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

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

356 

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

358 DBAPI connection whether or not that DBAPI connection has been 

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

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

361 out. 

362 

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

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

365 :meth:`._ConnectionRecord.invalidate` 

366 method is called, the DBAPI connection associated with this 

367 :class:`._ConnectionRecord` 

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

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

370 :class:`_pool.Pool` 

371 next uses this record. 

372 

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

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

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

376 still 

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

378 

379 .. seealso:: 

380 

381 :class:`._ConnectionFairy` 

382 

383 """ 

384 

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

386 self.__pool = pool 

387 if connect: 

388 self.__connect() 

389 self.finalize_callback = deque() 

390 

391 fresh = False 

392 

393 fairy_ref = None 

394 

395 starttime = None 

396 

397 dbapi_connection = None 

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

399 

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

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

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

403 

404 For adapted drivers, like the Asyncio implementations, this is a 

405 :class:`.AdaptedConnection` that adapts the driver connection 

406 to the DBAPI protocol. 

407 Use :attr:`._ConnectionRecord.driver_connection` to obtain the 

408 connection objected returned by the driver. 

409 

410 .. versionadded:: 1.4.24 

411 

412 """ 

413 

414 @property 

415 def driver_connection(self): 

416 """The connection object as returned by the driver after a connect. 

417 

418 For normal sync drivers that support the DBAPI protocol, this object 

419 is the same as the one referenced by 

420 :attr:`._ConnectionRecord.dbapi_connection`. 

421 

422 For adapted drivers, like the Asyncio ones, this is the actual object 

423 that was returned by the driver ``connect`` call. 

424 

425 As :attr:`._ConnectionRecord.dbapi_connection` it may be ``None`` 

426 if this :class:`._ConnectionRecord` has been marked as invalidated. 

427 

428 .. versionadded:: 1.4.24 

429 

430 """ 

431 

432 if self.dbapi_connection is None: 

433 return None 

434 else: 

435 return self.__pool._dialect.get_driver_connection( 

436 self.dbapi_connection 

437 ) 

438 

439 @property 

440 def connection(self): 

441 """An alias to :attr:`._ConnectionRecord.dbapi_connection`. 

442 

443 This alias is deprecated, please use the new name. 

444 

445 .. deprecated:: 1.4.24 

446 

447 """ 

448 return self.dbapi_connection 

449 

450 @connection.setter 

451 def connection(self, value): 

452 self.dbapi_connection = value 

453 

454 _soft_invalidate_time = 0 

455 

456 @util.memoized_property 

457 def info(self): 

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

459 

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

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

462 

463 .. note:: 

464 

465 The lifespan of this dictionary is linked to the 

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

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

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

469 persistent throughout the lifespan of the 

470 :class:`._ConnectionRecord` container. 

471 

472 """ 

473 return {} 

474 

475 @util.memoized_property 

476 def record_info(self): 

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

478 itself. 

479 

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

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

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

483 and will remain persistent throughout the life of the 

484 :class:`._ConnectionRecord`. 

485 

486 .. versionadded:: 1.1 

487 

488 """ 

489 return {} 

490 

491 @classmethod 

492 def checkout(cls, pool): 

493 rec = pool._do_get() 

494 try: 

495 dbapi_connection = rec.get_connection() 

496 except BaseException as err: 

497 with util.safe_reraise(): 

498 rec._checkin_failed(err, _fairy_was_created=False) 

499 

500 # never called, this is for code linters 

501 raise 

502 

503 echo = pool._should_log_debug() 

504 fairy = _ConnectionFairy(dbapi_connection, rec, echo) 

505 

506 rec.fairy_ref = ref = weakref.ref( 

507 fairy, 

508 lambda ref: _finalize_fairy 

509 and _finalize_fairy( 

510 None, rec, pool, ref, echo, transaction_was_reset=False 

511 ), 

512 ) 

513 _strong_ref_connection_records[ref] = rec 

514 if echo: 

515 pool.logger.debug( 

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

517 ) 

518 return fairy 

519 

520 def _checkin_failed(self, err, _fairy_was_created=True): 

521 self.invalidate(e=err) 

522 self.checkin( 

523 _fairy_was_created=_fairy_was_created, 

524 ) 

525 

526 def checkin(self, _fairy_was_created=True): 

527 if self.fairy_ref is None and _fairy_was_created: 

528 # _fairy_was_created is False for the initial get connection phase; 

529 # meaning there was no _ConnectionFairy and we must unconditionally 

530 # do a checkin. 

531 # 

532 # otherwise, if fairy_was_created==True, if fairy_ref is None here 

533 # that means we were checked in already, so this looks like 

534 # a double checkin. 

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

536 return 

537 self.fairy_ref = None 

538 connection = self.dbapi_connection 

539 pool = self.__pool 

540 while self.finalize_callback: 

541 finalizer = self.finalize_callback.pop() 

542 finalizer(connection) 

543 if pool.dispatch.checkin: 

544 pool.dispatch.checkin(connection, self) 

545 

546 pool._return_conn(self) 

547 

548 @property 

549 def in_use(self): 

550 return self.fairy_ref is not None 

551 

552 @property 

553 def last_connect_time(self): 

554 return self.starttime 

555 

556 def close(self): 

557 if self.dbapi_connection is not None: 

558 self.__close() 

559 

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

561 """Invalidate the DBAPI connection held by this 

562 :class:`._ConnectionRecord`. 

563 

564 This method is called for all connection invalidations, including 

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

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

567 as well as when any 

568 so-called "automatic invalidation" condition occurs. 

569 

570 :param e: an exception object indicating a reason for the 

571 invalidation. 

572 

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

574 connection will be recycled on next checkout. 

575 

576 .. versionadded:: 1.0.3 

577 

578 .. seealso:: 

579 

580 :ref:`pool_connection_invalidation` 

581 

582 """ 

583 # already invalidated 

584 if self.dbapi_connection is None: 

585 return 

586 if soft: 

587 self.__pool.dispatch.soft_invalidate( 

588 self.dbapi_connection, self, e 

589 ) 

590 else: 

591 self.__pool.dispatch.invalidate(self.dbapi_connection, self, e) 

592 if e is not None: 

593 self.__pool.logger.info( 

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

595 "Soft " if soft else "", 

596 self.dbapi_connection, 

597 e.__class__.__name__, 

598 e, 

599 ) 

600 else: 

601 self.__pool.logger.info( 

602 "%sInvalidate connection %r", 

603 "Soft " if soft else "", 

604 self.dbapi_connection, 

605 ) 

606 

607 if soft: 

608 self._soft_invalidate_time = time.time() 

609 else: 

610 self.__close(terminate=True) 

611 self.dbapi_connection = None 

612 

613 def get_connection(self): 

614 recycle = False 

615 

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

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

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

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

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

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

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

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

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

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

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

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

628 if self.dbapi_connection is None: 

629 self.info.clear() 

630 self.__connect() 

631 elif ( 

632 self.__pool._recycle > -1 

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

634 ): 

635 self.__pool.logger.info( 

636 "Connection %r exceeded timeout; recycling", 

637 self.dbapi_connection, 

638 ) 

639 recycle = True 

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

641 self.__pool.logger.info( 

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

643 + "recycling", 

644 self.dbapi_connection, 

645 ) 

646 recycle = True 

647 elif self._soft_invalidate_time > self.starttime: 

648 self.__pool.logger.info( 

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

650 + "recycling", 

651 self.dbapi_connection, 

652 ) 

653 recycle = True 

654 

655 if recycle: 

656 self.__close(terminate=True) 

657 self.info.clear() 

658 

659 self.__connect() 

660 return self.dbapi_connection 

661 

662 def _is_hard_or_soft_invalidated(self): 

663 return ( 

664 self.dbapi_connection is None 

665 or self.__pool._invalidate_time > self.starttime 

666 or (self._soft_invalidate_time > self.starttime) 

667 ) 

668 

669 def __close(self, terminate=False): 

670 self.finalize_callback.clear() 

671 if self.__pool.dispatch.close: 

672 self.__pool.dispatch.close(self.dbapi_connection, self) 

673 self.__pool._close_connection( 

674 self.dbapi_connection, terminate=terminate 

675 ) 

676 self.dbapi_connection = None 

677 

678 def __connect(self): 

679 pool = self.__pool 

680 

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

682 # creator fails, this attribute stays None 

683 self.dbapi_connection = None 

684 try: 

685 self.starttime = time.time() 

686 self.dbapi_connection = connection = pool._invoke_creator(self) 

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

688 self.fresh = True 

689 except BaseException as e: 

690 with util.safe_reraise(): 

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

692 else: 

693 # in SQLAlchemy 1.4 the first_connect event is not used by 

694 # the engine, so this will usually not be set 

695 if pool.dispatch.first_connect: 

696 pool.dispatch.first_connect.for_modify( 

697 pool.dispatch 

698 ).exec_once_unless_exception(self.dbapi_connection, self) 

699 

700 # init of the dialect now takes place within the connect 

701 # event, so ensure a mutex is used on the first run 

702 pool.dispatch.connect.for_modify( 

703 pool.dispatch 

704 )._exec_w_sync_on_first_run(self.dbapi_connection, self) 

705 

706 

707def _finalize_fairy( 

708 dbapi_connection, 

709 connection_record, 

710 pool, 

711 ref, # this is None when called directly, not by the gc 

712 echo, 

713 transaction_was_reset=False, 

714 fairy=None, 

715): 

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

717 been garbage collected. 

718 

719 When using an async dialect no IO can happen here (without using 

720 a dedicated thread), since this is called outside the greenlet 

721 context and with an already running loop. In this case function 

722 will only log a message and raise a warning. 

723 """ 

724 

725 if ref: 

726 _strong_ref_connection_records.pop(ref, None) 

727 elif fairy: 

728 _strong_ref_connection_records.pop(weakref.ref(fairy), None) 

729 

730 if ref is not None: 

731 if connection_record.fairy_ref is not ref: 

732 return 

733 assert dbapi_connection is None 

734 dbapi_connection = connection_record.dbapi_connection 

735 

736 # null pool is not _is_asyncio but can be used also with async dialects 

737 dont_restore_gced = ( 

738 pool._dialect.is_async and not pool._dialect.has_terminate 

739 ) 

740 

741 if dont_restore_gced: 

742 detach = not connection_record or ref 

743 can_manipulate_connection = not ref 

744 else: 

745 detach = not connection_record 

746 can_manipulate_connection = True 

747 

748 if dbapi_connection is not None: 

749 if connection_record and echo: 

750 pool.logger.debug( 

751 "Connection %r being returned to pool", 

752 dbapi_connection, 

753 ) 

754 

755 try: 

756 fairy = fairy or _ConnectionFairy( 

757 dbapi_connection, 

758 connection_record, 

759 echo, 

760 ) 

761 assert fairy.dbapi_connection is dbapi_connection 

762 if can_manipulate_connection: 

763 fairy._reset(pool, transaction_was_reset) 

764 

765 if detach: 

766 if connection_record: 

767 fairy._pool = pool 

768 fairy.detach() 

769 

770 if can_manipulate_connection: 

771 if pool.dispatch.close_detached: 

772 pool.dispatch.close_detached(dbapi_connection) 

773 

774 pool._close_connection(dbapi_connection) 

775 else: 

776 message = ( 

777 "The garbage collector is trying to clean up " 

778 "connection %r. This feature is unsupported on " 

779 "unsupported on asyncio " 

780 'dbapis that lack a "terminate" feature, ' 

781 "since no IO can be performed at this stage to " 

782 "reset the connection. Please close out all " 

783 "connections when they are no longer used, calling " 

784 "``close()`` or using a context manager to " 

785 "manage their lifetime." 

786 ) % dbapi_connection 

787 pool.logger.error(message) 

788 util.warn(message) 

789 

790 except BaseException as e: 

791 pool.logger.error( 

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

793 ) 

794 if connection_record: 

795 connection_record.invalidate(e=e) 

796 if not isinstance(e, Exception): 

797 raise 

798 

799 if connection_record and connection_record.fairy_ref is not None: 

800 connection_record.checkin() 

801 

802 

803# a dictionary of the _ConnectionFairy weakrefs to _ConnectionRecord, so that 

804# GC under pypy will call ConnectionFairy finalizers. linked directly to the 

805# weakref that will empty itself when collected so that it should not create 

806# any unmanaged memory references. 

807_strong_ref_connection_records = {} 

808 

809 

810class _ConnectionFairy(object): 

811 

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

813 support. 

814 

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

816 to provide context management to a DBAPI connection delivered by 

817 that :class:`_pool.Pool`. 

818 

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

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

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

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

823 invisible. 

824 

825 .. seealso:: 

826 

827 :class:`._ConnectionRecord` 

828 

829 """ 

830 

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

832 self.dbapi_connection = dbapi_connection 

833 self._connection_record = connection_record 

834 self._echo = echo 

835 

836 dbapi_connection = None 

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

838 

839 .. versionadded:: 1.4.24 

840 

841 .. seealso:: 

842 

843 :attr:`._ConnectionFairy.driver_connection` 

844 

845 :attr:`._ConnectionRecord.dbapi_connection` 

846 

847 :ref:`faq_dbapi_connection` 

848 

849 """ 

850 

851 _connection_record = None 

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

853 with the DBAPI connection. 

854 

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

856 

857 """ 

858 

859 @property 

860 def driver_connection(self): 

861 """The connection object as returned by the driver after a connect. 

862 

863 .. versionadded:: 1.4.24 

864 

865 .. seealso:: 

866 

867 :attr:`._ConnectionFairy.dbapi_connection` 

868 

869 :attr:`._ConnectionRecord.driver_connection` 

870 

871 :ref:`faq_dbapi_connection` 

872 

873 """ 

874 return self._connection_record.driver_connection 

875 

876 @property 

877 def connection(self): 

878 """An alias to :attr:`._ConnectionFairy.dbapi_connection`. 

879 

880 This alias is deprecated, please use the new name. 

881 

882 .. deprecated:: 1.4.24 

883 

884 """ 

885 return self.dbapi_connection 

886 

887 @connection.setter 

888 def connection(self, value): 

889 self.dbapi_connection = value 

890 

891 @classmethod 

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

893 if not fairy: 

894 fairy = _ConnectionRecord.checkout(pool) 

895 

896 fairy._pool = pool 

897 fairy._counter = 0 

898 

899 if threadconns is not None: 

900 threadconns.current = weakref.ref(fairy) 

901 

902 if fairy.dbapi_connection is None: 

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

904 fairy._counter += 1 

905 if ( 

906 not pool.dispatch.checkout and not pool._pre_ping 

907 ) or fairy._counter != 1: 

908 return fairy 

909 

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

911 # as the pre-pinger. 

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

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

914 # here. 

915 attempts = 2 

916 

917 while attempts > 0: 

918 connection_is_fresh = fairy._connection_record.fresh 

919 fairy._connection_record.fresh = False 

920 try: 

921 if pool._pre_ping: 

922 if not connection_is_fresh: 

923 if fairy._echo: 

924 pool.logger.debug( 

925 "Pool pre-ping on connection %s", 

926 fairy.dbapi_connection, 

927 ) 

928 result = pool._dialect.do_ping(fairy.dbapi_connection) 

929 if not result: 

930 if fairy._echo: 

931 pool.logger.debug( 

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

933 "will invalidate pool", 

934 fairy.dbapi_connection, 

935 ) 

936 raise exc.InvalidatePoolError() 

937 elif fairy._echo: 

938 pool.logger.debug( 

939 "Connection %s is fresh, skipping pre-ping", 

940 fairy.dbapi_connection, 

941 ) 

942 

943 pool.dispatch.checkout( 

944 fairy.dbapi_connection, fairy._connection_record, fairy 

945 ) 

946 return fairy 

947 except exc.DisconnectionError as e: 

948 if e.invalidate_pool: 

949 pool.logger.info( 

950 "Disconnection detected on checkout, " 

951 "invalidating all pooled connections prior to " 

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

953 e, 

954 ) 

955 fairy._connection_record.invalidate(e) 

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

957 else: 

958 pool.logger.info( 

959 "Disconnection detected on checkout, " 

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

961 fairy.dbapi_connection, 

962 e, 

963 ) 

964 fairy._connection_record.invalidate(e) 

965 try: 

966 fairy.dbapi_connection = ( 

967 fairy._connection_record.get_connection() 

968 ) 

969 except BaseException as err: 

970 with util.safe_reraise(): 

971 fairy._connection_record._checkin_failed( 

972 err, 

973 _fairy_was_created=True, 

974 ) 

975 

976 # prevent _ConnectionFairy from being carried 

977 # in the stack trace. Do this after the 

978 # connection record has been checked in, so that 

979 # if the del triggers a finalize fairy, it won't 

980 # try to checkin a second time. 

981 del fairy 

982 

983 attempts -= 1 

984 except BaseException as be_outer: 

985 with util.safe_reraise(): 

986 rec = fairy._connection_record 

987 if rec is not None: 

988 rec._checkin_failed( 

989 be_outer, 

990 _fairy_was_created=True, 

991 ) 

992 

993 # prevent _ConnectionFairy from being carried 

994 # in the stack trace, see above 

995 del fairy 

996 

997 # never called, this is for code linters 

998 raise 

999 

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

1001 fairy.invalidate() 

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

1003 

1004 def _checkout_existing(self): 

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

1006 

1007 def _checkin(self, transaction_was_reset=False): 

1008 _finalize_fairy( 

1009 self.dbapi_connection, 

1010 self._connection_record, 

1011 self._pool, 

1012 None, 

1013 self._echo, 

1014 transaction_was_reset=transaction_was_reset, 

1015 fairy=self, 

1016 ) 

1017 self.dbapi_connection = None 

1018 self._connection_record = None 

1019 

1020 _close = _checkin 

1021 

1022 def _reset(self, pool, transaction_was_reset=False): 

1023 if pool.dispatch.reset: 

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

1025 if pool._reset_on_return is reset_rollback: 

1026 if transaction_was_reset: 

1027 if self._echo: 

1028 pool.logger.debug( 

1029 "Connection %s reset, transaction already reset", 

1030 self.dbapi_connection, 

1031 ) 

1032 else: 

1033 if self._echo: 

1034 pool.logger.debug( 

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

1036 self.dbapi_connection, 

1037 ) 

1038 pool._dialect.do_rollback(self) 

1039 elif pool._reset_on_return is reset_commit: 

1040 if self._echo: 

1041 pool.logger.debug( 

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

1043 self.dbapi_connection, 

1044 ) 

1045 pool._dialect.do_commit(self) 

1046 

1047 @property 

1048 def _logger(self): 

1049 return self._pool.logger 

1050 

1051 @property 

1052 def is_valid(self): 

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

1054 to an active DBAPI connection.""" 

1055 

1056 return self.dbapi_connection is not None 

1057 

1058 @util.memoized_property 

1059 def info(self): 

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

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

1062 data to be associated with the connection. 

1063 

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

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

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

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

1068 :attr:`_engine.Connection.info` 

1069 accessors. 

1070 

1071 The dictionary associated with a particular DBAPI connection is 

1072 discarded when the connection itself is discarded. 

1073 

1074 """ 

1075 return self._connection_record.info 

1076 

1077 @property 

1078 def record_info(self): 

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

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

1081 

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

1083 of this dictionary is persistent across connections that are 

1084 disconnected and/or invalidated within the lifespan of a 

1085 :class:`._ConnectionRecord`. 

1086 

1087 .. versionadded:: 1.1 

1088 

1089 """ 

1090 if self._connection_record: 

1091 return self._connection_record.record_info 

1092 else: 

1093 return None 

1094 

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

1096 """Mark this connection as invalidated. 

1097 

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

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

1100 the DBAPI connection is immediately closed and discarded from 

1101 further use by the pool. The invalidation mechanism proceeds 

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

1103 

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

1105 

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

1107 connection will be recycled on next checkout. 

1108 

1109 .. versionadded:: 1.0.3 

1110 

1111 .. seealso:: 

1112 

1113 :ref:`pool_connection_invalidation` 

1114 

1115 """ 

1116 

1117 if self.dbapi_connection is None: 

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

1119 return 

1120 if self._connection_record: 

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

1122 if not soft: 

1123 self.dbapi_connection = None 

1124 self._checkin() 

1125 

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

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

1128 

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

1130 method. 

1131 

1132 """ 

1133 return self.dbapi_connection.cursor(*args, **kwargs) 

1134 

1135 def __getattr__(self, key): 

1136 return getattr(self.dbapi_connection, key) 

1137 

1138 def detach(self): 

1139 """Separate this connection from its Pool. 

1140 

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

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

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

1144 and will create a new connection when next used. 

1145 

1146 Note that any overall connection limiting constraints imposed by a 

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

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

1149 """ 

1150 

1151 if self._connection_record is not None: 

1152 rec = self._connection_record 

1153 rec.fairy_ref = None 

1154 rec.dbapi_connection = None 

1155 # TODO: should this be _return_conn? 

1156 self._pool._do_return_conn(self._connection_record) 

1157 self.info = self.info.copy() 

1158 self._connection_record = None 

1159 

1160 if self._pool.dispatch.detach: 

1161 self._pool.dispatch.detach(self.dbapi_connection, rec) 

1162 

1163 def close(self): 

1164 self._counter -= 1 

1165 if self._counter == 0: 

1166 self._checkin() 

1167 

1168 def _close_special(self, transaction_reset=False): 

1169 self._counter -= 1 

1170 if self._counter == 0: 

1171 self._checkin(transaction_was_reset=transaction_reset)