Coverage for fastblocks/initializers.py: 20%

99 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-09 00:47 -0700

1"""Application initialization components for FastBlocks. 

2 

3This module contains classes responsible for initializing various aspects 

4of a FastBlocks application, separating concerns from the main application class. 

5""" 

6 

7import logging 

8import typing as t 

9 

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 

16 

17 

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, ...] = () 

26 

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

37 

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 

45 

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 ) 

59 

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 

72 

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

80 

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

88 

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 [] 

104 

105 def _configure_exception_handlers(self) -> None: 

106 from .exceptions import handle_exception 

107 

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) 

115 

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) 

122 

123 def _configure_logging(self) -> None: 

124 if get_installed_adapter("logfire"): 

125 from logfire import instrument_starlette # type: ignore[import-untyped] 

126 

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 

141 

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 

146 

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 

151 

152 # Register event handlers, health checks, validation, and workflows (async, run in background) 

153 with suppress(Exception): 

154 import asyncio 

155 

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) 

162 

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

168 

169 if self.logger: 

170 self.logger.info( 

171 "FastBlocks integrations registered (events, health, validation, workflows)" 

172 ) 

173 

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 )