Repository: django/django

Issue:
# Fixed #36943 -- Preserved original URLconf exception in autoreloader.

#### Trac ticket number
<!-- Replace XXXXX with the corresponding Trac ticket number. -->
<!-- Or delete the line and write "N/A" if this is a trivial PR. -->

ticket-36943 

#### Branch description
Earlier, the autoreloader used to hide exceptions raised while loading the URLConf.

This change preserves the original exception using exception chaining

#### AI Assistance Disclosure (REQUIRED)
<!-- Please select exactly ONE of the following: -->
- [x] **No AI tools were used** in preparing this PR.
- [ ] **If AI tools were used**, I have disclosed which ones, and fully reviewed and verified their output.

#### Checklist
- [x] This PR follows the [contribution guidelines](https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/submitting-patches/).
- [x] This PR **does not** disclose a security vulnerability (see [vulnerability reporting](https://docs.djangoproject.com/en/stable/internals/security/)).
- [x] This PR targets the `main` branch. <!-- Backports will be evaluated and done by mergers, when necessary. -->
- [x] The commit message is written in past tense, mentions the ticket number, and ends with a period (see [guidelines](https://docs.djangoproject.com/en/dev/internals/contributing/committing-code/#committing-guidelines)).
- [x] I have checked the "Has patch" ticket flag in the Trac system.
- [x] I have added or updated relevant tests.
- [ ] I have added or updated relevant docs, including release notes if applicable.
- [ ] I have attached screenshots in both light and dark modes for any UI changes.


Implementation patch:
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index 99812979d73c..13019f7214b7 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -33,6 +33,8 @@
 # file paths to allow watching them in the future.
 _error_files = []
 _exception = None
+# Exception raised while loading the URLConf.
+_url_module_exception = None
 
 try:
     import termios
@@ -62,7 +64,7 @@ def wrapper(*args, **kwargs):
         global _exception
         try:
             fn(*args, **kwargs)
-        except Exception:
+        except Exception as e:
             _exception = sys.exc_info()
 
             et, ev, tb = _exception
@@ -75,8 +77,10 @@ def wrapper(*args, **kwargs):
 
             if filename not in _error_files:
                 _error_files.append(filename)
+            if _url_module_exception is not None:
+                raise e from _url_module_exception
 
-            raise
+            raise e
 
     return wrapper
 
@@ -339,6 +343,7 @@ def wait_for_apps_ready(self, app_reg, django_main_thread):
             return False
 
     def run(self, django_main_thread):
+        global _url_module_exception
         logger.debug("Waiting for apps ready_event.")
         self.wait_for_apps_ready(apps, django_main_thread)
         from django.urls import get_resolver
@@ -347,10 +352,10 @@ def run(self, django_main_thread):
         # reloader starts by accessing the urlconf_module property.
         try:
             get_resolver().urlconf_module
-        except Exception:
+        except Exception as e:
             # Loading the urlconf can result in errors during development.
-            # If this occurs then swallow the error and continue.
-            pass
+            # If this occurs then store the error and continue.
+            _url_module_exception = e
         logger.debug("Apps ready_event triggered. Sending autoreload_started signal.")
         autoreload_started.send(sender=self)
         self.run_loop()


Test patch:
diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py
index c9e6443c6fbf..2033728da809 100644
--- a/tests/utils_tests/test_autoreload.py
+++ b/tests/utils_tests/test_autoreload.py
@@ -434,6 +434,18 @@ def test_mutates_error_files(self):
                 autoreload._exception = None
         self.assertEqual(mocked_error_files.append.call_count, 1)
 
+    def test_urlconf_exception_is_used_as_cause(self):
+        urlconf_exc = ValueError("Error")
+        fake_method = mock.MagicMock(side_effect=RuntimeError())
+        wrapped = autoreload.check_errors(fake_method)
+        with mock.patch.object(autoreload, "_url_module_exception", urlconf_exc):
+            try:
+                with self.assertRaises(RuntimeError) as cm:
+                    wrapped()
+            finally:
+                autoreload._exception = None
+        self.assertIs(cm.exception.__cause__, urlconf_exc)
+
 
 class TestRaiseLastException(SimpleTestCase):
     @mock.patch("django.utils.autoreload._exception", None)