Coverage for fastblocks/adapters/templates/_base.py: 76%
74 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-21 04:50 -0700
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-21 04:50 -0700
1import typing as t
3from acb import pkg_registry
4from acb.adapters import get_adapters, root_path
5from acb.config import AdapterBase, Settings
6from acb.depends import depends
7from anyio import Path as AsyncPath
8from starlette.requests import Request
9from starlette.responses import Response
12async def safe_await(func_or_value: t.Any) -> t.Any:
13 if callable(func_or_value):
14 try:
15 result = func_or_value()
16 if hasattr(result, "__await__") and callable(result.__await__): # type: ignore[misc]
17 return await t.cast("t.Awaitable[t.Any]", result)
18 return result
19 except Exception:
20 return True
21 return func_or_value
24TemplateContext: t.TypeAlias = dict[str, t.Any]
25TemplateResponse: t.TypeAlias = Response
26TemplateStr: t.TypeAlias = str
27TemplatePath: t.TypeAlias = str
28T = t.TypeVar("T")
31class TemplateRenderer(t.Protocol):
32 async def render_template(
33 self,
34 request: Request,
35 template: TemplatePath,
36 _: TemplateContext | None = None,
37 ) -> TemplateResponse: ...
40class TemplateLoader(t.Protocol):
41 async def get_template(self, name: TemplatePath) -> t.Any: ...
43 async def list_templates(self) -> list[TemplatePath]: ...
46class TemplatesBaseSettings(Settings):
47 cache_timeout: int = 300
49 @depends.inject
50 def __init__(self, config: t.Any = depends(), **values: t.Any) -> None:
51 super().__init__(**values)
52 self.cache_timeout = self.cache_timeout if config.deployed else 1
55class TemplatesProtocol(t.Protocol):
56 def get_searchpath(self, adapter: t.Any, path: AsyncPath) -> None: ...
58 async def get_searchpaths(self, adapter: t.Any) -> list[AsyncPath]: ...
60 @staticmethod
61 def get_storage_path(path: AsyncPath) -> AsyncPath: ...
63 @staticmethod
64 def get_cache_key(path: AsyncPath) -> str: ...
67class TemplatesBase(AdapterBase):
68 app: t.Any | None = None
69 admin: t.Any | None = None
70 app_searchpaths: list[AsyncPath] | None = None
71 admin_searchpaths: list[AsyncPath] | None = None
73 def get_searchpath(self, adapter: t.Any, path: AsyncPath) -> list[AsyncPath]:
74 style = getattr(self.config.app, "style", "bulma")
75 base_path = path / "base"
76 style_path = path / style
77 style_adapter_path = path / style / adapter.name
78 theme_adapter_path = style_adapter_path / "theme"
79 return [theme_adapter_path, style_adapter_path, style_path, base_path]
81 async def get_searchpaths(self, adapter: t.Any) -> list[AsyncPath]:
82 searchpaths = []
83 if callable(root_path):
84 base_root = AsyncPath(root_path())
85 else:
86 base_root = AsyncPath(root_path)
87 if adapter and hasattr(adapter, "category"):
88 searchpaths.extend(
89 self.get_searchpath(
90 adapter, base_root / "templates" / adapter.category
91 ),
92 )
93 if adapter and hasattr(adapter, "category") and adapter.category == "app":
94 for a in [
95 a
96 for a in get_adapters()
97 if a
98 and hasattr(a, "category")
99 and a.category not in ("app", "admin", "secret")
100 ]:
101 exists_result = await safe_await((a.path / "_templates").exists)
102 if exists_result:
103 searchpaths.append(a.path / "_templates")
104 for pkg in pkg_registry.get():
105 if (
106 pkg
107 and hasattr(pkg, "path")
108 and adapter
109 and hasattr(adapter, "category")
110 ):
111 searchpaths.extend(
112 self.get_searchpath(
113 adapter,
114 pkg.path / "adapters" / adapter.category / "_templates",
115 ),
116 )
117 return searchpaths
119 @staticmethod
120 def get_storage_path(path: AsyncPath) -> AsyncPath:
121 templates_path_name = "templates"
122 if templates_path_name not in path.parts:
123 templates_path_name = "_templates"
124 depth = path.parts.index(templates_path_name) - 1
125 _path = list(path.parts[depth:])
126 _path.insert(1, _path.pop(0))
127 return AsyncPath("/".join(_path))
128 depth = path.parts.index(templates_path_name)
129 return AsyncPath("/".join(path.parts[depth:]))
131 @staticmethod
132 def get_cache_key(path: AsyncPath) -> str:
133 return ":".join(path.parts)