Plain is headed towards 1.0! Subscribe for development updates →

 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()")