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
« 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
9try:
10 import zoneinfo
11except ImportError:
12 from backports import zoneinfo
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
26NO_DB_ALIAS = "__no_db__"
29# RemovedInDjango50Warning
30def timezone_constructor(tzname):
31 if settings.USE_DEPRECATED_PYTZ:
32 import pytz
34 return pytz.timezone(tzname)
35 return zoneinfo.ZoneInfo(tzname)
38class BaseDatabaseWrapper:
39 """Represent a database connection."""
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
59 queries_limit = 9000
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
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
91 # Connection termination related attributes.
92 self.close_at = None
93 self.closed_in_transaction = False
94 self.errors_occurred = False
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()
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 = []
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
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 = []
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)
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
130 @cached_property
131 def timezone(self):
132 """
133 Return a tzinfo of the database connection time zone.
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.
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.
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"])
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"]
165 @property
166 def queries_logged(self):
167 return self.force_debug_cursor or settings.DEBUG
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)
178 # ##### Backend-specific methods for creating connections and cursors #####
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 )
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 )
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 )
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 )
207 # ##### Backend-specific methods for creating connections #####
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)
230 self.run_on_commit = []
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 )
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()
246 # ##### Backend-specific wrappers for PEP-249 connection methods #####
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
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))
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()
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()
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()
279 # ##### Generic wrappers for PEP-249 connection methods #####
281 @async_unsafe
282 def cursor(self):
283 """Create a cursor, opening a connection if necessary."""
284 return self._cursor()
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
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 = []
307 @async_unsafe
308 def close(self):
309 """Close the connection to the database."""
310 self.validate_thread_sharing()
311 self.run_on_commit = []
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
327 # ##### Backend-specific savepoint management methods #####
329 def _savepoint(self, sid):
330 with self.cursor() as cursor:
331 cursor.execute(self.ops.savepoint_create_sql(sid))
333 def _savepoint_rollback(self, sid):
334 with self.cursor() as cursor:
335 cursor.execute(self.ops.savepoint_rollback_sql(sid))
337 def _savepoint_commit(self, sid):
338 with self.cursor() as cursor:
339 cursor.execute(self.ops.savepoint_commit_sql(sid))
341 def _savepoint_allowed(self):
342 # Savepoints cannot be created outside a transaction
343 return self.features.uses_savepoints and not self.get_autocommit()
345 # ##### Generic savepoint management methods #####
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
357 thread_ident = _thread.get_ident()
358 tid = str(thread_ident).replace("-", "")
360 self.savepoint_state += 1
361 sid = "s%s_x%d" % (tid, self.savepoint_state)
363 self.validate_thread_sharing()
364 self._savepoint(sid)
366 return sid
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
376 self.validate_thread_sharing()
377 self._savepoint_rollback(sid)
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 ]
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
392 self.validate_thread_sharing()
393 self._savepoint_commit(sid)
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
402 # ##### Backend-specific transaction management methods #####
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 )
412 # ##### Generic transaction management methods #####
414 def get_autocommit(self):
415 """Get the autocommit state."""
416 self.ensure_connection()
417 return self.autocommit
419 def set_autocommit(
420 self, autocommit, force_begin_transaction_with_broken_autocommit=False
421 ):
422 """
423 Enable or disable autocommit.
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()
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 )
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)
447 self.autocommit = autocommit
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
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
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
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 )
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 )
485 # ##### Foreign key constraints checks handling #####
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()
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
507 def enable_constraint_checking(self):
508 """
509 Backends can implement as needed to re-enable foreign key constraint
510 checking.
511 """
512 pass
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
522 # ##### Connection termination handling #####
524 def is_usable(self):
525 """
526 Test if the database connection is usable.
528 This method may assume that self.connection is not None.
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 )
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
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
558 if self.close_at is not None and time.monotonic() >= self.close_at:
559 self.close()
560 return
562 # ##### Thread safety handling #####
564 @property
565 def allow_thread_sharing(self):
566 with self._thread_sharing_lock:
567 return self._thread_sharing_count > 0
569 def inc_thread_sharing(self):
570 with self._thread_sharing_lock:
571 self._thread_sharing_count += 1
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
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 )
596 # ##### Miscellaneous #####
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
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)
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()
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)
624 def make_cursor(self, cursor):
625 """Create a cursor without debug logging."""
626 return utils.CursorWrapper(cursor, self)
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.
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()
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()
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)
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()
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()
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()
706 def copy(self, alias=None):
707 """
708 Return a copy of this connection.
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)