Repository: django/django
Issue category: Feature Request

Issue:
Clarify django.urls.set_urlconf scoping behaviour
Description
	 
django.urls.set_urlconf ​docstring mentions setting the urlconf for the current thread. However, this is backed by asgiref.local.Local, which is supposed to provide scoping features related to asyncio tasks as well. This becomes relevant, for example, when doing multi-tenancy with more than one urlconf and trying to call django.urls.reverse in an ASGI application.
I have been trying to infer what is the expected behaviour in async Django code by following the current implementation, and I found that asgiref.local.Local behaviour has changed over time (see ​https://github.com/django/asgiref/issues/473).
I assume that using asgiref.local.Local instead of threading.local hints at an intention is to give set_urlconf/get_urlconf meaningful semantics for Channels consumers or ASGI applications.
Whether the intention is to isolate set_urlconf/get_urlconf across different asyncio tasks or to only support isolation between threads, I suppose it would be useful if their behaviour was documented also for the case of asyncio code, especially given they back django.urls.reverse.


Hints:
["I'm struggling to follow what this is asking for - can you share an example of the behavior you're seeing? From what I can see, both async and sync requests handle the urlconf the same - it is the ROOT_URLCONF unless set by a middleware \u200bas documented.", 1728267595.0]
["Firstly, just for Django, set_urlconf is not public API so you shouldn't use it. Rather prefer the documented HttpRequest.urlconf attribute (\u200bdocs) to set per-tenant URLs in a middleware, if that's your strategy. Secondly, the \u200blinked asgiref issue (#473) is in progress, and should be resolved shortly. (Setting locals has been leaking out of asyncio tasks since the recent v3.8, which is a regression. You can pin to v3.7.2 pending the next release.) It's not a Django issue. If you need further advice please see TicketClosingReasons/UseSupportChannels.", 1728268240.0]
['From what I could see, "Set the URLconf for the current thread" in set_urlconf\'s docstring looks like a leftover from the pre-async times and could become "Set the URLconf for the current thread or asyncio task". I understand it\'s not a public API and indeed I only set request.urlconf in my code. We spotted the docstring while trying to double check the validity of what we were designing on the async case, and had some extra digging to do to reassure ourselves we were still doing the right thing, hence this issue. I\'m surprised the ticket got closed, but fair enough.', 1728269648.0]
['Yes, OK, just changing the docstring would be fine.', 1728270267.0]
['\u200bPR', 1728270548.0]
['In d876be79: Fixed #35807 -- Mentioned async case for internal get/set urlconf helpers.', 1728274635.0]

Patch:
diff --git a/django/urls/base.py b/django/urls/base.py
index 753779c75b46..bb40ba222436 100644
--- a/django/urls/base.py
+++ b/django/urls/base.py
@@ -127,8 +127,9 @@ def clear_script_prefix():
 
 def set_urlconf(urlconf_name):
     """
-    Set the URLconf for the current thread (overriding the default one in
-    settings). If urlconf_name is None, revert back to the default.
+    Set the URLconf for the current thread or asyncio task (overriding the
+    default one in settings). If urlconf_name is None, revert back to the
+    default.
     """
     if urlconf_name:
         _urlconfs.value = urlconf_name
@@ -139,8 +140,8 @@ def set_urlconf(urlconf_name):
 
 def get_urlconf(default=None):
     """
-    Return the root URLconf to use for the current thread if it has been
-    changed from the default one.
+    Return the root URLconf to use for the current thread or asyncio task if it
+    has been changed from the default one.
     """
     return getattr(_urlconfs, "value", default)
 


Test patch: