Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/db/backends/base/base.py: 69%

345 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import _thread 

2import copy 

3import threading 

4import time 

5import warnings 

6from collections import deque 

7from contextlib import contextmanager 

8 

9try: 

10 import zoneinfo 

11except ImportError: 

12 from backports import zoneinfo 

13 

14from django.conf import settings 

15from django.core.exceptions import ImproperlyConfigured 

16from django.db import DEFAULT_DB_ALIAS, DatabaseError 

17from django.db.backends import utils 

18from django.db.backends.base.validation import BaseDatabaseValidation 

19from django.db.backends.signals import connection_created 

20from django.db.transaction import TransactionManagementError 

21from django.db.utils import DatabaseErrorWrapper 

22from django.utils import timezone 

23from django.utils.asyncio import async_unsafe 

24from django.utils.functional import cached_property 

25 

26NO_DB_ALIAS = "__no_db__" 

27 

28 

29# RemovedInDjango50Warning 

30def timezone_constructor(tzname): 

31 if settings.USE_DEPRECATED_PYTZ: 

32 import pytz 

33 

34 return pytz.timezone(tzname) 

35 return zoneinfo.ZoneInfo(tzname) 

36 

37 

38class BaseDatabaseWrapper: 

39 """Represent a database connection.""" 

40 

41 # Mapping of Field objects to their column types. 

42 data_types = {} 

43 # Mapping of Field objects to their SQL suffix such as AUTOINCREMENT. 

44 data_types_suffix = {} 

45 # Mapping of Field objects to their SQL for CHECK constraints. 

46 data_type_check_constraints = {} 

47 ops = None 

48 vendor = "unknown" 

49 display_name = "unknown" 

50 SchemaEditorClass = None 

51 # Classes instantiated in __init__(). 

52 client_class = None 

53 creation_class = None 

54 features_class = None 

55 introspection_class = None 

56 ops_class = None 

57 validation_class = BaseDatabaseValidation 

58 

59 queries_limit = 9000 

60 

61 def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS): 

62 # Connection related attributes. 

63 # The underlying database connection. 

64 self.connection = None 

65 # `settings_dict` should be a dictionary containing keys such as 

66 # NAME, USER, etc. It's called `settings_dict` instead of `settings` 

67 # to disambiguate it from Django settings modules. 

68 self.settings_dict = settings_dict 

69 self.alias = alias 

70 # Query logging in debug mode or when explicitly enabled. 

71 self.queries_log = deque(maxlen=self.queries_limit) 

72 self.force_debug_cursor = False 

73 

74 # Transaction related attributes. 

75 # Tracks if the connection is in autocommit mode. Per PEP 249, by 

76 # default, it isn't. 

77 self.autocommit = False 

78 # Tracks if the connection is in a transaction managed by 'atomic'. 

79 self.in_atomic_block = False 

80 # Increment to generate unique savepoint ids. 

81 self.savepoint_state = 0 

82 # List of savepoints created by 'atomic'. 

83 self.savepoint_ids = [] 

84 # Tracks if the outermost 'atomic' block should commit on exit, 

85 # ie. if autocommit was active on entry. 

86 self.commit_on_exit = True 

87 # Tracks if the transaction should be rolled back to the next 

88 # available savepoint because of an exception in an inner block. 

89 self.needs_rollback = False 

90 

91 # Connection termination related attributes. 

92 self.close_at = None 

93 self.closed_in_transaction = False 

94 self.errors_occurred = False 

95 

96 # Thread-safety related attributes. 

97 self._thread_sharing_lock = threading.Lock() 

98 self._thread_sharing_count = 0 

99 self._thread_ident = _thread.get_ident() 

100 

101 # A list of no-argument functions to run when the transaction commits. 

102 # Each entry is an (sids, func) tuple, where sids is a set of the 

103 # active savepoint IDs when this function was registered. 

104 self.run_on_commit = [] 

105 

106 # Should we run the on-commit hooks the next time set_autocommit(True) 

107 # is called? 

108 self.run_commit_hooks_on_set_autocommit_on = False 

109 

110 # A stack of wrappers to be invoked around execute()/executemany() 

111 # calls. Each entry is a function taking five arguments: execute, sql, 

112 # params, many, and context. It's the function's responsibility to 

113 # call execute(sql, params, many, context). 

114 self.execute_wrappers = [] 

115 

116 self.client = self.client_class(self) 

117 self.creation = self.creation_class(self) 

118 self.features = self.features_class(self) 

119 self.introspection = self.introspection_class(self) 

120 self.ops = self.ops_class(self) 

121 self.validation = self.validation_class(self) 

122 

123 def ensure_timezone(self): 

124 """ 

125 Ensure the connection's timezone is set to `self.timezone_name` and 

126 return whether it changed or not. 

127 """ 

128 return False 

129 

130 @cached_property 

131 def timezone(self): 

132 """ 

133 Return a tzinfo of the database connection time zone. 

134 

135 This is only used when time zone support is enabled. When a datetime is 

136 read from the database, it is always returned in this time zone. 

137 

138 When the database backend supports time zones, it doesn't matter which 

139 time zone Django uses, as long as aware datetimes are used everywhere. 

140 Other users connecting to the database can choose their own time zone. 

141 

142 When the database backend doesn't support time zones, the time zone 

143 Django uses may be constrained by the requirements of other users of 

144 the database. 

145 """ 

146 if not settings.USE_TZ: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true

147 return None 

148 elif self.settings_dict["TIME_ZONE"] is None: 148 ↛ 151line 148 didn't jump to line 151, because the condition on line 148 was never false

149 return timezone.utc 

150 else: 

151 return timezone_constructor(self.settings_dict["TIME_ZONE"]) 

152 

153 @cached_property 

154 def timezone_name(self): 

155 """ 

156 Name of the time zone of the database connection. 

157 """ 

158 if not settings.USE_TZ: 158 ↛ 159line 158 didn't jump to line 159, because the condition on line 158 was never true

159 return settings.TIME_ZONE 

160 elif self.settings_dict["TIME_ZONE"] is None: 160 ↛ 163line 160 didn't jump to line 163, because the condition on line 160 was never false

161 return "UTC" 

162 else: 

163 return self.settings_dict["TIME_ZONE"] 

164 

165 @property 

166 def queries_logged(self): 

167 return self.force_debug_cursor or settings.DEBUG 

168 

169 @property 

170 def queries(self): 

171 if len(self.queries_log) == self.queries_log.maxlen: 

172 warnings.warn( 

173 "Limit for query logging exceeded, only the last {} queries " 

174 "will be returned.".format(self.queries_log.maxlen) 

175 ) 

176 return list(self.queries_log) 

177 

178 # ##### Backend-specific methods for creating connections and cursors ##### 

179 

180 def get_connection_params(self): 

181 """Return a dict of parameters suitable for get_new_connection.""" 

182 raise NotImplementedError( 

183 "subclasses of BaseDatabaseWrapper may require a get_connection_params() " 

184 "method" 

185 ) 

186 

187 def get_new_connection(self, conn_params): 

188 """Open a connection to the database.""" 

189 raise NotImplementedError( 

190 "subclasses of BaseDatabaseWrapper may require a get_new_connection() " 

191 "method" 

192 ) 

193 

194 def init_connection_state(self): 

195 """Initialize the database connection settings.""" 

196 raise NotImplementedError( 

197 "subclasses of BaseDatabaseWrapper may require an init_connection_state() " 

198 "method" 

199 ) 

200 

201 def create_cursor(self, name=None): 

202 """Create a cursor. Assume that a connection is established.""" 

203 raise NotImplementedError( 

204 "subclasses of BaseDatabaseWrapper may require a create_cursor() method" 

205 ) 

206 

207 # ##### Backend-specific methods for creating connections ##### 

208 

209 @async_unsafe 

210 def connect(self): 

211 """Connect to the database. Assume that the connection is closed.""" 

212 # Check for invalid configurations. 

213 self.check_settings() 

214 # In case the previous connection was closed while in an atomic block 

215 self.in_atomic_block = False 

216 self.savepoint_ids = [] 

217 self.needs_rollback = False 

218 # Reset parameters defining when to close the connection 

219 max_age = self.settings_dict["CONN_MAX_AGE"] 

220 self.close_at = None if max_age is None else time.monotonic() + max_age 

221 self.closed_in_transaction = False 

222 self.errors_occurred = False 

223 # Establish the connection 

224 conn_params = self.get_connection_params() 

225 self.connection = self.get_new_connection(conn_params) 

226 self.set_autocommit(self.settings_dict["AUTOCOMMIT"]) 

227 self.init_connection_state() 

228 connection_created.send(sender=self.__class__, connection=self) 

229 

230 self.run_on_commit = [] 

231 

232 def check_settings(self): 

233 if self.settings_dict["TIME_ZONE"] is not None and not settings.USE_TZ: 233 ↛ 234line 233 didn't jump to line 234, because the condition on line 233 was never true

234 raise ImproperlyConfigured( 

235 "Connection '%s' cannot set TIME_ZONE because USE_TZ is False." 

236 % self.alias 

237 ) 

238 

239 @async_unsafe 

240 def ensure_connection(self): 

241 """Guarantee that a connection to the database is established.""" 

242 if self.connection is None: 

243 with self.wrap_database_errors: 

244 self.connect() 

245 

246 # ##### Backend-specific wrappers for PEP-249 connection methods ##### 

247 

248 def _prepare_cursor(self, cursor): 

249 """ 

250 Validate the connection is usable and perform database cursor wrapping. 

251 """ 

252 self.validate_thread_sharing() 

253 if self.queries_logged: 253 ↛ 254line 253 didn't jump to line 254, because the condition on line 253 was never true

254 wrapped_cursor = self.make_debug_cursor(cursor) 

255 else: 

256 wrapped_cursor = self.make_cursor(cursor) 

257 return wrapped_cursor 

258 

259 def _cursor(self, name=None): 

260 self.ensure_connection() 

261 with self.wrap_database_errors: 

262 return self._prepare_cursor(self.create_cursor(name)) 

263 

264 def _commit(self): 

265 if self.connection is not None: 265 ↛ exitline 265 didn't return from function '_commit', because the condition on line 265 was never false

266 with self.wrap_database_errors: 

267 return self.connection.commit() 

268 

269 def _rollback(self): 

270 if self.connection is not None: 270 ↛ exitline 270 didn't return from function '_rollback', because the condition on line 270 was never false

271 with self.wrap_database_errors: 

272 return self.connection.rollback() 

273 

274 def _close(self): 

275 if self.connection is not None: 275 ↛ exitline 275 didn't return from function '_close', because the condition on line 275 was never false

276 with self.wrap_database_errors: 

277 return self.connection.close() 

278 

279 # ##### Generic wrappers for PEP-249 connection methods ##### 

280 

281 @async_unsafe 

282 def cursor(self): 

283 """Create a cursor, opening a connection if necessary.""" 

284 return self._cursor() 

285 

286 @async_unsafe 

287 def commit(self): 

288 """Commit a transaction and reset the dirty flag.""" 

289 self.validate_thread_sharing() 

290 self.validate_no_atomic_block() 

291 self._commit() 

292 # A successful commit means that the database connection works. 

293 self.errors_occurred = False 

294 self.run_commit_hooks_on_set_autocommit_on = True 

295 

296 @async_unsafe 

297 def rollback(self): 

298 """Roll back a transaction and reset the dirty flag.""" 

299 self.validate_thread_sharing() 

300 self.validate_no_atomic_block() 

301 self._rollback() 

302 # A successful rollback means that the database connection works. 

303 self.errors_occurred = False 

304 self.needs_rollback = False 

305 self.run_on_commit = [] 

306 

307 @async_unsafe 

308 def close(self): 

309 """Close the connection to the database.""" 

310 self.validate_thread_sharing() 

311 self.run_on_commit = [] 

312 

313 # Don't call validate_no_atomic_block() to avoid making it difficult 

314 # to get rid of a connection in an invalid state. The next connect() 

315 # will reset the transaction state anyway. 

316 if self.closed_in_transaction or self.connection is None: 

317 return 

318 try: 

319 self._close() 

320 finally: 

321 if self.in_atomic_block: 321 ↛ 322line 321 didn't jump to line 322, because the condition on line 321 was never true

322 self.closed_in_transaction = True 

323 self.needs_rollback = True 

324 else: 

325 self.connection = None 

326 

327 # ##### Backend-specific savepoint management methods ##### 

328 

329 def _savepoint(self, sid): 

330 with self.cursor() as cursor: 

331 cursor.execute(self.ops.savepoint_create_sql(sid)) 

332 

333 def _savepoint_rollback(self, sid): 

334 with self.cursor() as cursor: 

335 cursor.execute(self.ops.savepoint_rollback_sql(sid)) 

336 

337 def _savepoint_commit(self, sid): 

338 with self.cursor() as cursor: 

339 cursor.execute(self.ops.savepoint_commit_sql(sid)) 

340 

341 def _savepoint_allowed(self): 

342 # Savepoints cannot be created outside a transaction 

343 return self.features.uses_savepoints and not self.get_autocommit() 

344 

345 # ##### Generic savepoint management methods ##### 

346 

347 @async_unsafe 

348 def savepoint(self): 

349 """ 

350 Create a savepoint inside the current transaction. Return an 

351 identifier for the savepoint that will be used for the subsequent 

352 rollback or commit. Do nothing if savepoints are not supported. 

353 """ 

354 if not self._savepoint_allowed(): 354 ↛ 355line 354 didn't jump to line 355, because the condition on line 354 was never true

355 return 

356 

357 thread_ident = _thread.get_ident() 

358 tid = str(thread_ident).replace("-", "") 

359 

360 self.savepoint_state += 1 

361 sid = "s%s_x%d" % (tid, self.savepoint_state) 

362 

363 self.validate_thread_sharing() 

364 self._savepoint(sid) 

365 

366 return sid 

367 

368 @async_unsafe 

369 def savepoint_rollback(self, sid): 

370 """ 

371 Roll back to a savepoint. Do nothing if savepoints are not supported. 

372 """ 

373 if not self._savepoint_allowed(): 373 ↛ 374line 373 didn't jump to line 374, because the condition on line 373 was never true

374 return 

375 

376 self.validate_thread_sharing() 

377 self._savepoint_rollback(sid) 

378 

379 # Remove any callbacks registered while this savepoint was active. 

380 self.run_on_commit = [ 

381 (sids, func) for (sids, func) in self.run_on_commit if sid not in sids 

382 ] 

383 

384 @async_unsafe 

385 def savepoint_commit(self, sid): 

386 """ 

387 Release a savepoint. Do nothing if savepoints are not supported. 

388 """ 

389 if not self._savepoint_allowed(): 389 ↛ 390line 389 didn't jump to line 390, because the condition on line 389 was never true

390 return 

391 

392 self.validate_thread_sharing() 

393 self._savepoint_commit(sid) 

394 

395 @async_unsafe 

396 def clean_savepoints(self): 

397 """ 

398 Reset the counter used to generate unique savepoint ids in this thread. 

399 """ 

400 self.savepoint_state = 0 

401 

402 # ##### Backend-specific transaction management methods ##### 

403 

404 def _set_autocommit(self, autocommit): 

405 """ 

406 Backend-specific implementation to enable or disable autocommit. 

407 """ 

408 raise NotImplementedError( 

409 "subclasses of BaseDatabaseWrapper may require a _set_autocommit() method" 

410 ) 

411 

412 # ##### Generic transaction management methods ##### 

413 

414 def get_autocommit(self): 

415 """Get the autocommit state.""" 

416 self.ensure_connection() 

417 return self.autocommit 

418 

419 def set_autocommit( 

420 self, autocommit, force_begin_transaction_with_broken_autocommit=False 

421 ): 

422 """ 

423 Enable or disable autocommit. 

424 

425 The usual way to start a transaction is to turn autocommit off. 

426 SQLite does not properly start a transaction when disabling 

427 autocommit. To avoid this buggy behavior and to actually enter a new 

428 transaction, an explicit BEGIN is required. Using 

429 force_begin_transaction_with_broken_autocommit=True will issue an 

430 explicit BEGIN with SQLite. This option will be ignored for other 

431 backends. 

432 """ 

433 self.validate_no_atomic_block() 

434 self.ensure_connection() 

435 

436 start_transaction_under_autocommit = ( 

437 force_begin_transaction_with_broken_autocommit 

438 and not autocommit 

439 and hasattr(self, "_start_transaction_under_autocommit") 

440 ) 

441 

442 if start_transaction_under_autocommit: 442 ↛ 443line 442 didn't jump to line 443, because the condition on line 442 was never true

443 self._start_transaction_under_autocommit() 

444 else: 

445 self._set_autocommit(autocommit) 

446 

447 self.autocommit = autocommit 

448 

449 if autocommit and self.run_commit_hooks_on_set_autocommit_on: 

450 self.run_and_clear_commit_hooks() 

451 self.run_commit_hooks_on_set_autocommit_on = False 

452 

453 def get_rollback(self): 

454 """Get the "needs rollback" flag -- for *advanced use* only.""" 

455 if not self.in_atomic_block: 

456 raise TransactionManagementError( 

457 "The rollback flag doesn't work outside of an 'atomic' block." 

458 ) 

459 return self.needs_rollback 

460 

461 def set_rollback(self, rollback): 

462 """ 

463 Set or unset the "needs rollback" flag -- for *advanced use* only. 

464 """ 

465 if not self.in_atomic_block: 465 ↛ 466line 465 didn't jump to line 466, because the condition on line 465 was never true

466 raise TransactionManagementError( 

467 "The rollback flag doesn't work outside of an 'atomic' block." 

468 ) 

469 self.needs_rollback = rollback 

470 

471 def validate_no_atomic_block(self): 

472 """Raise an error if an atomic block is active.""" 

473 if self.in_atomic_block: 473 ↛ 474line 473 didn't jump to line 474, because the condition on line 473 was never true

474 raise TransactionManagementError( 

475 "This is forbidden when an 'atomic' block is active." 

476 ) 

477 

478 def validate_no_broken_transaction(self): 

479 if self.needs_rollback: 479 ↛ 480line 479 didn't jump to line 480, because the condition on line 479 was never true

480 raise TransactionManagementError( 

481 "An error occurred in the current transaction. You can't " 

482 "execute queries until the end of the 'atomic' block." 

483 ) 

484 

485 # ##### Foreign key constraints checks handling ##### 

486 

487 @contextmanager 

488 def constraint_checks_disabled(self): 

489 """ 

490 Disable foreign key constraint checking. 

491 """ 

492 disabled = self.disable_constraint_checking() 

493 try: 

494 yield 

495 finally: 

496 if disabled: 

497 self.enable_constraint_checking() 

498 

499 def disable_constraint_checking(self): 

500 """ 

501 Backends can implement as needed to temporarily disable foreign key 

502 constraint checking. Should return True if the constraints were 

503 disabled and will need to be reenabled. 

504 """ 

505 return False 

506 

507 def enable_constraint_checking(self): 

508 """ 

509 Backends can implement as needed to re-enable foreign key constraint 

510 checking. 

511 """ 

512 pass 

513 

514 def check_constraints(self, table_names=None): 

515 """ 

516 Backends can override this method if they can apply constraint 

517 checking (e.g. via "SET CONSTRAINTS ALL IMMEDIATE"). Should raise an 

518 IntegrityError if any invalid foreign key references are encountered. 

519 """ 

520 pass 

521 

522 # ##### Connection termination handling ##### 

523 

524 def is_usable(self): 

525 """ 

526 Test if the database connection is usable. 

527 

528 This method may assume that self.connection is not None. 

529 

530 Actual implementations should take care not to raise exceptions 

531 as that may prevent Django from recycling unusable connections. 

532 """ 

533 raise NotImplementedError( 

534 "subclasses of BaseDatabaseWrapper may require an is_usable() method" 

535 ) 

536 

537 def close_if_unusable_or_obsolete(self): 

538 """ 

539 Close the current connection if unrecoverable errors have occurred 

540 or if it outlived its maximum age. 

541 """ 

542 if self.connection is not None: 

543 # If the application didn't restore the original autocommit setting, 

544 # don't take chances, drop the connection. 

545 if self.get_autocommit() != self.settings_dict["AUTOCOMMIT"]: 

546 self.close() 

547 return 

548 

549 # If an exception other than DataError or IntegrityError occurred 

550 # since the last commit / rollback, check if the connection works. 

551 if self.errors_occurred: 

552 if self.is_usable(): 

553 self.errors_occurred = False 

554 else: 

555 self.close() 

556 return 

557 

558 if self.close_at is not None and time.monotonic() >= self.close_at: 

559 self.close() 

560 return 

561 

562 # ##### Thread safety handling ##### 

563 

564 @property 

565 def allow_thread_sharing(self): 

566 with self._thread_sharing_lock: 

567 return self._thread_sharing_count > 0 

568 

569 def inc_thread_sharing(self): 

570 with self._thread_sharing_lock: 

571 self._thread_sharing_count += 1 

572 

573 def dec_thread_sharing(self): 

574 with self._thread_sharing_lock: 

575 if self._thread_sharing_count <= 0: 

576 raise RuntimeError( 

577 "Cannot decrement the thread sharing count below zero." 

578 ) 

579 self._thread_sharing_count -= 1 

580 

581 def validate_thread_sharing(self): 

582 """ 

583 Validate that the connection isn't accessed by another thread than the 

584 one which originally created it, unless the connection was explicitly 

585 authorized to be shared between threads (via the `inc_thread_sharing()` 

586 method). Raise an exception if the validation fails. 

587 """ 

588 if not (self.allow_thread_sharing or self._thread_ident == _thread.get_ident()): 588 ↛ 589line 588 didn't jump to line 589, because the condition on line 588 was never true

589 raise DatabaseError( 

590 "DatabaseWrapper objects created in a " 

591 "thread can only be used in that same thread. The object " 

592 "with alias '%s' was created in thread id %s and this is " 

593 "thread id %s." % (self.alias, self._thread_ident, _thread.get_ident()) 

594 ) 

595 

596 # ##### Miscellaneous ##### 

597 

598 def prepare_database(self): 

599 """ 

600 Hook to do any database check or preparation, generally called before 

601 migrating a project or an app. 

602 """ 

603 pass 

604 

605 @cached_property 

606 def wrap_database_errors(self): 

607 """ 

608 Context manager and decorator that re-throws backend-specific database 

609 exceptions using Django's common wrappers. 

610 """ 

611 return DatabaseErrorWrapper(self) 

612 

613 def chunked_cursor(self): 

614 """ 

615 Return a cursor that tries to avoid caching in the database (if 

616 supported by the database), otherwise return a regular cursor. 

617 """ 

618 return self.cursor() 

619 

620 def make_debug_cursor(self, cursor): 

621 """Create a cursor that logs all queries in self.queries_log.""" 

622 return utils.CursorDebugWrapper(cursor, self) 

623 

624 def make_cursor(self, cursor): 

625 """Create a cursor without debug logging.""" 

626 return utils.CursorWrapper(cursor, self) 

627 

628 @contextmanager 

629 def temporary_connection(self): 

630 """ 

631 Context manager that ensures that a connection is established, and 

632 if it opened one, closes it to avoid leaving a dangling connection. 

633 This is useful for operations outside of the request-response cycle. 

634 

635 Provide a cursor: with self.temporary_connection() as cursor: ... 

636 """ 

637 must_close = self.connection is None 

638 try: 

639 with self.cursor() as cursor: 

640 yield cursor 

641 finally: 

642 if must_close: 642 ↛ 643line 642 didn't jump to line 643, because the condition on line 642 was never true

643 self.close() 

644 

645 @contextmanager 

646 def _nodb_cursor(self): 

647 """ 

648 Return a cursor from an alternative connection to be used when there is 

649 no need to access the main database, specifically for test db 

650 creation/deletion. This also prevents the production database from 

651 being exposed to potential child threads while (or after) the test 

652 database is destroyed. Refs #10868, #17786, #16969. 

653 """ 

654 conn = self.__class__({**self.settings_dict, "NAME": None}, alias=NO_DB_ALIAS) 

655 try: 

656 with conn.cursor() as cursor: 

657 yield cursor 

658 finally: 

659 conn.close() 

660 

661 def schema_editor(self, *args, **kwargs): 

662 """ 

663 Return a new instance of this backend's SchemaEditor. 

664 """ 

665 if self.SchemaEditorClass is None: 665 ↛ 666line 665 didn't jump to line 666, because the condition on line 665 was never true

666 raise NotImplementedError( 

667 "The SchemaEditorClass attribute of this database wrapper is still None" 

668 ) 

669 return self.SchemaEditorClass(self, *args, **kwargs) 

670 

671 def on_commit(self, func): 

672 if not callable(func): 

673 raise TypeError("on_commit()'s callback must be a callable.") 

674 if self.in_atomic_block: 

675 # Transaction in progress; save for execution on commit. 

676 self.run_on_commit.append((set(self.savepoint_ids), func)) 

677 elif not self.get_autocommit(): 

678 raise TransactionManagementError( 

679 "on_commit() cannot be used in manual transaction management" 

680 ) 

681 else: 

682 # No transaction in progress and in autocommit mode; execute 

683 # immediately. 

684 func() 

685 

686 def run_and_clear_commit_hooks(self): 

687 self.validate_no_atomic_block() 

688 current_run_on_commit = self.run_on_commit 

689 self.run_on_commit = [] 

690 while current_run_on_commit: 690 ↛ 691line 690 didn't jump to line 691, because the condition on line 690 was never true

691 sids, func = current_run_on_commit.pop(0) 

692 func() 

693 

694 @contextmanager 

695 def execute_wrapper(self, wrapper): 

696 """ 

697 Return a context manager under which the wrapper is applied to suitable 

698 database query executions. 

699 """ 

700 self.execute_wrappers.append(wrapper) 

701 try: 

702 yield 

703 finally: 

704 self.execute_wrappers.pop() 

705 

706 def copy(self, alias=None): 

707 """ 

708 Return a copy of this connection. 

709 

710 For tests that require two connections to the same database. 

711 """ 

712 settings_dict = copy.deepcopy(self.settings_dict) 

713 if alias is None: 

714 alias = self.alias 

715 return type(self)(settings_dict, alias)