diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py index 3f3d29874a..23015a57a3 100644 --- a/django/db/backends/base/base.py +++ b/django/db/backends/base/base.py @@ -741,11 +741,8 @@ class BaseDatabaseWrapper: try: func() except Exception as e: - logger.error( - f"Error calling {func.__qualname__} in on_commit() (%s).", - e, - exc_info=True, - ) + name = getattr(func, "__qualname__", func) + logger.exception("Error calling %s in on_commit() (%s).", name, e) else: func() @@ -759,11 +756,11 @@ class BaseDatabaseWrapper: try: func() except Exception as e: - logger.error( - f"Error calling {func.__qualname__} in on_commit() during " - f"transaction (%s).", + name = getattr(func, "__qualname__", func) + logger.exception( + "Error calling %s in on_commit() during transaction (%s).", + name, e, - exc_info=True, ) else: func() diff --git a/django/test/testcases.py b/django/test/testcases.py index c587f770a6..c56fa52806 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1523,11 +1523,11 @@ class TestCase(TransactionTestCase): try: callback() except Exception as e: - logger.error( - f"Error calling {callback.__qualname__} in " - f"on_commit() (%s).", + name = getattr(callback, "__qualname__", callback) + logger.exception( + "Error calling %s in on_commit() (%s).", + name, e, - exc_info=True, ) else: callback() diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 70cca3d441..92f13ab81c 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -4,6 +4,7 @@ import threading import traceback import unittest import warnings +from functools import partial from io import StringIO from unittest import mock @@ -2145,6 +2146,33 @@ class CaptureOnCommitCallbacksTests(TestCase): self.assertIsInstance(raised_exception, MyException) self.assertEqual(str(raised_exception), "robust callback") + def test_execute_robust_with_callback_as_partial(self): + class MyException(Exception): + pass + + def hook(): + self.callback_called = True + raise MyException("robust callback") + + hook_partial = partial(hook) + + with self.assertLogs("django.test", "ERROR") as cm: + with self.captureOnCommitCallbacks(execute=True) as callbacks: + transaction.on_commit(hook_partial, robust=True) + + self.assertEqual(len(callbacks), 1) + self.assertIs(self.callback_called, True) + + log_record = cm.records[0] + self.assertEqual( + log_record.getMessage(), + f"Error calling {hook_partial} in on_commit() (robust callback).", + ) + self.assertIsNotNone(log_record.exc_info) + raised_exception = log_record.exc_info[1] + self.assertIsInstance(raised_exception, MyException) + self.assertEqual(str(raised_exception), "robust callback") + class DisallowedDatabaseQueriesTests(SimpleTestCase): def test_disallowed_database_connections(self): diff --git a/tests/transaction_hooks/tests.py b/tests/transaction_hooks/tests.py index 938e92575f..5390165072 100644 --- a/tests/transaction_hooks/tests.py +++ b/tests/transaction_hooks/tests.py @@ -1,3 +1,5 @@ +from functools import partial + from django.db import connection, transaction from django.test import TransactionTestCase, skipUnlessDBFeature @@ -63,6 +65,28 @@ class TestConnectionOnCommit(TransactionTestCase): self.assertIsInstance(raised_exception, ForcedError) self.assertEqual(str(raised_exception), "robust callback") + def test_robust_if_no_transaction_with_callback_as_partial(self): + def robust_callback(): + raise ForcedError("robust callback") + + robust_callback_partial = partial(robust_callback) + + with self.assertLogs("django.db.backends.base", "ERROR") as cm: + transaction.on_commit(robust_callback_partial, robust=True) + self.do(1) + + self.assertDone([1]) + log_record = cm.records[0] + self.assertEqual( + log_record.getMessage(), + f"Error calling {robust_callback_partial} " + f"in on_commit() (robust callback).", + ) + self.assertIsNotNone(log_record.exc_info) + raised_exception = log_record.exc_info[1] + self.assertIsInstance(raised_exception, ForcedError) + self.assertEqual(str(raised_exception), "robust callback") + def test_robust_transaction(self): def robust_callback(): raise ForcedError("robust callback") @@ -84,6 +108,29 @@ class TestConnectionOnCommit(TransactionTestCase): self.assertIsInstance(raised_exception, ForcedError) self.assertEqual(str(raised_exception), "robust callback") + def test_robust_transaction_with_callback_as_partial(self): + def robust_callback(): + raise ForcedError("robust callback") + + robust_callback_partial = partial(robust_callback) + + with self.assertLogs("django.db.backends", "ERROR") as cm: + with transaction.atomic(): + transaction.on_commit(robust_callback_partial, robust=True) + self.do(1) + + self.assertDone([1]) + log_record = cm.records[0] + self.assertEqual( + log_record.getMessage(), + f"Error calling {robust_callback_partial} in on_commit() during " + "transaction (robust callback).", + ) + self.assertIsNotNone(log_record.exc_info) + raised_exception = log_record.exc_info[1] + self.assertIsInstance(raised_exception, ForcedError) + self.assertEqual(str(raised_exception), "robust callback") + def test_delays_execution_until_after_transaction_commit(self): with transaction.atomic(): self.do(1)