crackerjack 0.30.3__py3-none-any.whl → 0.31.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +225 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +169 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +652 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +401 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +561 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +640 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +411 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +435 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +144 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +615 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +370 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +141 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +360 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +347 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +347 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +395 -0
- crackerjack/services/git.py +165 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +847 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.4.dist-info/METADATA +742 -0
- crackerjack-0.31.4.dist-info/RECORD +148 -0
- crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
"""Enhanced dependency injection container with lifecycle management."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import threading
|
|
5
|
+
import typing as t
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, TypeVar
|
|
11
|
+
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from crackerjack.models.protocols import (
|
|
15
|
+
FileSystemInterface,
|
|
16
|
+
GitInterface,
|
|
17
|
+
HookManager,
|
|
18
|
+
PublishManager,
|
|
19
|
+
TestManagerProtocol,
|
|
20
|
+
)
|
|
21
|
+
from crackerjack.services.logging import get_logger
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
FactoryFunc = Callable[..., T]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ServiceLifetime(Enum):
|
|
28
|
+
"""Service lifetime enumeration."""
|
|
29
|
+
|
|
30
|
+
SINGLETON = "singleton"
|
|
31
|
+
TRANSIENT = "transient"
|
|
32
|
+
SCOPED = "scoped"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ServiceDescriptor:
|
|
37
|
+
"""Describes how to create a service instance."""
|
|
38
|
+
|
|
39
|
+
interface: type
|
|
40
|
+
implementation: type | None = None
|
|
41
|
+
factory: Callable[..., Any] | None = None
|
|
42
|
+
lifetime: ServiceLifetime = ServiceLifetime.TRANSIENT
|
|
43
|
+
instance: Any | None = None
|
|
44
|
+
created_count: int = 0
|
|
45
|
+
dependencies: list[type] = field(default_factory=list)
|
|
46
|
+
|
|
47
|
+
def __post_init__(self):
|
|
48
|
+
if self.implementation is self.factory is self.instance is None:
|
|
49
|
+
msg = "Must provide either implementation, factory, or instance"
|
|
50
|
+
raise ValueError(msg)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ServiceScope:
|
|
54
|
+
"""Represents a service scope for scoped services."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, name: str) -> None:
|
|
57
|
+
self.name = name
|
|
58
|
+
self._instances: dict[str, Any] = {}
|
|
59
|
+
self._lock = threading.Lock()
|
|
60
|
+
self.logger = get_logger("crackerjack.container.scope")
|
|
61
|
+
|
|
62
|
+
def get_instance(self, key: str) -> Any | None:
|
|
63
|
+
"""Get scoped instance."""
|
|
64
|
+
with self._lock:
|
|
65
|
+
return self._instances.get(key)
|
|
66
|
+
|
|
67
|
+
def set_instance(self, key: str, instance: Any) -> None:
|
|
68
|
+
"""Set scoped instance."""
|
|
69
|
+
with self._lock:
|
|
70
|
+
self._instances[key] = instance
|
|
71
|
+
self.logger.debug("Scoped instance created", scope=self.name, service=key)
|
|
72
|
+
|
|
73
|
+
def dispose(self) -> None:
|
|
74
|
+
"""Dispose of all scoped instances."""
|
|
75
|
+
with self._lock:
|
|
76
|
+
for key, instance in self._instances.items():
|
|
77
|
+
if hasattr(instance, "dispose"):
|
|
78
|
+
try:
|
|
79
|
+
instance.dispose()
|
|
80
|
+
except Exception as e:
|
|
81
|
+
self.logger.exception(
|
|
82
|
+
"Error disposing service",
|
|
83
|
+
service=key,
|
|
84
|
+
error=str(e),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self._instances.clear()
|
|
88
|
+
self.logger.info("Service scope disposed", scope=self.name)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class DependencyResolver:
|
|
92
|
+
"""Resolves service dependencies through constructor injection."""
|
|
93
|
+
|
|
94
|
+
def __init__(self, container: "EnhancedDependencyContainer") -> None:
|
|
95
|
+
self.container = container
|
|
96
|
+
self.logger = get_logger("crackerjack.container.resolver")
|
|
97
|
+
|
|
98
|
+
def create_instance(
|
|
99
|
+
self,
|
|
100
|
+
descriptor: ServiceDescriptor,
|
|
101
|
+
scope: ServiceScope | None = None,
|
|
102
|
+
) -> Any:
|
|
103
|
+
"""Create service instance with dependency injection."""
|
|
104
|
+
if descriptor.instance is not None:
|
|
105
|
+
return descriptor.instance
|
|
106
|
+
|
|
107
|
+
if descriptor.factory is not None:
|
|
108
|
+
return self._create_from_factory(descriptor.factory)
|
|
109
|
+
|
|
110
|
+
if descriptor.implementation is not None:
|
|
111
|
+
return self._create_from_class(descriptor.implementation)
|
|
112
|
+
|
|
113
|
+
msg = f"Cannot create instance for {descriptor.interface}"
|
|
114
|
+
raise ValueError(msg)
|
|
115
|
+
|
|
116
|
+
def _create_from_factory(self, factory: Callable[..., Any]) -> Any:
|
|
117
|
+
"""Create instance using factory function."""
|
|
118
|
+
sig = inspect.signature(factory)
|
|
119
|
+
kwargs = {}
|
|
120
|
+
|
|
121
|
+
for param_name, param in sig.parameters.items():
|
|
122
|
+
if param.annotation != inspect.Parameter.empty:
|
|
123
|
+
try:
|
|
124
|
+
dependency = self.container.get(param.annotation)
|
|
125
|
+
kwargs[param_name] = dependency
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.logger.warning(
|
|
128
|
+
"Could not inject dependency",
|
|
129
|
+
parameter=param_name,
|
|
130
|
+
type=param.annotation,
|
|
131
|
+
error=str(e),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return factory(**kwargs)
|
|
135
|
+
|
|
136
|
+
def _create_from_class(self, implementation: type) -> Any:
|
|
137
|
+
"""Create instance using class constructor with dependency injection."""
|
|
138
|
+
try:
|
|
139
|
+
kwargs = self._build_constructor_kwargs(implementation)
|
|
140
|
+
return self._instantiate_with_logging(implementation, kwargs)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
self.logger.exception(
|
|
143
|
+
"Failed to create instance",
|
|
144
|
+
implementation_class=implementation.__name__,
|
|
145
|
+
error=str(e),
|
|
146
|
+
)
|
|
147
|
+
raise
|
|
148
|
+
|
|
149
|
+
def _build_constructor_kwargs(self, implementation: type) -> dict[str, Any]:
|
|
150
|
+
"""Build constructor kwargs by resolving dependencies."""
|
|
151
|
+
init_sig = inspect.signature(implementation.__init__)
|
|
152
|
+
kwargs = {}
|
|
153
|
+
|
|
154
|
+
for param_name, param in init_sig.parameters.items():
|
|
155
|
+
if param_name == "self":
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
if param.annotation != inspect.Parameter.empty:
|
|
159
|
+
self._resolve_parameter_dependency(
|
|
160
|
+
kwargs, param_name, param, implementation.__name__
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return kwargs
|
|
164
|
+
|
|
165
|
+
def _resolve_parameter_dependency(
|
|
166
|
+
self,
|
|
167
|
+
kwargs: dict[str, Any],
|
|
168
|
+
param_name: str,
|
|
169
|
+
param: inspect.Parameter,
|
|
170
|
+
class_name: str,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Resolve a single parameter dependency."""
|
|
173
|
+
try:
|
|
174
|
+
dependency = self.container.get(param.annotation)
|
|
175
|
+
kwargs[param_name] = dependency
|
|
176
|
+
except Exception as e:
|
|
177
|
+
if param.default == inspect.Parameter.empty:
|
|
178
|
+
self.logger.exception(
|
|
179
|
+
"Required dependency not available",
|
|
180
|
+
implementation_class=class_name,
|
|
181
|
+
parameter=param_name,
|
|
182
|
+
type=param.annotation,
|
|
183
|
+
error=str(e),
|
|
184
|
+
)
|
|
185
|
+
raise
|
|
186
|
+
self.logger.debug(
|
|
187
|
+
"Optional dependency not available, using default",
|
|
188
|
+
parameter=param_name,
|
|
189
|
+
type=param.annotation,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def _instantiate_with_logging(
|
|
193
|
+
self, implementation: type, kwargs: dict[str, Any]
|
|
194
|
+
) -> Any:
|
|
195
|
+
"""Create instance and log the creation."""
|
|
196
|
+
instance = implementation(**kwargs)
|
|
197
|
+
self.logger.debug(
|
|
198
|
+
"Instance created with DI",
|
|
199
|
+
implementation_class=implementation.__name__,
|
|
200
|
+
)
|
|
201
|
+
return instance
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class EnhancedDependencyContainer:
|
|
205
|
+
"""Enhanced dependency injection container with lifecycle management."""
|
|
206
|
+
|
|
207
|
+
def __init__(self, name: str = "default") -> None:
|
|
208
|
+
self.name = name
|
|
209
|
+
self._services: dict[str, ServiceDescriptor] = {}
|
|
210
|
+
self._singletons: dict[str, Any] = {}
|
|
211
|
+
self._lock = threading.Lock()
|
|
212
|
+
self._current_scope: ServiceScope | None = None
|
|
213
|
+
self.resolver = DependencyResolver(self)
|
|
214
|
+
self.logger = get_logger("crackerjack.container")
|
|
215
|
+
|
|
216
|
+
def register_singleton(
|
|
217
|
+
self,
|
|
218
|
+
interface: type,
|
|
219
|
+
implementation: type | None = None,
|
|
220
|
+
factory: Callable[..., Any] | None = None,
|
|
221
|
+
instance: Any | None = None,
|
|
222
|
+
) -> "EnhancedDependencyContainer":
|
|
223
|
+
"""Register a singleton service."""
|
|
224
|
+
key = self._get_service_key(interface)
|
|
225
|
+
|
|
226
|
+
descriptor = ServiceDescriptor(
|
|
227
|
+
interface=interface,
|
|
228
|
+
implementation=implementation,
|
|
229
|
+
factory=factory,
|
|
230
|
+
instance=instance,
|
|
231
|
+
lifetime=ServiceLifetime.SINGLETON,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
with self._lock:
|
|
235
|
+
self._services[key] = descriptor
|
|
236
|
+
|
|
237
|
+
self.logger.debug("Singleton registered", interface=interface.__name__)
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def register_transient(
|
|
241
|
+
self,
|
|
242
|
+
interface: type,
|
|
243
|
+
implementation: type | None = None,
|
|
244
|
+
factory: Callable[..., Any] | None = None,
|
|
245
|
+
) -> "EnhancedDependencyContainer":
|
|
246
|
+
"""Register a transient service."""
|
|
247
|
+
key = self._get_service_key(interface)
|
|
248
|
+
|
|
249
|
+
descriptor = ServiceDescriptor(
|
|
250
|
+
interface=interface,
|
|
251
|
+
implementation=implementation,
|
|
252
|
+
factory=factory,
|
|
253
|
+
lifetime=ServiceLifetime.TRANSIENT,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
with self._lock:
|
|
257
|
+
self._services[key] = descriptor
|
|
258
|
+
|
|
259
|
+
self.logger.debug("Transient registered", interface=interface.__name__)
|
|
260
|
+
return self
|
|
261
|
+
|
|
262
|
+
def register_scoped(
|
|
263
|
+
self,
|
|
264
|
+
interface: type,
|
|
265
|
+
implementation: type | None = None,
|
|
266
|
+
factory: Callable[..., Any] | None = None,
|
|
267
|
+
) -> "EnhancedDependencyContainer":
|
|
268
|
+
"""Register a scoped service."""
|
|
269
|
+
key = self._get_service_key(interface)
|
|
270
|
+
|
|
271
|
+
descriptor = ServiceDescriptor(
|
|
272
|
+
interface=interface,
|
|
273
|
+
implementation=implementation,
|
|
274
|
+
factory=factory,
|
|
275
|
+
lifetime=ServiceLifetime.SCOPED,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
with self._lock:
|
|
279
|
+
self._services[key] = descriptor
|
|
280
|
+
|
|
281
|
+
self.logger.debug("Scoped registered", interface=interface.__name__)
|
|
282
|
+
return self
|
|
283
|
+
|
|
284
|
+
def get(self, interface: type, scope: ServiceScope | None = None) -> Any:
|
|
285
|
+
"""Get service instance."""
|
|
286
|
+
key = self._get_service_key(interface)
|
|
287
|
+
|
|
288
|
+
with self._lock:
|
|
289
|
+
if key not in self._services:
|
|
290
|
+
msg = f"Service {interface.__name__} not registered"
|
|
291
|
+
raise ValueError(msg)
|
|
292
|
+
|
|
293
|
+
descriptor = self._services[key]
|
|
294
|
+
|
|
295
|
+
return self._create_service_instance(descriptor, scope or self._current_scope)
|
|
296
|
+
|
|
297
|
+
def get_optional(self, interface: type, default: Any = None) -> Any:
|
|
298
|
+
"""Get service instance or return default if not registered."""
|
|
299
|
+
try:
|
|
300
|
+
return self.get(interface)
|
|
301
|
+
except ValueError:
|
|
302
|
+
return default
|
|
303
|
+
|
|
304
|
+
def is_registered(self, interface: type) -> bool:
|
|
305
|
+
"""Check if service is registered."""
|
|
306
|
+
key = self._get_service_key(interface)
|
|
307
|
+
return key in self._services
|
|
308
|
+
|
|
309
|
+
def create_scope(self, name: str = "scope") -> ServiceScope:
|
|
310
|
+
"""Create a new service scope."""
|
|
311
|
+
return ServiceScope(name)
|
|
312
|
+
|
|
313
|
+
def set_current_scope(self, scope: ServiceScope | None) -> None:
|
|
314
|
+
"""Set the current service scope."""
|
|
315
|
+
self._current_scope = scope
|
|
316
|
+
|
|
317
|
+
def get_service_info(self) -> dict[str, Any]:
|
|
318
|
+
"""Get information about registered services."""
|
|
319
|
+
info = {}
|
|
320
|
+
|
|
321
|
+
with self._lock:
|
|
322
|
+
for key, descriptor in self._services.items():
|
|
323
|
+
info[key] = {
|
|
324
|
+
"interface": descriptor.interface.__name__,
|
|
325
|
+
"implementation": descriptor.implementation.__name__
|
|
326
|
+
if descriptor.implementation
|
|
327
|
+
else None,
|
|
328
|
+
"lifetime": descriptor.lifetime.value,
|
|
329
|
+
"created_count": descriptor.created_count,
|
|
330
|
+
"has_instance": descriptor.instance is not None,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return info
|
|
334
|
+
|
|
335
|
+
def dispose(self) -> None:
|
|
336
|
+
"""Dispose of container and all singletons."""
|
|
337
|
+
with self._lock:
|
|
338
|
+
# Dispose singletons
|
|
339
|
+
for key, instance in self._singletons.items():
|
|
340
|
+
if hasattr(instance, "dispose"):
|
|
341
|
+
try:
|
|
342
|
+
instance.dispose()
|
|
343
|
+
except Exception as e:
|
|
344
|
+
self.logger.exception(
|
|
345
|
+
"Error disposing singleton",
|
|
346
|
+
service=key,
|
|
347
|
+
error=str(e),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
self._singletons.clear()
|
|
351
|
+
|
|
352
|
+
# Dispose current scope
|
|
353
|
+
if self._current_scope:
|
|
354
|
+
self._current_scope.dispose()
|
|
355
|
+
self._current_scope = None
|
|
356
|
+
|
|
357
|
+
self.logger.info("Container disposed", name=self.name)
|
|
358
|
+
|
|
359
|
+
def _create_service_instance(
|
|
360
|
+
self,
|
|
361
|
+
descriptor: ServiceDescriptor,
|
|
362
|
+
scope: ServiceScope | None = None,
|
|
363
|
+
) -> Any:
|
|
364
|
+
"""Create service instance based on lifetime."""
|
|
365
|
+
if descriptor.lifetime == ServiceLifetime.SINGLETON:
|
|
366
|
+
return self._get_or_create_singleton(descriptor)
|
|
367
|
+
if descriptor.lifetime == ServiceLifetime.SCOPED:
|
|
368
|
+
return self._get_or_create_scoped(descriptor, scope)
|
|
369
|
+
# Transient
|
|
370
|
+
return self._create_transient_instance(descriptor)
|
|
371
|
+
|
|
372
|
+
def _get_or_create_singleton(self, descriptor: ServiceDescriptor) -> Any:
|
|
373
|
+
"""Get or create singleton instance."""
|
|
374
|
+
key = self._get_service_key(descriptor.interface)
|
|
375
|
+
|
|
376
|
+
if key in self._singletons:
|
|
377
|
+
return self._singletons[key]
|
|
378
|
+
|
|
379
|
+
instance = self.resolver.create_instance(descriptor)
|
|
380
|
+
self._singletons[key] = instance
|
|
381
|
+
descriptor.created_count += 1
|
|
382
|
+
|
|
383
|
+
self.logger.debug("Singleton created", interface=descriptor.interface.__name__)
|
|
384
|
+
return instance
|
|
385
|
+
|
|
386
|
+
def _get_or_create_scoped(
|
|
387
|
+
self,
|
|
388
|
+
descriptor: ServiceDescriptor,
|
|
389
|
+
scope: ServiceScope | None,
|
|
390
|
+
) -> Any:
|
|
391
|
+
"""Get or create scoped instance."""
|
|
392
|
+
if scope is None:
|
|
393
|
+
msg = f"Scoped service {descriptor.interface.__name__} requires an active scope"
|
|
394
|
+
raise ValueError(
|
|
395
|
+
msg,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
key = self._get_service_key(descriptor.interface)
|
|
399
|
+
instance = scope.get_instance(key)
|
|
400
|
+
|
|
401
|
+
if instance is None:
|
|
402
|
+
instance = self.resolver.create_instance(descriptor, scope)
|
|
403
|
+
scope.set_instance(key, instance)
|
|
404
|
+
descriptor.created_count += 1
|
|
405
|
+
|
|
406
|
+
return instance
|
|
407
|
+
|
|
408
|
+
def _create_transient_instance(self, descriptor: ServiceDescriptor) -> Any:
|
|
409
|
+
"""Create new transient instance."""
|
|
410
|
+
instance = self.resolver.create_instance(descriptor)
|
|
411
|
+
descriptor.created_count += 1
|
|
412
|
+
return instance
|
|
413
|
+
|
|
414
|
+
def _get_service_key(self, interface: type) -> str:
|
|
415
|
+
"""Get service key from interface type."""
|
|
416
|
+
return f"{interface.__module__}.{interface.__name__}"
|
|
417
|
+
|
|
418
|
+
def __enter__(self):
|
|
419
|
+
return self
|
|
420
|
+
|
|
421
|
+
def __exit__(
|
|
422
|
+
self,
|
|
423
|
+
exc_type: type[BaseException] | None,
|
|
424
|
+
exc_val: BaseException | None,
|
|
425
|
+
_exc_tb: t.Any,
|
|
426
|
+
) -> None:
|
|
427
|
+
self.dispose()
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class ServiceCollectionBuilder:
|
|
431
|
+
"""Builder pattern for configuring services."""
|
|
432
|
+
|
|
433
|
+
def __init__(self, container: EnhancedDependencyContainer) -> None:
|
|
434
|
+
self.container = container
|
|
435
|
+
self.console: Console | None = None
|
|
436
|
+
self.pkg_path: Path | None = None
|
|
437
|
+
self.dry_run: bool = False
|
|
438
|
+
|
|
439
|
+
def with_console(self, console: Console) -> "ServiceCollectionBuilder":
|
|
440
|
+
"""Set console for services that need it."""
|
|
441
|
+
self.console = console
|
|
442
|
+
return self
|
|
443
|
+
|
|
444
|
+
def with_package_path(self, pkg_path: Path) -> "ServiceCollectionBuilder":
|
|
445
|
+
"""Set package path for services that need it."""
|
|
446
|
+
self.pkg_path = pkg_path
|
|
447
|
+
return self
|
|
448
|
+
|
|
449
|
+
def with_dry_run(self, dry_run: bool) -> "ServiceCollectionBuilder":
|
|
450
|
+
"""Set dry run mode."""
|
|
451
|
+
self.dry_run = dry_run
|
|
452
|
+
return self
|
|
453
|
+
|
|
454
|
+
def add_core_services(self) -> "ServiceCollectionBuilder":
|
|
455
|
+
"""Add core Crackerjack services."""
|
|
456
|
+
console = self.console or Console(force_terminal=True)
|
|
457
|
+
pkg_path = self.pkg_path or Path.cwd()
|
|
458
|
+
|
|
459
|
+
# Enhanced filesystem service
|
|
460
|
+
from crackerjack.services.enhanced_filesystem import EnhancedFileSystemService
|
|
461
|
+
|
|
462
|
+
self.container.register_singleton(
|
|
463
|
+
FileSystemInterface,
|
|
464
|
+
factory=EnhancedFileSystemService,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Git service
|
|
468
|
+
from crackerjack.services.git import GitService
|
|
469
|
+
|
|
470
|
+
self.container.register_transient(
|
|
471
|
+
GitInterface,
|
|
472
|
+
factory=lambda: GitService(console=console, pkg_path=pkg_path),
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Async hook manager
|
|
476
|
+
from crackerjack.managers.async_hook_manager import AsyncHookManager
|
|
477
|
+
|
|
478
|
+
self.container.register_scoped(
|
|
479
|
+
HookManager,
|
|
480
|
+
factory=lambda: AsyncHookManager(console=console, pkg_path=pkg_path),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# Test manager
|
|
484
|
+
from crackerjack.managers.test_manager import TestManagementImpl
|
|
485
|
+
|
|
486
|
+
self.container.register_transient(
|
|
487
|
+
TestManagerProtocol,
|
|
488
|
+
factory=lambda: TestManagementImpl(console=console, pkg_path=pkg_path),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Publish manager
|
|
492
|
+
from crackerjack.managers.publish_manager import PublishManagerImpl
|
|
493
|
+
|
|
494
|
+
self.container.register_transient(
|
|
495
|
+
PublishManager,
|
|
496
|
+
factory=lambda: PublishManagerImpl(
|
|
497
|
+
console=console,
|
|
498
|
+
pkg_path=pkg_path,
|
|
499
|
+
dry_run=self.dry_run,
|
|
500
|
+
),
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
return self
|
|
504
|
+
|
|
505
|
+
def add_configuration_services(self) -> "ServiceCollectionBuilder":
|
|
506
|
+
"""Add configuration services."""
|
|
507
|
+
console = self.console or Console(force_terminal=True)
|
|
508
|
+
pkg_path = self.pkg_path or Path.cwd()
|
|
509
|
+
|
|
510
|
+
# Unified configuration service
|
|
511
|
+
from crackerjack.services.unified_config import UnifiedConfigurationService
|
|
512
|
+
|
|
513
|
+
self.container.register_singleton(
|
|
514
|
+
UnifiedConfigurationService,
|
|
515
|
+
factory=lambda: UnifiedConfigurationService(console, pkg_path),
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
return self
|
|
519
|
+
|
|
520
|
+
def build(self) -> EnhancedDependencyContainer:
|
|
521
|
+
"""Build the configured container."""
|
|
522
|
+
return self.container
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def create_enhanced_container(
|
|
526
|
+
console: Console | None = None,
|
|
527
|
+
pkg_path: Path | None = None,
|
|
528
|
+
dry_run: bool = False,
|
|
529
|
+
name: str = "crackerjack",
|
|
530
|
+
) -> EnhancedDependencyContainer:
|
|
531
|
+
"""Create enhanced dependency injection container with default services."""
|
|
532
|
+
container = EnhancedDependencyContainer(name)
|
|
533
|
+
|
|
534
|
+
builder = ServiceCollectionBuilder(container)
|
|
535
|
+
builder.with_console(console or Console(force_terminal=True))
|
|
536
|
+
builder.with_package_path(pkg_path or Path.cwd())
|
|
537
|
+
builder.with_dry_run(dry_run)
|
|
538
|
+
|
|
539
|
+
builder.add_core_services()
|
|
540
|
+
builder.add_configuration_services()
|
|
541
|
+
|
|
542
|
+
return builder.build()
|