Coverage for fastblocks/initializers.py: 20%
99 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
1"""Application initialization components for FastBlocks.
3This module contains classes responsible for initializing various aspects
4of a FastBlocks application, separating concerns from the main application class.
5"""
7import logging
8import typing as t
10from acb import register_pkg
11from acb.adapters import get_installed_adapter
12from acb.config import AdapterBase, Config
13from acb.depends import depends
14from starception import install_error_handler
15from starlette.applications import Starlette
18class ApplicationInitializer:
19 def __init__(self, app: Starlette, **kwargs: t.Any) -> None:
20 self.app = app
21 self.kwargs = kwargs
22 self.config: t.Any | None = None
23 self.logger: t.Any | None = None
24 self.depends: t.Any | None = None
25 self._acb_modules: tuple[t.Any, ...] = ()
27 def initialize(self) -> None:
28 self._load_acb_modules()
29 self._setup_dependencies()
30 self._configure_error_handling()
31 self._configure_debug_mode()
32 self._initialize_starlette()
33 self._configure_exception_handlers()
34 self._setup_models()
35 self._configure_logging()
36 self._register_event_handlers()
38 def _load_acb_modules(self) -> None:
39 try:
40 logger = depends.get("logger")
41 logger_class: type[t.Any] | None = (
42 logger.__class__ if logger is not None else None
43 )
44 from acb.logger import InterceptHandler
46 interceptor_class: type[t.Any] | None = InterceptHandler
47 except Exception:
48 logger_class = None
49 interceptor_class = None
50 self._acb_modules = (
51 register_pkg,
52 get_installed_adapter,
53 Config,
54 AdapterBase,
55 interceptor_class,
56 logger_class,
57 depends,
58 )
60 def _setup_dependencies(self) -> None:
61 self.config = (
62 self.kwargs.get("config")
63 if self.kwargs.get("config") is not None
64 else depends.get("config")
65 )
66 self.logger = (
67 self.kwargs.get("logger")
68 if self.kwargs.get("logger") is not None
69 else depends.get("logger")
70 )
71 self.depends = depends
73 def _configure_error_handling(self) -> None:
74 if not getattr(self.config, "deployed", False) or not getattr(
75 getattr(self.config, "debug", None),
76 "production",
77 False,
78 ):
79 install_error_handler()
81 def _configure_debug_mode(self) -> None:
82 debug_config = getattr(self.config, "debug", None)
83 self.app.debug = (
84 getattr(debug_config, "fastblocks", False) if debug_config else False
85 )
86 if self.logger:
87 self.logger.warning(f"Fastblocks debug: {self.app.debug}")
89 def _initialize_starlette(self) -> None:
90 Starlette.__init__(
91 self.app,
92 debug=self.app.debug,
93 routes=[],
94 middleware=self.kwargs.get("middleware")
95 if self.kwargs.get("middleware") is not None
96 else [],
97 lifespan=self.kwargs.get("lifespan"),
98 exception_handlers=self.kwargs.get("exception_handlers")
99 if self.kwargs.get("exception_handlers") is not None
100 else {},
101 )
102 middleware = self.kwargs.get("middleware")
103 self.app.user_middleware = list(middleware) if middleware is not None else []
105 def _configure_exception_handlers(self) -> None:
106 from .exceptions import handle_exception
108 exception_handlers = self.kwargs.get("exception_handlers")
109 if exception_handlers is None:
110 exception_handlers = {
111 404: handle_exception,
112 500: handle_exception,
113 }
114 object.__setattr__(self.app, "exception_handlers", exception_handlers)
116 def _setup_models(self) -> None:
117 try:
118 models = self.depends.get("models") # type: ignore[union-attr]
119 except Exception:
120 models = None
121 object.__setattr__(self.app, "models", models)
123 def _configure_logging(self) -> None:
124 if get_installed_adapter("logfire"):
125 from logfire import instrument_starlette # type: ignore[import-untyped]
127 instrument_starlette(self.app)
128 interceptor_class = self._acb_modules[4]
129 if interceptor_class:
130 for logger_name in (
131 "uvicorn",
132 "uvicorn.access",
133 "granian",
134 "granian.access",
135 ):
136 server_logger = logging.getLogger(logger_name)
137 server_logger.handlers.clear()
138 server_logger.addHandler(interceptor_class())
139 server_logger.setLevel(logging.DEBUG)
140 server_logger.propagate = False
142 def _register_event_handlers(self) -> None:
143 """Register FastBlocks event handlers, health checks, validation, and workflows with ACB."""
144 try:
145 from contextlib import suppress
147 from ._events_integration import register_fastblocks_event_handlers
148 from ._health_integration import register_fastblocks_health_checks
149 from ._validation_integration import register_fastblocks_validation
150 from ._workflows_integration import register_fastblocks_workflows
152 # Register event handlers, health checks, validation, and workflows (async, run in background)
153 with suppress(Exception):
154 import asyncio
156 # Try to get or create event loop
157 try:
158 loop = asyncio.get_event_loop()
159 except RuntimeError:
160 loop = asyncio.new_event_loop()
161 asyncio.set_event_loop(loop)
163 # Schedule registration tasks
164 loop.create_task(register_fastblocks_event_handlers())
165 loop.create_task(register_fastblocks_health_checks())
166 loop.create_task(register_fastblocks_validation())
167 loop.create_task(register_fastblocks_workflows())
169 if self.logger:
170 self.logger.info(
171 "FastBlocks integrations registered (events, health, validation, workflows)"
172 )
174 except ImportError:
175 # ACB integrations not available - graceful degradation
176 if self.logger:
177 self.logger.debug(
178 "ACB integrations not available - running without enhanced features"
179 )
180 except Exception as e:
181 # Log error but don't fail application startup
182 if self.logger:
183 self.logger.warning(
184 f"Failed to register ACB integrations: {e} - continuing with degraded features"
185 )