1from threading import local
2
3from plain.utils.functional import lazy
4
5from .exceptions import NoReverseMatch, Resolver404
6from .resolvers import _get_cached_resolver, get_ns_resolver, get_resolver
7
8# Overridden URLconfs for each thread are stored here.
9_urlconfs = local()
10
11
12def resolve(path, urlconf=None):
13 if urlconf is None:
14 urlconf = get_urlconf()
15 return get_resolver(urlconf).resolve(path)
16
17
18def reverse(viewname, urlconf=None, args=None, kwargs=None, using_namespace=None):
19 if urlconf is None:
20 urlconf = get_urlconf()
21 resolver = get_resolver(urlconf)
22 args = args or []
23 kwargs = kwargs or {}
24
25 if not isinstance(viewname, str):
26 view = viewname
27 else:
28 *path, view = viewname.split(":")
29
30 if using_namespace:
31 current_path = using_namespace.split(":")
32 current_path.reverse()
33 else:
34 current_path = None
35
36 resolved_path = []
37 ns_pattern = ""
38 ns_converters = {}
39 for ns in path:
40 current_ns = current_path.pop() if current_path else None
41 # Lookup the name to see if it could be an app identifier.
42 try:
43 app_list = resolver.app_dict[ns]
44 # Yes! Path part matches an app in the current Resolver.
45 if current_ns and current_ns in app_list:
46 # If we are reversing for a particular app, use that
47 # namespace.
48 ns = current_ns
49 elif ns not in app_list:
50 # The name isn't shared by one of the instances (i.e.,
51 # the default) so pick the first instance as the default.
52 ns = app_list[0]
53 except KeyError:
54 pass
55
56 if ns != current_ns:
57 current_path = None
58
59 try:
60 extra, resolver = resolver.namespace_dict[ns]
61 resolved_path.append(ns)
62 ns_pattern += extra
63 ns_converters.update(resolver.pattern.converters)
64 except KeyError as key:
65 if resolved_path:
66 raise NoReverseMatch(
67 "{} is not a registered namespace inside '{}'".format(
68 key, ":".join(resolved_path)
69 )
70 )
71 else:
72 raise NoReverseMatch("%s is not a registered namespace" % key)
73 if ns_pattern:
74 resolver = get_ns_resolver(
75 ns_pattern, resolver, tuple(ns_converters.items())
76 )
77
78 return resolver.reverse(view, *args, **kwargs)
79
80
81reverse_lazy = lazy(reverse, str)
82
83
84def clear_url_caches():
85 _get_cached_resolver.cache_clear()
86 get_ns_resolver.cache_clear()
87
88
89def set_urlconf(urlconf_name):
90 """
91 Set the URLconf for the current thread (overriding the default one in
92 settings). If urlconf_name is None, revert back to the default.
93 """
94 if urlconf_name:
95 _urlconfs.value = urlconf_name
96 else:
97 if hasattr(_urlconfs, "value"):
98 del _urlconfs.value
99
100
101def get_urlconf(default=None):
102 """
103 Return the root URLconf to use for the current thread if it has been
104 changed from the default one.
105 """
106 return getattr(_urlconfs, "value", default)
107
108
109def is_valid_path(path, urlconf=None):
110 """
111 Return the ResolverMatch if the given path resolves against the default URL
112 resolver, False otherwise. This is a convenience method to make working
113 with "is this a match?" cases easier, avoiding try...except blocks.
114 """
115 try:
116 return resolve(path, urlconf)
117 except Resolver404:
118 return False