Coverage for fastblocks/applications.py: 41%
175 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
1import typing as t
2from platform import system
4from acb.config import AdapterBase
5from acb.depends import depends
6from starception import add_link_template, set_editor
7from starlette.applications import Starlette
8from starlette.middleware import Middleware
9from starlette.middleware.errors import ServerErrorMiddleware
10from starlette.middleware.exceptions import ExceptionMiddleware
11from starlette.types import ASGIApp, ExceptionHandler, Lifespan
13from .initializers import ApplicationInitializer
14from .middleware import MiddlewarePosition
17class FastBlocksSettings:
18 def __init_subclass__(cls, **kwargs: t.Any) -> None:
19 if AdapterBase not in cls.__bases__:
20 cls.__bases__ = (AdapterBase, *cls.__bases__)
21 super().__init_subclass__(**kwargs)
24AppType = t.TypeVar("AppType", bound="FastBlocks")
26match system():
27 case "Windows":
28 add_link_template("pycharm", "pycharm64.exe --line {lineno} {path}")
29 case "Darwin":
30 add_link_template("pycharm", "pycharm --line {lineno} {path}")
31 case "Linux":
32 add_link_template("pycharm", "pycharm.sh --line {lineno} {path}")
33 case _:
34 ...
37class MiddlewareManager:
38 def __init__(self) -> None:
39 self._system_middleware: dict[MiddlewarePosition, t.Any] = {}
40 self._middleware_stack_cache: list[Middleware] | None = None
41 self.user_middleware: list[Middleware] = []
43 def add_user_middleware(
44 self,
45 middleware_class: t.Any,
46 *args: t.Any,
47 **kwargs: t.Any,
48 ) -> None:
49 position = kwargs.pop("position", None)
51 middleware = Middleware(middleware_class, *args, **kwargs)
53 if not hasattr(self, "user_middleware"):
54 self.user_middleware = []
56 if position is not None and isinstance(position, int):
57 self.user_middleware.insert(position, middleware)
58 else:
59 self.user_middleware.append(middleware)
61 self._middleware_stack_cache = None
63 def add_system_middleware(
64 self,
65 middleware_class: t.Any,
66 position: MiddlewarePosition,
67 **kwargs: t.Any,
68 ) -> None:
69 self._system_middleware[position] = (middleware_class, kwargs)
70 self._middleware_stack_cache = None
72 def get_middleware_stack(self) -> dict[str, t.Any]:
73 return {
74 "user_middleware": [
75 self._extract_middleware_info(middleware)
76 for middleware in self.user_middleware
77 ],
78 "system_middleware": {
79 pos.name: self._extract_middleware_info(middleware)
80 for pos, middleware in self._system_middleware.items()
81 },
82 }
84 def _extract_middleware_info(self, middleware: t.Any) -> dict[str, t.Any]:
85 if isinstance(middleware, Middleware):
86 return {
87 "class": getattr(middleware.cls, "__name__", str(middleware.cls)),
88 "args": middleware.args,
89 "kwargs": middleware.kwargs,
90 }
91 if isinstance(middleware, tuple) and len(middleware) >= 2:
92 cls, kwargs = middleware[0], middleware[1]
93 return {
94 "class": cls.__name__ if hasattr(cls, "__name__") else str(cls),
95 "kwargs": kwargs,
96 }
97 return {
98 "class": middleware.__class__.__name__,
99 "raw": str(middleware),
100 }
103class FastBlocks(Starlette):
104 middleware_manager: MiddlewareManager
105 templates: t.Any
106 models: t.Any
107 _middleware_position_map: dict[MiddlewarePosition, int]
109 def __init__(
110 self,
111 middleware: t.Sequence[Middleware] | None = None,
112 exception_handlers: t.Mapping[t.Any, ExceptionHandler] | None = None,
113 lifespan: Lifespan["t.Self"] | None = None,
114 config: t.Any | None = None,
115 logger: t.Any | None = None,
116 ) -> None:
117 initializer = ApplicationInitializer(
118 self,
119 middleware=middleware,
120 exception_handlers=exception_handlers,
121 lifespan=lifespan,
122 config=config,
123 logger=logger,
124 )
126 object.__setattr__(self, "middleware_manager", MiddlewareManager())
128 self._middleware_position_map = {pos: pos.value for pos in MiddlewarePosition}
129 self.templates = None
130 self.models = None
132 initializer.initialize()
134 set_editor("pycharm")
136 def add_middleware(
137 self,
138 middleware_class: t.Any,
139 *args: t.Any,
140 **kwargs: t.Any,
141 ) -> None:
142 self.middleware_manager.add_user_middleware(middleware_class, *args, **kwargs)
144 @property
145 def user_middleware(self) -> list[Middleware]:
146 return self.middleware_manager.user_middleware
148 @user_middleware.setter
149 def user_middleware(self, value: list[Middleware]) -> None:
150 self.middleware_manager.user_middleware = value
152 @property
153 def _system_middleware(self) -> dict[MiddlewarePosition, t.Any]:
154 return self.middleware_manager._system_middleware
156 @_system_middleware.setter
157 def _system_middleware(self, value: dict[MiddlewarePosition, t.Any]) -> None:
158 self.middleware_manager._system_middleware = value
160 @property
161 def _middleware_stack_cache(self) -> list[Middleware] | None:
162 return self.middleware_manager._middleware_stack_cache
164 @_middleware_stack_cache.setter
165 def _middleware_stack_cache(self, value: list[Middleware] | None) -> None:
166 self.middleware_manager._middleware_stack_cache = value
168 def add_system_middleware(
169 self,
170 middleware_class: type,
171 *,
172 position: MiddlewarePosition,
173 **options: t.Any,
174 ) -> None:
175 self.middleware_manager.add_system_middleware(
176 middleware_class,
177 position,
178 **options,
179 )
181 def _extract_middleware_info(self, middleware: t.Any) -> tuple[str, type] | None:
182 try:
183 if hasattr(middleware, "cls"):
184 cls = middleware.cls
185 elif isinstance(middleware, tuple) and len(middleware) > 0:
186 cls = middleware[0]
188 return None
189 cls_name = str(getattr(cls, "__name__", cls))
190 return cls_name, cls
191 except (AttributeError, IndexError, TypeError):
192 return None
194 def _get_system_middleware_with_overrides(self) -> list[t.Any]:
195 from .middleware import middlewares
197 modified_system_middleware = middlewares().copy()
198 for position, middleware in self._system_middleware.items():
199 position_index = position.value
200 if 0 <= position_index < len(modified_system_middleware):
201 modified_system_middleware[position_index] = middleware
202 else:
203 modified_system_middleware.append(middleware)
205 return modified_system_middleware
207 def get_middleware_stack(self) -> list[tuple[str, type]]:
208 middleware_list = [("ExceptionMiddleware", ExceptionMiddleware)]
209 system_middleware = self._get_system_middleware_with_overrides()
210 for middleware in system_middleware:
211 info = self._extract_middleware_info(middleware)
212 if info:
213 middleware_list.append(info)
214 for middleware in self.user_middleware:
215 info = self._extract_middleware_info(middleware)
216 if info:
217 middleware_list.extend(
218 (
219 info,
220 (
221 "ServerErrorMiddleware",
222 t.cast("type", ServerErrorMiddleware),
223 ),
224 ),
225 )
226 return middleware_list
228 def _get_dependencies(self, config: t.Any, logger: t.Any) -> tuple[t.Any, t.Any]:
229 if config is None:
230 config = depends.get("config")
231 if logger is None:
232 try:
233 logger = depends.get("logger")
234 except Exception:
235 logger = None
236 return config, logger
238 def _separate_exception_handlers(
239 self,
240 ) -> tuple[t.Any, dict[t.Any, ExceptionHandler]]:
241 error_handler = None
242 exception_handlers: dict[t.Any, ExceptionHandler] = {}
243 for key, value in self.exception_handlers.items():
244 if key in (500, Exception):
245 error_handler = value
246 else:
247 exception_handlers[key] = value
248 return error_handler, exception_handlers
250 def _build_base_middleware_list(self, error_handler: t.Any) -> list[Middleware]:
251 middleware_list = [
252 Middleware(
253 ServerErrorMiddleware,
254 handler=error_handler,
255 debug=self.debug,
256 ),
257 ]
258 middleware_list.extend(self.user_middleware)
259 return middleware_list
261 def _apply_system_middleware_overrides(
262 self,
263 system_middleware: list[t.Any],
264 logger: t.Any,
265 ) -> list[t.Any]:
266 if not (hasattr(self, "_system_middleware") and self._system_middleware):
267 return system_middleware
269 modified_system_middleware = system_middleware.copy()
271 for position, middleware in self._system_middleware.items():
272 position_index = self._middleware_position_map[position]
274 if 0 <= position_index < len(modified_system_middleware):
275 if logger:
276 logger.debug(f"Replacing middleware at position {position.name}")
277 modified_system_middleware[position_index] = middleware
278 else:
279 if logger:
280 logger.debug(f"Adding middleware at position {position.name}")
281 modified_system_middleware.append(middleware)
283 return modified_system_middleware
285 def _apply_middleware_to_app(
286 self,
287 middleware_list: list[t.Any],
288 logger: t.Any,
289 ) -> ASGIApp:
290 app = self.router
291 for cls, args, kwargs in reversed(middleware_list):
292 if logger:
293 logger.debug(f"Adding middleware: {cls.__name__}")
294 app = cls(*args, app=app, **kwargs)
295 return app
297 def build_middleware_stack(
298 self,
299 config: t.Any | None = None,
300 logger: t.Any | None = None,
301 ) -> ASGIApp:
302 if self._middleware_stack_cache is not None:
303 return t.cast(t.Any, self._middleware_stack_cache) # type: ignore[no-any-return]
305 config, logger = self._get_dependencies(config, logger)
306 error_handler, exception_handlers = self._separate_exception_handlers()
308 from .middleware import middlewares
310 middleware_list = self._build_base_middleware_list(error_handler)
311 system_middleware = middlewares()
312 system_middleware = self._apply_system_middleware_overrides(
313 system_middleware,
314 logger,
315 )
317 middleware_list.extend(system_middleware)
318 middleware_list.append(
319 Middleware(
320 ExceptionMiddleware,
321 handlers=exception_handlers,
322 debug=self.debug,
323 ),
324 )
326 app = self._apply_middleware_to_app(middleware_list, logger)
328 if logger:
329 logger.info("Middleware stack built")
331 object.__setattr__(self, "_middleware_stack_cache", app)
332 return app