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

396 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# sqlalchemy/pool.py 

2# Copyright (C) 2005-2022 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 Exception: 

264 self.logger.error( 

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

266 ) 

267 

268 def _create_connection(self): 

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

270 

271 return _ConnectionRecord(self) 

272 

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

274 """Mark all connections established within the generation 

275 of the given connection as invalidated. 

276 

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

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

279 no action is performed. 

280 

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

282 time will be recycled upon next checkout. 

283 """ 

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

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

286 self._invalidate_time = time.time() 

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

288 connection.invalidate(exception) 

289 

290 def recreate(self): 

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

292 and configured with identical creation arguments. 

293 

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

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

296 its place. 

297 

298 """ 

299 

300 raise NotImplementedError() 

301 

302 def dispose(self): 

303 """Dispose of this pool. 

304 

305 This method leaves the possibility of checked-out connections 

306 remaining open, as it only affects connections that are 

307 idle in the pool. 

308 

309 .. seealso:: 

310 

311 :meth:`Pool.recreate` 

312 

313 """ 

314 

315 raise NotImplementedError() 

316 

317 def connect(self): 

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

319 

320 The connection is instrumented such that when its 

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

322 the pool. 

323 

324 """ 

325 return _ConnectionFairy._checkout(self) 

326 

327 def _return_conn(self, record): 

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

329 

330 This method is called when an instrumented DBAPI connection 

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

332 

333 """ 

334 self._do_return_conn(record) 

335 

336 def _do_get(self): 

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

338 

339 raise NotImplementedError() 

340 

341 def _do_return_conn(self, conn): 

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

343 

344 raise NotImplementedError() 

345 

346 def status(self): 

347 raise NotImplementedError() 

348 

349 

350class _ConnectionRecord(object): 

351 

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

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

354 

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

356 DBAPI connection whether or not that DBAPI connection has been 

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

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

359 out. 

360 

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

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

363 :meth:`._ConnectionRecord.invalidate` 

364 method is called, the DBAPI connection associated with this 

365 :class:`._ConnectionRecord` 

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

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

368 :class:`_pool.Pool` 

369 next uses this record. 

370 

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

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

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

374 still 

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

376 

377 .. seealso:: 

378 

379 :class:`._ConnectionFairy` 

380 

381 """ 

382 

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

384 self.__pool = pool 

385 if connect: 

386 self.__connect() 

387 self.finalize_callback = deque() 

388 

389 fresh = False 

390 

391 fairy_ref = None 

392 

393 starttime = None 

394 

395 dbapi_connection = None 

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

397 

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

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

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

401 

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

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

404 to the DBAPI protocol. 

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

406 connection objected returned by the driver. 

407 

408 .. versionadded:: 1.4.24 

409 

410 """ 

411 

412 @property 

413 def driver_connection(self): 

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

415 

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

417 is the same as the one referenced by 

418 :attr:`._ConnectionRecord.dbapi_connection`. 

419 

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

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

422 

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

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

425 

426 .. versionadded:: 1.4.24 

427 

428 """ 

429 

430 if self.dbapi_connection is None: 

431 return None 

432 else: 

433 return self.__pool._dialect.get_driver_connection( 

434 self.dbapi_connection 

435 ) 

436 

437 @property 

438 def connection(self): 

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

440 

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

442 

443 .. deprecated:: 1.4.24 

444 

445 """ 

446 return self.dbapi_connection 

447 

448 @connection.setter 

449 def connection(self, value): 

450 self.dbapi_connection = value 

451 

452 _soft_invalidate_time = 0 

453 

454 @util.memoized_property 

455 def info(self): 

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

457 

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

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

460 

461 .. note:: 

462 

463 The lifespan of this dictionary is linked to the 

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

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

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

467 persistent throughout the lifespan of the 

468 :class:`._ConnectionRecord` container. 

469 

470 """ 

471 return {} 

472 

473 @util.memoized_property 

474 def record_info(self): 

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

476 itself. 

477 

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

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

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

481 and will remain persistent throughout the life of the 

482 :class:`._ConnectionRecord`. 

483 

484 .. versionadded:: 1.1 

485 

486 """ 

487 return {} 

488 

489 @classmethod 

490 def checkout(cls, pool): 

491 rec = pool._do_get() 

492 try: 

493 dbapi_connection = rec.get_connection() 

494 except Exception as err: 

495 with util.safe_reraise(): 

496 rec._checkin_failed(err, _fairy_was_created=False) 

497 echo = pool._should_log_debug() 

498 fairy = _ConnectionFairy(dbapi_connection, rec, echo) 

499 

500 rec.fairy_ref = ref = weakref.ref( 

501 fairy, 

502 lambda ref: _finalize_fairy 

503 and _finalize_fairy( 

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

505 ), 

506 ) 

507 _strong_ref_connection_records[ref] = rec 

508 if echo: 

509 pool.logger.debug( 

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

511 ) 

512 return fairy 

513 

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

515 self.invalidate(e=err) 

516 self.checkin( 

517 _fairy_was_created=_fairy_was_created, 

518 ) 

519 

520 def checkin(self, _fairy_was_created=True): 

521 if self.fairy_ref is None and _fairy_was_created: 

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

523 # meaning there was no _ConnectionFairy and we must unconditionally 

524 # do a checkin. 

525 # 

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

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

528 # a double checkin. 

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

530 return 

531 self.fairy_ref = None 

532 connection = self.dbapi_connection 

533 pool = self.__pool 

534 while self.finalize_callback: 

535 finalizer = self.finalize_callback.pop() 

536 finalizer(connection) 

537 if pool.dispatch.checkin: 

538 pool.dispatch.checkin(connection, self) 

539 

540 pool._return_conn(self) 

541 

542 @property 

543 def in_use(self): 

544 return self.fairy_ref is not None 

545 

546 @property 

547 def last_connect_time(self): 

548 return self.starttime 

549 

550 def close(self): 

551 if self.dbapi_connection is not None: 

552 self.__close() 

553 

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

555 """Invalidate the DBAPI connection held by this 

556 :class:`._ConnectionRecord`. 

557 

558 This method is called for all connection invalidations, including 

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

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

561 as well as when any 

562 so-called "automatic invalidation" condition occurs. 

563 

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

565 invalidation. 

566 

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

568 connection will be recycled on next checkout. 

569 

570 .. versionadded:: 1.0.3 

571 

572 .. seealso:: 

573 

574 :ref:`pool_connection_invalidation` 

575 

576 """ 

577 # already invalidated 

578 if self.dbapi_connection is None: 

579 return 

580 if soft: 

581 self.__pool.dispatch.soft_invalidate( 

582 self.dbapi_connection, self, e 

583 ) 

584 else: 

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

586 if e is not None: 

587 self.__pool.logger.info( 

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

589 "Soft " if soft else "", 

590 self.dbapi_connection, 

591 e.__class__.__name__, 

592 e, 

593 ) 

594 else: 

595 self.__pool.logger.info( 

596 "%sInvalidate connection %r", 

597 "Soft " if soft else "", 

598 self.dbapi_connection, 

599 ) 

600 

601 if soft: 

602 self._soft_invalidate_time = time.time() 

603 else: 

604 self.__close(terminate=True) 

605 self.dbapi_connection = None 

606 

607 def get_connection(self): 

608 recycle = False 

609 

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

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

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

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

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

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

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

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

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

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

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

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

622 if self.dbapi_connection is None: 

623 self.info.clear() 

624 self.__connect() 

625 elif ( 

626 self.__pool._recycle > -1 

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

628 ): 

629 self.__pool.logger.info( 

630 "Connection %r exceeded timeout; recycling", 

631 self.dbapi_connection, 

632 ) 

633 recycle = True 

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

635 self.__pool.logger.info( 

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

637 + "recycling", 

638 self.dbapi_connection, 

639 ) 

640 recycle = True 

641 elif self._soft_invalidate_time > self.starttime: 

642 self.__pool.logger.info( 

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

644 + "recycling", 

645 self.dbapi_connection, 

646 ) 

647 recycle = True 

648 

649 if recycle: 

650 self.__close(terminate=True) 

651 self.info.clear() 

652 

653 self.__connect() 

654 return self.dbapi_connection 

655 

656 def _is_hard_or_soft_invalidated(self): 

657 return ( 

658 self.dbapi_connection is None 

659 or self.__pool._invalidate_time > self.starttime 

660 or (self._soft_invalidate_time > self.starttime) 

661 ) 

662 

663 def __close(self, terminate=False): 

664 self.finalize_callback.clear() 

665 if self.__pool.dispatch.close: 

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

667 self.__pool._close_connection( 

668 self.dbapi_connection, terminate=terminate 

669 ) 

670 self.dbapi_connection = None 

671 

672 def __connect(self): 

673 pool = self.__pool 

674 

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

676 # creator fails, this attribute stays None 

677 self.dbapi_connection = None 

678 try: 

679 self.starttime = time.time() 

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

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

682 self.fresh = True 

683 except Exception as e: 

684 with util.safe_reraise(): 

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

686 else: 

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

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

689 if pool.dispatch.first_connect: 

690 pool.dispatch.first_connect.for_modify( 

691 pool.dispatch 

692 ).exec_once_unless_exception(self.dbapi_connection, self) 

693 

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

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

696 pool.dispatch.connect.for_modify( 

697 pool.dispatch 

698 )._exec_w_sync_on_first_run(self.dbapi_connection, self) 

699 

700 

701def _finalize_fairy( 

702 dbapi_connection, 

703 connection_record, 

704 pool, 

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

706 echo, 

707 transaction_was_reset=False, 

708 fairy=None, 

709): 

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

711 been garbage collected. 

712 

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

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

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

716 will only log a message and raise a warning. 

717 """ 

718 

719 if ref: 

720 _strong_ref_connection_records.pop(ref, None) 

721 elif fairy: 

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

723 

724 if ref is not None: 

725 if connection_record.fairy_ref is not ref: 

726 return 

727 assert dbapi_connection is None 

728 dbapi_connection = connection_record.dbapi_connection 

729 

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

731 dont_restore_gced = ( 

732 pool._dialect.is_async and not pool._dialect.has_terminate 

733 ) 

734 

735 if dont_restore_gced: 

736 detach = not connection_record or ref 

737 can_manipulate_connection = not ref 

738 else: 

739 detach = not connection_record 

740 can_manipulate_connection = True 

741 

742 if dbapi_connection is not None: 

743 if connection_record and echo: 

744 pool.logger.debug( 

745 "Connection %r being returned to pool", 

746 dbapi_connection, 

747 ) 

748 

749 try: 

750 fairy = fairy or _ConnectionFairy( 

751 dbapi_connection, 

752 connection_record, 

753 echo, 

754 ) 

755 assert fairy.dbapi_connection is dbapi_connection 

756 if can_manipulate_connection: 

757 fairy._reset(pool, transaction_was_reset) 

758 

759 if detach: 

760 if connection_record: 

761 fairy._pool = pool 

762 fairy.detach() 

763 

764 if can_manipulate_connection: 

765 if pool.dispatch.close_detached: 

766 pool.dispatch.close_detached(dbapi_connection) 

767 

768 pool._close_connection(dbapi_connection) 

769 else: 

770 message = ( 

771 "The garbage collector is trying to clean up " 

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

773 "unsupported on asyncio " 

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

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

776 "reset the connection. Please close out all " 

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

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

779 "manage their lifetime." 

780 ) % dbapi_connection 

781 pool.logger.error(message) 

782 util.warn(message) 

783 

784 except BaseException as e: 

785 pool.logger.error( 

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

787 ) 

788 if connection_record: 

789 connection_record.invalidate(e=e) 

790 if not isinstance(e, Exception): 

791 raise 

792 

793 if connection_record and connection_record.fairy_ref is not None: 

794 connection_record.checkin() 

795 

796 

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

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

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

800# any unmanaged memory references. 

801_strong_ref_connection_records = {} 

802 

803 

804class _ConnectionFairy(object): 

805 

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

807 support. 

808 

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

810 to provide context management to a DBAPI connection delivered by 

811 that :class:`_pool.Pool`. 

812 

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

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

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

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

817 invisible. 

818 

819 .. seealso:: 

820 

821 :class:`._ConnectionRecord` 

822 

823 """ 

824 

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

826 self.dbapi_connection = dbapi_connection 

827 self._connection_record = connection_record 

828 self._echo = echo 

829 

830 dbapi_connection = None 

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

832 

833 .. versionadded:: 1.4.24 

834 

835 .. seealso:: 

836 

837 :attr:`._ConnectionFairy.driver_connection` 

838 

839 :attr:`._ConnectionRecord.dbapi_connection` 

840 

841 :ref:`faq_dbapi_connection` 

842 

843 """ 

844 

845 _connection_record = None 

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

847 with the DBAPI connection. 

848 

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

850 

851 """ 

852 

853 @property 

854 def driver_connection(self): 

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

856 

857 .. versionadded:: 1.4.24 

858 

859 .. seealso:: 

860 

861 :attr:`._ConnectionFairy.dbapi_connection` 

862 

863 :attr:`._ConnectionRecord.driver_connection` 

864 

865 :ref:`faq_dbapi_connection` 

866 

867 """ 

868 return self._connection_record.driver_connection 

869 

870 @property 

871 def connection(self): 

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

873 

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

875 

876 .. deprecated:: 1.4.24 

877 

878 """ 

879 return self.dbapi_connection 

880 

881 @connection.setter 

882 def connection(self, value): 

883 self.dbapi_connection = value 

884 

885 @classmethod 

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

887 if not fairy: 

888 fairy = _ConnectionRecord.checkout(pool) 

889 

890 fairy._pool = pool 

891 fairy._counter = 0 

892 

893 if threadconns is not None: 

894 threadconns.current = weakref.ref(fairy) 

895 

896 if fairy.dbapi_connection is None: 

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

898 fairy._counter += 1 

899 if ( 

900 not pool.dispatch.checkout and not pool._pre_ping 

901 ) or fairy._counter != 1: 

902 return fairy 

903 

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

905 # as the pre-pinger. 

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

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

908 # here. 

909 attempts = 2 

910 while attempts > 0: 

911 connection_is_fresh = fairy._connection_record.fresh 

912 fairy._connection_record.fresh = False 

913 try: 

914 if pool._pre_ping: 

915 if not connection_is_fresh: 

916 if fairy._echo: 

917 pool.logger.debug( 

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

919 fairy.dbapi_connection, 

920 ) 

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

922 if not result: 

923 if fairy._echo: 

924 pool.logger.debug( 

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

926 "will invalidate pool", 

927 fairy.dbapi_connection, 

928 ) 

929 raise exc.InvalidatePoolError() 

930 elif fairy._echo: 

931 pool.logger.debug( 

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

933 fairy.dbapi_connection, 

934 ) 

935 

936 pool.dispatch.checkout( 

937 fairy.dbapi_connection, fairy._connection_record, fairy 

938 ) 

939 return fairy 

940 except exc.DisconnectionError as e: 

941 if e.invalidate_pool: 

942 pool.logger.info( 

943 "Disconnection detected on checkout, " 

944 "invalidating all pooled connections prior to " 

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

946 e, 

947 ) 

948 fairy._connection_record.invalidate(e) 

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

950 else: 

951 pool.logger.info( 

952 "Disconnection detected on checkout, " 

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

954 fairy.dbapi_connection, 

955 e, 

956 ) 

957 fairy._connection_record.invalidate(e) 

958 try: 

959 fairy.dbapi_connection = ( 

960 fairy._connection_record.get_connection() 

961 ) 

962 except Exception as err: 

963 with util.safe_reraise(): 

964 fairy._connection_record._checkin_failed( 

965 err, 

966 _fairy_was_created=True, 

967 ) 

968 

969 # prevent _ConnectionFairy from being carried 

970 # in the stack trace. Do this after the 

971 # connection record has been checked in, so that 

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

973 # try to checkin a second time. 

974 del fairy 

975 

976 attempts -= 1 

977 

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

979 fairy.invalidate() 

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

981 

982 def _checkout_existing(self): 

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

984 

985 def _checkin(self, transaction_was_reset=False): 

986 _finalize_fairy( 

987 self.dbapi_connection, 

988 self._connection_record, 

989 self._pool, 

990 None, 

991 self._echo, 

992 transaction_was_reset=transaction_was_reset, 

993 fairy=self, 

994 ) 

995 self.dbapi_connection = None 

996 self._connection_record = None 

997 

998 _close = _checkin 

999 

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

1001 if pool.dispatch.reset: 

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

1003 if pool._reset_on_return is reset_rollback: 

1004 if transaction_was_reset: 

1005 if self._echo: 

1006 pool.logger.debug( 

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

1008 self.dbapi_connection, 

1009 ) 

1010 else: 

1011 if self._echo: 

1012 pool.logger.debug( 

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

1014 self.dbapi_connection, 

1015 ) 

1016 pool._dialect.do_rollback(self) 

1017 elif pool._reset_on_return is reset_commit: 

1018 if self._echo: 

1019 pool.logger.debug( 

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

1021 self.dbapi_connection, 

1022 ) 

1023 pool._dialect.do_commit(self) 

1024 

1025 @property 

1026 def _logger(self): 

1027 return self._pool.logger 

1028 

1029 @property 

1030 def is_valid(self): 

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

1032 to an active DBAPI connection.""" 

1033 

1034 return self.dbapi_connection is not None 

1035 

1036 @util.memoized_property 

1037 def info(self): 

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

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

1040 data to be associated with the connection. 

1041 

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

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

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

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

1046 :attr:`_engine.Connection.info` 

1047 accessors. 

1048 

1049 The dictionary associated with a particular DBAPI connection is 

1050 discarded when the connection itself is discarded. 

1051 

1052 """ 

1053 return self._connection_record.info 

1054 

1055 @property 

1056 def record_info(self): 

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

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

1059 

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

1061 of this dictionary is persistent across connections that are 

1062 disconnected and/or invalidated within the lifespan of a 

1063 :class:`._ConnectionRecord`. 

1064 

1065 .. versionadded:: 1.1 

1066 

1067 """ 

1068 if self._connection_record: 

1069 return self._connection_record.record_info 

1070 else: 

1071 return None 

1072 

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

1074 """Mark this connection as invalidated. 

1075 

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

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

1078 the DBAPI connection is immediately closed and discarded from 

1079 further use by the pool. The invalidation mechanism proceeds 

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

1081 

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

1083 

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

1085 connection will be recycled on next checkout. 

1086 

1087 .. versionadded:: 1.0.3 

1088 

1089 .. seealso:: 

1090 

1091 :ref:`pool_connection_invalidation` 

1092 

1093 """ 

1094 

1095 if self.dbapi_connection is None: 

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

1097 return 

1098 if self._connection_record: 

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

1100 if not soft: 

1101 self.dbapi_connection = None 

1102 self._checkin() 

1103 

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

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

1106 

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

1108 method. 

1109 

1110 """ 

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

1112 

1113 def __getattr__(self, key): 

1114 return getattr(self.dbapi_connection, key) 

1115 

1116 def detach(self): 

1117 """Separate this connection from its Pool. 

1118 

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

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

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

1122 and will create a new connection when next used. 

1123 

1124 Note that any overall connection limiting constraints imposed by a 

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

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

1127 """ 

1128 

1129 if self._connection_record is not None: 

1130 rec = self._connection_record 

1131 rec.fairy_ref = None 

1132 rec.dbapi_connection = None 

1133 # TODO: should this be _return_conn? 

1134 self._pool._do_return_conn(self._connection_record) 

1135 self.info = self.info.copy() 

1136 self._connection_record = None 

1137 

1138 if self._pool.dispatch.detach: 

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

1140 

1141 def close(self): 

1142 self._counter -= 1 

1143 if self._counter == 0: 

1144 self._checkin() 

1145 

1146 def _close_special(self, transaction_reset=False): 

1147 self._counter -= 1 

1148 if self._counter == 0: 

1149 self._checkin(transaction_was_reset=transaction_reset)