crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +64 -6
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +257 -218
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +558 -240
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +66 -13
- crackerjack/managers/test_command_builder.py +5 -17
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +109 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +161 -32
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +174 -33
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +15 -12
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +3 -0
- crackerjack/mixins/error_handling.py +145 -0
- crackerjack/models/config.py +21 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +176 -107
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/models/task.py +3 -0
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +90 -105
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +18 -11
- crackerjack/services/config_merge.py +30 -85
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +41 -17
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +41 -45
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +30 -33
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +409 -0
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +416 -0
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +250 -576
- crackerjack/services/performance_cache.py +382 -0
- crackerjack/services/performance_monitor.py +565 -0
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +605 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +61 -30
- crackerjack/services/security_logger.py +18 -22
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.32.0.dist-info/RECORD +0 -180
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
"""Comprehensive resource management with automatic cleanup patterns.
|
|
2
|
-
|
|
3
|
-
This module provides RAII (Resource Acquisition Is Initialization) patterns
|
|
4
|
-
and comprehensive resource lifecycle management to prevent resource leaks
|
|
5
|
-
even in error scenarios.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
1
|
import asyncio
|
|
9
2
|
import contextlib
|
|
10
3
|
import logging
|
|
@@ -19,16 +12,10 @@ from types import TracebackType
|
|
|
19
12
|
|
|
20
13
|
|
|
21
14
|
class ResourceProtocol(t.Protocol):
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async def cleanup(self) -> None:
|
|
25
|
-
"""Clean up the resource."""
|
|
26
|
-
...
|
|
15
|
+
async def cleanup(self) -> None: ...
|
|
27
16
|
|
|
28
17
|
|
|
29
18
|
class ResourceManager:
|
|
30
|
-
"""Centralized resource management with automatic cleanup on error scenarios."""
|
|
31
|
-
|
|
32
19
|
def __init__(self, logger: logging.Logger | None = None) -> None:
|
|
33
20
|
self.logger = logger or logging.getLogger(__name__)
|
|
34
21
|
self._resources: list[ResourceProtocol] = []
|
|
@@ -37,10 +24,8 @@ class ResourceManager:
|
|
|
37
24
|
self._closed = False
|
|
38
25
|
|
|
39
26
|
def register_resource(self, resource: ResourceProtocol) -> None:
|
|
40
|
-
"""Register a resource for automatic cleanup."""
|
|
41
27
|
with self._lock:
|
|
42
28
|
if self._closed:
|
|
43
|
-
# If already closed, immediately clean up the resource
|
|
44
29
|
asyncio.create_task(resource.cleanup())
|
|
45
30
|
return
|
|
46
31
|
self._resources.append(resource)
|
|
@@ -48,10 +33,8 @@ class ResourceManager:
|
|
|
48
33
|
def register_cleanup_callback(
|
|
49
34
|
self, callback: t.Callable[[], t.Awaitable[None]]
|
|
50
35
|
) -> None:
|
|
51
|
-
"""Register a cleanup callback to be called during shutdown."""
|
|
52
36
|
with self._lock:
|
|
53
37
|
if self._closed:
|
|
54
|
-
# If already closed, immediately call the callback
|
|
55
38
|
coro = callback()
|
|
56
39
|
if asyncio.iscoroutine(coro):
|
|
57
40
|
asyncio.ensure_future(coro)
|
|
@@ -59,7 +42,6 @@ class ResourceManager:
|
|
|
59
42
|
self._cleanup_callbacks.append(callback)
|
|
60
43
|
|
|
61
44
|
async def cleanup_all(self) -> None:
|
|
62
|
-
"""Clean up all registered resources."""
|
|
63
45
|
with self._lock:
|
|
64
46
|
if self._closed:
|
|
65
47
|
return
|
|
@@ -68,14 +50,12 @@ class ResourceManager:
|
|
|
68
50
|
resources = self._resources.copy()
|
|
69
51
|
callbacks = self._cleanup_callbacks.copy()
|
|
70
52
|
|
|
71
|
-
# Clean up resources
|
|
72
53
|
for resource in resources:
|
|
73
54
|
try:
|
|
74
55
|
await resource.cleanup()
|
|
75
56
|
except Exception as e:
|
|
76
57
|
self.logger.warning(f"Error cleaning up resource {resource}: {e}")
|
|
77
58
|
|
|
78
|
-
# Call cleanup callbacks
|
|
79
59
|
for callback in callbacks:
|
|
80
60
|
try:
|
|
81
61
|
await callback()
|
|
@@ -99,8 +79,6 @@ class ResourceManager:
|
|
|
99
79
|
|
|
100
80
|
|
|
101
81
|
class ManagedResource(ABC):
|
|
102
|
-
"""Base class for managed resources with automatic cleanup."""
|
|
103
|
-
|
|
104
82
|
def __init__(self, manager: ResourceManager | None = None) -> None:
|
|
105
83
|
self.manager = manager
|
|
106
84
|
self._closed = False
|
|
@@ -110,19 +88,15 @@ class ManagedResource(ABC):
|
|
|
110
88
|
|
|
111
89
|
@abstractmethod
|
|
112
90
|
async def cleanup(self) -> None:
|
|
113
|
-
"""Clean up the resource. Must be implemented by subclasses."""
|
|
114
91
|
pass
|
|
115
92
|
|
|
116
93
|
async def close(self) -> None:
|
|
117
|
-
"""Manually close the resource."""
|
|
118
94
|
if not self._closed:
|
|
119
95
|
self._closed = True
|
|
120
96
|
await self.cleanup()
|
|
121
97
|
|
|
122
98
|
def __del__(self) -> None:
|
|
123
|
-
"""Ensure cleanup is called even if explicitly forgotten."""
|
|
124
99
|
if not self._closed:
|
|
125
|
-
# Create cleanup task if event loop is available
|
|
126
100
|
with contextlib.suppress(RuntimeError):
|
|
127
101
|
loop = asyncio.get_event_loop()
|
|
128
102
|
if loop.is_running():
|
|
@@ -130,8 +104,6 @@ class ManagedResource(ABC):
|
|
|
130
104
|
|
|
131
105
|
|
|
132
106
|
class ManagedTemporaryFile(ManagedResource):
|
|
133
|
-
"""Temporary file with automatic cleanup on error scenarios."""
|
|
134
|
-
|
|
135
107
|
def __init__(
|
|
136
108
|
self,
|
|
137
109
|
suffix: str = "",
|
|
@@ -142,43 +114,35 @@ class ManagedTemporaryFile(ManagedResource):
|
|
|
142
114
|
self.temp_file = tempfile.NamedTemporaryFile(
|
|
143
115
|
suffix=suffix,
|
|
144
116
|
prefix=prefix,
|
|
145
|
-
delete=False,
|
|
117
|
+
delete=False,
|
|
146
118
|
)
|
|
147
119
|
self.path = Path(self.temp_file.name)
|
|
148
120
|
|
|
149
121
|
async def cleanup(self) -> None:
|
|
150
|
-
"""Clean up temporary file."""
|
|
151
122
|
if not self._closed:
|
|
152
123
|
self._closed = True
|
|
153
124
|
|
|
154
|
-
# Close file handle
|
|
155
125
|
if not self.temp_file.closed:
|
|
156
126
|
self.temp_file.close()
|
|
157
127
|
|
|
158
|
-
# Remove file if it exists
|
|
159
128
|
try:
|
|
160
129
|
if self.path.exists():
|
|
161
130
|
self.path.unlink()
|
|
162
131
|
except OSError as e:
|
|
163
|
-
# Log but don't raise - cleanup should be best effort
|
|
164
132
|
logging.getLogger(__name__).warning(
|
|
165
133
|
f"Failed to remove temporary file {self.path}: {e}"
|
|
166
134
|
)
|
|
167
135
|
|
|
168
136
|
def write_text(self, content: str, encoding: str = "utf-8") -> None:
|
|
169
|
-
"""Write text content to the temporary file."""
|
|
170
137
|
if self._closed:
|
|
171
138
|
raise RuntimeError("Cannot write to closed temporary file")
|
|
172
139
|
self.path.write_text(content, encoding=encoding)
|
|
173
140
|
|
|
174
141
|
def read_text(self, encoding: str = "utf-8") -> str:
|
|
175
|
-
"""Read text content from the temporary file."""
|
|
176
142
|
return self.path.read_text(encoding=encoding)
|
|
177
143
|
|
|
178
144
|
|
|
179
145
|
class ManagedTemporaryDirectory(ManagedResource):
|
|
180
|
-
"""Temporary directory with automatic cleanup on error scenarios."""
|
|
181
|
-
|
|
182
146
|
def __init__(
|
|
183
147
|
self,
|
|
184
148
|
suffix: str = "",
|
|
@@ -190,7 +154,6 @@ class ManagedTemporaryDirectory(ManagedResource):
|
|
|
190
154
|
self.path = Path(self.temp_dir)
|
|
191
155
|
|
|
192
156
|
async def cleanup(self) -> None:
|
|
193
|
-
"""Clean up temporary directory and all contents."""
|
|
194
157
|
if not self._closed:
|
|
195
158
|
self._closed = True
|
|
196
159
|
|
|
@@ -206,8 +169,6 @@ class ManagedTemporaryDirectory(ManagedResource):
|
|
|
206
169
|
|
|
207
170
|
|
|
208
171
|
class ManagedProcess(ManagedResource):
|
|
209
|
-
"""Process with automatic cleanup on error scenarios."""
|
|
210
|
-
|
|
211
172
|
def __init__(
|
|
212
173
|
self,
|
|
213
174
|
process: asyncio.subprocess.Process,
|
|
@@ -219,18 +180,15 @@ class ManagedProcess(ManagedResource):
|
|
|
219
180
|
self.timeout = timeout
|
|
220
181
|
|
|
221
182
|
async def cleanup(self) -> None:
|
|
222
|
-
"""Clean up process with graceful termination."""
|
|
223
183
|
if not self._closed and self.process.returncode is None:
|
|
224
184
|
self._closed = True
|
|
225
185
|
|
|
226
186
|
try:
|
|
227
|
-
# Try graceful termination first
|
|
228
187
|
self.process.terminate()
|
|
229
188
|
|
|
230
189
|
try:
|
|
231
190
|
await asyncio.wait_for(self.process.wait(), timeout=5.0)
|
|
232
191
|
except TimeoutError:
|
|
233
|
-
# Force kill if graceful termination fails
|
|
234
192
|
self.process.kill()
|
|
235
193
|
try:
|
|
236
194
|
await asyncio.wait_for(self.process.wait(), timeout=2.0)
|
|
@@ -240,7 +198,6 @@ class ManagedProcess(ManagedResource):
|
|
|
240
198
|
)
|
|
241
199
|
|
|
242
200
|
except ProcessLookupError:
|
|
243
|
-
# Process already terminated
|
|
244
201
|
pass
|
|
245
202
|
except Exception as e:
|
|
246
203
|
logging.getLogger(__name__).warning(
|
|
@@ -249,8 +206,6 @@ class ManagedProcess(ManagedResource):
|
|
|
249
206
|
|
|
250
207
|
|
|
251
208
|
class ManagedTask(ManagedResource):
|
|
252
|
-
"""Asyncio task with automatic cancellation on error scenarios."""
|
|
253
|
-
|
|
254
209
|
def __init__(
|
|
255
210
|
self,
|
|
256
211
|
task: asyncio.Task[t.Any],
|
|
@@ -262,7 +217,6 @@ class ManagedTask(ManagedResource):
|
|
|
262
217
|
self.timeout = timeout
|
|
263
218
|
|
|
264
219
|
async def cleanup(self) -> None:
|
|
265
|
-
"""Clean up task with cancellation."""
|
|
266
220
|
if not self._closed and not self.task.done():
|
|
267
221
|
self._closed = True
|
|
268
222
|
|
|
@@ -271,15 +225,12 @@ class ManagedTask(ManagedResource):
|
|
|
271
225
|
try:
|
|
272
226
|
await asyncio.wait_for(self.task, timeout=self.timeout)
|
|
273
227
|
except (TimeoutError, asyncio.CancelledError):
|
|
274
|
-
# Expected when cancelling or timing out
|
|
275
228
|
pass
|
|
276
229
|
except Exception as e:
|
|
277
230
|
logging.getLogger(__name__).warning(f"Error cleaning up task: {e}")
|
|
278
231
|
|
|
279
232
|
|
|
280
233
|
class ManagedFileHandle(ManagedResource):
|
|
281
|
-
"""File handle with automatic closing on error scenarios."""
|
|
282
|
-
|
|
283
234
|
def __init__(
|
|
284
235
|
self,
|
|
285
236
|
file_handle: t.IO[t.Any],
|
|
@@ -289,7 +240,6 @@ class ManagedFileHandle(ManagedResource):
|
|
|
289
240
|
self.file_handle = file_handle
|
|
290
241
|
|
|
291
242
|
async def cleanup(self) -> None:
|
|
292
|
-
"""Clean up file handle."""
|
|
293
243
|
if not self._closed and not self.file_handle.closed:
|
|
294
244
|
self._closed = True
|
|
295
245
|
|
|
@@ -300,8 +250,6 @@ class ManagedFileHandle(ManagedResource):
|
|
|
300
250
|
|
|
301
251
|
|
|
302
252
|
class ResourceContext:
|
|
303
|
-
"""Context manager for automatic resource management."""
|
|
304
|
-
|
|
305
253
|
def __init__(self) -> None:
|
|
306
254
|
self.resource_manager = ResourceManager()
|
|
307
255
|
|
|
@@ -310,7 +258,6 @@ class ResourceContext:
|
|
|
310
258
|
suffix: str = "",
|
|
311
259
|
prefix: str = "crackerjack-",
|
|
312
260
|
) -> ManagedTemporaryFile:
|
|
313
|
-
"""Create a managed temporary file."""
|
|
314
261
|
return ManagedTemporaryFile(suffix, prefix, self.resource_manager)
|
|
315
262
|
|
|
316
263
|
def managed_temp_dir(
|
|
@@ -318,7 +265,6 @@ class ResourceContext:
|
|
|
318
265
|
suffix: str = "",
|
|
319
266
|
prefix: str = "crackerjack-",
|
|
320
267
|
) -> ManagedTemporaryDirectory:
|
|
321
|
-
"""Create a managed temporary directory."""
|
|
322
268
|
return ManagedTemporaryDirectory(suffix, prefix, self.resource_manager)
|
|
323
269
|
|
|
324
270
|
def managed_process(
|
|
@@ -326,7 +272,6 @@ class ResourceContext:
|
|
|
326
272
|
process: asyncio.subprocess.Process,
|
|
327
273
|
timeout: float = 30.0,
|
|
328
274
|
) -> ManagedProcess:
|
|
329
|
-
"""Create a managed process."""
|
|
330
275
|
return ManagedProcess(process, timeout, self.resource_manager)
|
|
331
276
|
|
|
332
277
|
def managed_task(
|
|
@@ -334,14 +279,12 @@ class ResourceContext:
|
|
|
334
279
|
task: asyncio.Task[t.Any],
|
|
335
280
|
timeout: float = 30.0,
|
|
336
281
|
) -> ManagedTask:
|
|
337
|
-
"""Create a managed task."""
|
|
338
282
|
return ManagedTask(task, timeout, self.resource_manager)
|
|
339
283
|
|
|
340
284
|
def managed_file(
|
|
341
285
|
self,
|
|
342
286
|
file_handle: t.IO[t.Any],
|
|
343
287
|
) -> ManagedFileHandle:
|
|
344
|
-
"""Create a managed file handle."""
|
|
345
288
|
return ManagedFileHandle(file_handle, self.resource_manager)
|
|
346
289
|
|
|
347
290
|
async def __aenter__(self) -> "ResourceContext":
|
|
@@ -357,18 +300,15 @@ class ResourceContext:
|
|
|
357
300
|
await self.resource_manager.__aexit__(exc_type, exc_val, exc_tb)
|
|
358
301
|
|
|
359
302
|
|
|
360
|
-
# Global resource manager registry
|
|
361
303
|
_global_managers: weakref.WeakSet[ResourceManager] = weakref.WeakSet()
|
|
362
304
|
|
|
363
305
|
|
|
364
306
|
def register_global_resource_manager(manager: ResourceManager) -> None:
|
|
365
|
-
"""Register a resource manager for global cleanup."""
|
|
366
307
|
_global_managers.add(manager)
|
|
367
308
|
|
|
368
309
|
|
|
369
310
|
async def cleanup_all_global_resources() -> None:
|
|
370
|
-
|
|
371
|
-
managers = list(_global_managers)
|
|
311
|
+
managers = list[t.Any](_global_managers)
|
|
372
312
|
|
|
373
313
|
cleanup_tasks = [asyncio.create_task(manager.cleanup_all()) for manager in managers]
|
|
374
314
|
|
|
@@ -376,17 +316,16 @@ async def cleanup_all_global_resources() -> None:
|
|
|
376
316
|
await asyncio.gather(*cleanup_tasks, return_exceptions=True)
|
|
377
317
|
|
|
378
318
|
|
|
379
|
-
# Cleanup helper context managers
|
|
380
319
|
@contextlib.asynccontextmanager
|
|
381
|
-
async def with_resource_cleanup():
|
|
382
|
-
"""Context manager that ensures resource cleanup on any exit."""
|
|
320
|
+
async def with_resource_cleanup() -> t.AsyncIterator[ResourceContext]:
|
|
383
321
|
async with ResourceContext() as ctx:
|
|
384
322
|
yield ctx
|
|
385
323
|
|
|
386
324
|
|
|
387
325
|
@contextlib.asynccontextmanager
|
|
388
|
-
async def with_temp_file(
|
|
389
|
-
|
|
326
|
+
async def with_temp_file(
|
|
327
|
+
suffix: str = "", prefix: str = "crackerjack-"
|
|
328
|
+
) -> t.AsyncIterator[ManagedTemporaryFile]:
|
|
390
329
|
async with ResourceContext() as ctx:
|
|
391
330
|
temp_file = ctx.managed_temp_file(suffix, prefix)
|
|
392
331
|
try:
|
|
@@ -396,8 +335,9 @@ async def with_temp_file(suffix: str = "", prefix: str = "crackerjack-"):
|
|
|
396
335
|
|
|
397
336
|
|
|
398
337
|
@contextlib.asynccontextmanager
|
|
399
|
-
async def with_temp_dir(
|
|
400
|
-
|
|
338
|
+
async def with_temp_dir(
|
|
339
|
+
suffix: str = "", prefix: str = "crackerjack-"
|
|
340
|
+
) -> t.AsyncIterator[ManagedTemporaryDirectory]:
|
|
401
341
|
async with ResourceContext() as ctx:
|
|
402
342
|
temp_dir = ctx.managed_temp_dir(suffix, prefix)
|
|
403
343
|
try:
|
|
@@ -410,20 +350,16 @@ async def with_temp_dir(suffix: str = "", prefix: str = "crackerjack-"):
|
|
|
410
350
|
async def with_managed_process(
|
|
411
351
|
process: asyncio.subprocess.Process,
|
|
412
352
|
timeout: float = 30.0,
|
|
413
|
-
):
|
|
414
|
-
"""Context manager for process with automatic cleanup."""
|
|
353
|
+
) -> t.AsyncIterator[asyncio.subprocess.Process]:
|
|
415
354
|
async with ResourceContext() as ctx:
|
|
416
355
|
managed_proc = ctx.managed_process(process, timeout)
|
|
417
356
|
try:
|
|
418
|
-
yield managed_proc
|
|
357
|
+
yield managed_proc.process
|
|
419
358
|
finally:
|
|
420
359
|
await managed_proc.cleanup()
|
|
421
360
|
|
|
422
361
|
|
|
423
|
-
# Enhanced error handling utilities
|
|
424
362
|
class ResourceLeakDetector:
|
|
425
|
-
"""Detector for potential resource leaks during development."""
|
|
426
|
-
|
|
427
363
|
def __init__(self) -> None:
|
|
428
364
|
self.open_files: set[str] = set()
|
|
429
365
|
self.active_processes: set[int] = set()
|
|
@@ -431,35 +367,28 @@ class ResourceLeakDetector:
|
|
|
431
367
|
self._start_time = time.time()
|
|
432
368
|
|
|
433
369
|
def track_file(self, file_path: str) -> None:
|
|
434
|
-
"""Track an opened file."""
|
|
435
370
|
self.open_files.add(file_path)
|
|
436
371
|
|
|
437
372
|
def untrack_file(self, file_path: str) -> None:
|
|
438
|
-
"""Untrack a closed file."""
|
|
439
373
|
self.open_files.discard(file_path)
|
|
440
374
|
|
|
441
375
|
def track_process(self, pid: int) -> None:
|
|
442
|
-
"""Track a spawned process."""
|
|
443
376
|
self.active_processes.add(pid)
|
|
444
377
|
|
|
445
378
|
def untrack_process(self, pid: int) -> None:
|
|
446
|
-
"""Untrack a terminated process."""
|
|
447
379
|
self.active_processes.discard(pid)
|
|
448
380
|
|
|
449
381
|
def track_task(self, task: asyncio.Task[t.Any]) -> None:
|
|
450
|
-
"""Track an active task."""
|
|
451
382
|
self.active_tasks.add(task)
|
|
452
383
|
|
|
453
384
|
def untrack_task(self, task: asyncio.Task[t.Any]) -> None:
|
|
454
|
-
"""Untrack a completed task."""
|
|
455
385
|
self.active_tasks.discard(task)
|
|
456
386
|
|
|
457
387
|
def get_leak_report(self) -> dict[str, t.Any]:
|
|
458
|
-
"""Get a report of potential resource leaks."""
|
|
459
388
|
return {
|
|
460
389
|
"duration_seconds": time.time() - self._start_time,
|
|
461
|
-
"open_files": list(self.open_files),
|
|
462
|
-
"active_processes": list(self.active_processes),
|
|
390
|
+
"open_files": list[t.Any](self.open_files),
|
|
391
|
+
"active_processes": list[t.Any](self.active_processes),
|
|
463
392
|
"active_tasks": len([t for t in self.active_tasks if not t.done()]),
|
|
464
393
|
"total_tracked_files": len(self.open_files),
|
|
465
394
|
"total_tracked_processes": len(self.active_processes),
|
|
@@ -467,7 +396,6 @@ class ResourceLeakDetector:
|
|
|
467
396
|
}
|
|
468
397
|
|
|
469
398
|
def has_potential_leaks(self) -> bool:
|
|
470
|
-
"""Check if there are potential resource leaks."""
|
|
471
399
|
return bool(
|
|
472
400
|
self.open_files
|
|
473
401
|
or self.active_processes
|
|
@@ -475,24 +403,20 @@ class ResourceLeakDetector:
|
|
|
475
403
|
)
|
|
476
404
|
|
|
477
405
|
|
|
478
|
-
# Development-time resource leak detection
|
|
479
406
|
_leak_detector: ResourceLeakDetector | None = None
|
|
480
407
|
|
|
481
408
|
|
|
482
409
|
def enable_leak_detection() -> ResourceLeakDetector:
|
|
483
|
-
"""Enable resource leak detection for development."""
|
|
484
410
|
global _leak_detector
|
|
485
411
|
_leak_detector = ResourceLeakDetector()
|
|
486
412
|
return _leak_detector
|
|
487
413
|
|
|
488
414
|
|
|
489
415
|
def get_leak_detector() -> ResourceLeakDetector | None:
|
|
490
|
-
"""Get the current resource leak detector, if enabled."""
|
|
491
416
|
return _leak_detector
|
|
492
417
|
|
|
493
418
|
|
|
494
419
|
def disable_leak_detection() -> dict[str, t.Any] | None:
|
|
495
|
-
"""Disable resource leak detection and return final report."""
|
|
496
420
|
global _leak_detector
|
|
497
421
|
if _leak_detector:
|
|
498
422
|
report = _leak_detector.get_leak_report()
|