Opened 3 weeks ago

Closed 6 days ago

#36943 closed Cleanup/optimization (fixed)

Autoreloader hides exceptions from urlconf module

Reported by: Austin Morton Owned by: Varun Kasyap Pentamaraju
Component: Utilities Version: 6.0
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Initialize an empty project

$ uv init . && uv add django && uv run django-admin startproject repro .

Initialized project `django-repro` at `/Users/amorton/src/django-repro`
Using CPython 3.14.0
Creating virtual environment at: .venv
Resolved 5 packages in 132ms
Installed 3 packages in 97ms
 + asgiref==3.11.1
 + django==6.0.2
 + sqlparse==0.5.5

Edit urls.py to the following:

from django.urls import include, path, register_converter


class MyConverter:
    # forgot to specify regex
    # regex = "[^/]+"

    def to_python(self, value):
        return value

    def to_url(self, value):
        return value


register_converter(MyConverter, "mine")

urlpatterns = [
    path("<mine:foo>", include([])),
]

Run the development server:

$ uv run manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

Exception in thread django-main-thread:
Traceback (most recent call last):
   ...
  File "/Users/amorton/src/django-repro/repro/urls.py", line 15, in <module>
    register_converter(MyConverter, "mine")
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/Users/amorton/src/django-repro/.venv/lib/python3.14/site-packages/django/urls/converters.py", line 57, in register_converter
    raise ValueError(f"Converter {type_name!r} is already registered.")
ValueError: Converter 'mine' is already registered.

What's going on here is that the original exception is being swallowed in django.utils.autoreload.BaseReloader.run.
The exception gets thrown after the call to register_converter succeeds, but before the URLResolver.urlconf_module cached property returns.
The urlconf module is executed a second time later, and the original exception is never shown to the user.

Ultimately this was a user-error on my part, but was very difficult to debug.

Change History (7)

comment:1 by Jacob Walls, 3 weeks ago

Component: Core (URLs)Utilities
Summary: "Converter is already registered" incorrectly raised when registering a malformed converter.Autoreloader hides exceptions from urlconf module
Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

Thanks for the report. We could at least include the original error in the cause without altering the behavior.

Something like this looks promising, would you like to polish and submit a PR?

  • django/utils/autoreload.py

    diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
    index 99812979d7..9e5edaee57 100644
    a b logger = logging.getLogger("django.utils.autoreload")  
    3333# file paths to allow watching them in the future.
    3434_error_files = []
    3535_exception = None
     36# A nice comment.
     37_url_module_exception = None
    3638
    3739try:
    3840    import termios
    def check_errors(fn):  
    6264        global _exception
    6365        try:
    6466            fn(*args, **kwargs)
    65         except Exception:
     67        except Exception as e:
    6668            _exception = sys.exc_info()
    6769
    6870            et, ev, tb = _exception
    def check_errors(fn):  
    7577
    7678            if filename not in _error_files:
    7779                _error_files.append(filename)
     80            if _url_module_exception is not None:
     81                raise e from _url_module_exception
    7882
    79             raise
     83            raise e
    8084
    8185    return wrapper
    8286
    class BaseReloader:  
    339343            return False
    340344
    341345    def run(self, django_main_thread):
     346        global _url_module_exception
    342347        logger.debug("Waiting for apps ready_event.")
    343348        self.wait_for_apps_ready(apps, django_main_thread)
    344349        from django.urls import get_resolver
    class BaseReloader:  
    347352        # reloader starts by accessing the urlconf_module property.
    348353        try:
    349354            get_resolver().urlconf_module
    350         except Exception:
     355        except Exception as e:
    351356            # Loading the urlconf can result in errors during development.
    352             # If this occurs then swallow the error and continue.
    353             pass
     357            # If this occurs then store the error and continue.
     358            _url_module_exception = e
    354359        logger.debug("Apps ready_event triggered. Sending autoreload_started signal.")
    355360        autoreload_started.send(sender=self)
    356361        self.run_loop()

comment:2 by Varun Kasyap Pentamaraju, 3 weeks ago

Owner: set to Varun Kasyap Pentamaraju
Status: newassigned

comment:3 by Varun Kasyap Pentamaraju, 13 days ago

Has patch: set

comment:4 by Natalia Bidart, 13 days ago

Needs tests: set
Patch needs improvement: set

comment:5 by Varun Kasyap Pentamaraju, 7 days ago

Needs tests: unset
Patch needs improvement: unset

comment:6 by Jacob Walls, 7 days ago

Triage Stage: AcceptedReady for checkin

comment:7 by Jacob Walls <jacobtylerwalls@…>, 6 days ago

Resolution: fixed
Status: assignedclosed

In 3483bfc:

Fixed #36943 -- Preserved any exception from URLconf module in autoreloader.

Co-authored-by: Jacob Walls <jacobtylerwalls@…>

Note: See TracTickets for help on using tickets.
Back to Top