1from __future__ import annotations
2
3from typing import Any
4
5from plain.runtime import settings
6from plain.utils.functional import lazy
7
8from .exceptions import NoReverseMatch
9from .resolvers import get_ns_resolver, get_resolver
10
11
12def reverse(url_name: str, *args: Any, **kwargs: Any) -> str:
13 resolver = get_resolver()
14
15 *path, view = url_name.split(":")
16
17 current_path = None
18
19 resolved_path = []
20 ns_pattern = ""
21 ns_converters = {}
22 for ns in path:
23 current_ns = current_path.pop() if current_path else None
24
25 if ns != current_ns:
26 current_path = None
27
28 try:
29 extra, resolver = resolver.namespace_dict[ns]
30 resolved_path.append(ns)
31 ns_pattern += extra
32 ns_converters.update(resolver.pattern.converters)
33 except KeyError as key:
34 if resolved_path:
35 raise NoReverseMatch(
36 "{} is not a registered namespace inside '{}'".format(
37 key, ":".join(resolved_path)
38 )
39 )
40 else:
41 raise NoReverseMatch(f"{key} is not a registered namespace")
42 if ns_pattern:
43 resolver = get_ns_resolver(ns_pattern, resolver, tuple(ns_converters.items()))
44
45 return resolver.reverse(view, *args, **kwargs)
46
47
48reverse_lazy = lazy(reverse, str)
49
50
51def absolute_url(path: str) -> str:
52 """Convert a relative path to an absolute URL using the BASE_URL setting."""
53 if not settings.BASE_URL:
54 raise ValueError(
55 "The BASE_URL setting must be configured to generate absolute URLs."
56 )
57
58 base = settings.BASE_URL.rstrip("/")
59 if path and not path.startswith("/"):
60 path = "/" + path
61
62 return base + path
63
64
65def reverse_absolute(url_name: str, *args: Any, **kwargs: Any) -> str:
66 """Reverse a URL name and return an absolute URL using the BASE_URL setting."""
67 return absolute_url(reverse(url_name, *args, **kwargs))