Coverage for session_buddy / utils / instance_managers.py: 65.77%
97 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 00:43 -0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 00:43 -0800
1"""Instance managers for MCP server singletons.
3This module provides lazy initialization and access to global singleton instances
4for application monitoring, LLM providers, and serverless session management.
6Extracted from server.py Phase 2.6 to reduce cognitive complexity.
7"""
9from __future__ import annotations
11import os
12from contextlib import suppress
13from pathlib import Path
14from typing import TYPE_CHECKING, Any
16from session_buddy.di import SessionPaths, get_sync_typed
17from session_buddy.di.container import depends
19if TYPE_CHECKING:
20 from session_buddy.adapters.reflection_adapter import (
21 ReflectionDatabaseAdapter as ReflectionDatabase,
22 )
23 from session_buddy.app_monitor import ApplicationMonitor
24 from session_buddy.interruption_manager import InterruptionManager
25 from session_buddy.llm_providers import LLMManager
26 from session_buddy.serverless_mode import ServerlessSessionManager
29async def get_app_monitor() -> ApplicationMonitor | None:
30 """Resolve application monitor via DI, creating it on demand.
32 Note:
33 Uses the Oneiric-backed service container for singleton resolution.
35 """
36 try:
37 from session_buddy.app_monitor import ApplicationMonitor
38 except ImportError:
39 return None
41 with suppress(Exception):
42 monitor = get_sync_typed(ApplicationMonitor) # type: ignore[no-any-return]
43 if isinstance(monitor, ApplicationMonitor): 43 ↛ 46line 43 didn't jump to line 46
44 return monitor
46 data_dir = _resolve_claude_dir() / "data" / "app_monitoring"
47 working_dir = Path(os.environ.get("PWD", str(Path.cwd())))
48 project_paths = [str(working_dir)] if working_dir.exists() else []
50 monitor = ApplicationMonitor(str(data_dir), project_paths)
51 depends.set(ApplicationMonitor, monitor)
52 return monitor
55async def get_llm_manager() -> LLMManager | None:
56 """Resolve LLM manager via DI, creating it on demand.
58 Note:
59 Uses the Oneiric-backed service container for singleton resolution.
61 """
62 try:
63 from session_buddy.llm_providers import LLMManager
64 except ImportError:
65 return None
67 with suppress(Exception):
68 manager = get_sync_typed(LLMManager) # type: ignore[no-any-return]
69 if isinstance(manager, LLMManager): 69 ↛ 72line 69 didn't jump to line 72
70 return manager
72 config_path = _resolve_claude_dir() / "data" / "llm_config.json"
73 manager = LLMManager(str(config_path) if config_path.exists() else None)
74 depends.set(LLMManager, manager)
75 return manager
78async def get_serverless_manager() -> ServerlessSessionManager | None:
79 """Resolve serverless session manager via DI, creating it on demand.
81 Note:
82 Uses the Oneiric-backed service container for singleton resolution.
84 """
85 try:
86 from session_buddy.serverless_mode import (
87 ServerlessConfigManager,
88 ServerlessSessionManager,
89 )
90 except ImportError:
91 return None
93 with suppress(Exception):
94 manager = get_sync_typed(ServerlessSessionManager) # type: ignore[no-any-return]
95 if isinstance(manager, ServerlessSessionManager): 95 ↛ 98line 95 didn't jump to line 98
96 return manager
98 claude_dir = _resolve_claude_dir()
99 config_path = claude_dir / "data" / "serverless_config.json"
100 config = ServerlessConfigManager.load_config(
101 str(config_path) if config_path.exists() else None,
102 )
103 storage_backend = ServerlessConfigManager.create_storage_backend(config)
104 manager = ServerlessSessionManager(storage_backend)
105 depends.set(ServerlessSessionManager, manager)
106 return manager
109async def get_reflection_database() -> ReflectionDatabase | None:
110 """Resolve reflection database via DI, creating it on demand.
112 Note:
113 Returns ReflectionDatabaseAdapter which maintains API compatibility
114 with the original ReflectionDatabase while using Oneiric DuckDB.
116 """
117 try:
118 from session_buddy.adapters.reflection_adapter_oneiric import (
119 ReflectionDatabaseAdapterOneiric as ReflectionDatabaseAdapter,
120 )
121 except ImportError:
122 try:
123 from session_buddy.adapters.reflection_adapter import (
124 ReflectionDatabaseAdapter,
125 )
126 except ImportError:
127 return None
129 # Note: We use ReflectionDatabaseAdapter as the key for the new implementation
130 with suppress(Exception):
131 db = depends.get_sync(ReflectionDatabaseAdapter)
132 if isinstance(db, ReflectionDatabaseAdapter): 132 ↛ 135line 132 didn't jump to line 135
133 return db
135 from session_buddy.adapters.lifecycle import init_reflection_adapter
137 await init_reflection_adapter()
138 with suppress(Exception):
139 db = depends.get_sync(ReflectionDatabaseAdapter)
140 if isinstance(db, ReflectionDatabaseAdapter): 140 ↛ 142line 140 didn't jump to line 142
141 return db
142 return None
145async def get_interruption_manager() -> InterruptionManager | None:
146 """Resolve interruption manager via DI, creating it on demand.
148 Note:
149 Uses the Oneiric-backed service container for singleton resolution.
151 """
152 try:
153 from session_buddy.interruption_manager import InterruptionManager
154 except ImportError:
155 return None
157 with suppress(Exception):
158 manager = get_sync_typed(InterruptionManager) # type: ignore[no-any-return]
159 if isinstance(manager, InterruptionManager):
160 return manager
162 manager = InterruptionManager()
163 depends.set(InterruptionManager, manager)
164 return manager
167def reset_instances() -> None:
168 """Reset registered instances in the DI container."""
169 depends.reset()
172def _resolve_claude_dir() -> Path:
173 """Resolve claude directory via type-safe DI.
175 Returns:
176 Path to .claude directory, using SessionPaths from DI container
177 or falling back to default home directory.
179 Note:
180 Uses SessionPaths type for DI resolution instead of string keys.
182 """
183 with suppress(KeyError, AttributeError, RuntimeError, TypeError):
184 # RuntimeError: when adapter requires async
185 # TypeError: when DI has type confusion
186 paths = depends.get_sync(SessionPaths)
187 if isinstance(paths, SessionPaths): 187 ↛ 192line 187 didn't jump to line 192
188 paths.claude_dir.mkdir(parents=True, exist_ok=True)
189 return paths.claude_dir
191 # Fallback: create default paths if not registered
192 default_dir = Path(os.path.expanduser("~")) / ".claude"
193 default_dir.mkdir(parents=True, exist_ok=True)
194 return default_dir
197def _iter_dependencies() -> list[type[Any]]:
198 deps: list[type[Any]] = []
199 with suppress(ImportError):
200 from session_buddy.app_monitor import ApplicationMonitor
202 deps.append(ApplicationMonitor)
203 with suppress(ImportError):
204 from session_buddy.llm_providers import LLMManager
206 deps.append(LLMManager)
207 with suppress(ImportError):
208 from session_buddy.interruption_manager import InterruptionManager
210 deps.append(InterruptionManager)
211 with suppress(ImportError):
212 from session_buddy.serverless_mode import ServerlessSessionManager
214 deps.append(ServerlessSessionManager)
215 with suppress(ImportError):
216 from session_buddy.adapters.reflection_adapter import (
217 ReflectionDatabaseAdapter,
218 )
220 deps.append(ReflectionDatabaseAdapter)
221 return deps