1import re
2from typing import TYPE_CHECKING
3
4from .patterns import RegexPattern, RoutePattern, URLPattern
5from .resolvers import (
6 URLResolver,
7)
8
9if TYPE_CHECKING:
10 from plain.views import View
11
12
13class Router:
14 """
15 Base class for defining url patterns.
16
17 A namespace is required, and generally recommended,
18 except for the root router in app.urls where it is typically "".
19 """
20
21 namespace: str
22 urls: list
23
24
25def include(
26 route: str | re.Pattern, router_or_urls: list | tuple | str | type[Router]
27) -> URLResolver:
28 """
29 Include URLs from another module or a nested list of URL patterns.
30 """
31 if isinstance(route, str):
32 pattern = RoutePattern(route, is_endpoint=False)
33 elif isinstance(route, re.Pattern):
34 pattern = RegexPattern(route.pattern, is_endpoint=False)
35 else:
36 raise TypeError("include() route must be a string or regex")
37
38 if isinstance(router_or_urls, list | tuple):
39 # We were given an explicit list of sub-patterns,
40 # so we generate a router for it
41 class _IncludeRouter(Router):
42 namespace = ""
43 urls = router_or_urls
44
45 return URLResolver(pattern=pattern, router=_IncludeRouter())
46 elif isinstance(router_or_urls, type) and issubclass(router_or_urls, Router):
47 router_class = router_or_urls
48 router = router_class()
49
50 return URLResolver(
51 pattern=pattern,
52 router=router,
53 )
54 else:
55 raise TypeError(
56 f"include() urls must be a list, tuple, or Router class (not a Router() instance): {router_or_urls}"
57 )
58
59
60def path(route: str | re.Pattern, view: type["View"], *, name: str = "") -> URLPattern:
61 """
62 Standard URL with a view.
63 """
64 from plain.views import View
65
66 if isinstance(route, str):
67 pattern = RoutePattern(route, name=name, is_endpoint=True)
68 elif isinstance(route, re.Pattern):
69 pattern = RegexPattern(route.pattern, name=name, is_endpoint=True)
70 else:
71 raise TypeError("path() route must be a string or regex")
72
73 # You can't pass a View() instance to path()
74 if isinstance(view, View):
75 view_cls_name = view.__class__.__name__
76 raise TypeError(
77 f"view must be a callable, pass {view_cls_name} or {view_cls_name}.as_view(*args, **kwargs), not "
78 f"{view_cls_name}()."
79 )
80
81 # You typically pass a View class and we call as_view() for you
82 if isinstance(view, type) and issubclass(view, View):
83 return URLPattern(pattern=pattern, view=view.as_view(), name=name)
84
85 # If you called View.as_view() yourself (or technically any callable)
86 if callable(view):
87 return URLPattern(pattern=pattern, view=view, name=name)
88
89 raise TypeError("view must be a View class or View.as_view()")