crackerjack 0.33.0__py3-none-any.whl → 0.33.2__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 +4 -13
- 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 +104 -204
- 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 +171 -174
- 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 +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -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 +17 -16
- 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 +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- 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 +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- 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 +6 -35
- 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 +15 -9
- crackerjack/services/config_merge.py +19 -80
- 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 +27 -25
- 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 +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- 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 +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- 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 +618 -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 +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- 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.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
"""WebSocket and network resource lifecycle management.
|
|
2
|
-
|
|
3
|
-
Provides comprehensive cleanup patterns for WebSocket connections,
|
|
4
|
-
HTTP clients, and network resources to prevent resource leaks.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
1
|
import asyncio
|
|
8
2
|
import contextlib
|
|
9
3
|
import logging
|
|
@@ -19,11 +13,9 @@ from .resource_manager import ManagedResource, ResourceManager
|
|
|
19
13
|
|
|
20
14
|
|
|
21
15
|
class ManagedWebSocketConnection(ManagedResource):
|
|
22
|
-
"""WebSocket connection with automatic cleanup on error scenarios."""
|
|
23
|
-
|
|
24
16
|
def __init__(
|
|
25
17
|
self,
|
|
26
|
-
websocket: t.Any,
|
|
18
|
+
websocket: t.Any,
|
|
27
19
|
manager: ResourceManager | None = None,
|
|
28
20
|
) -> None:
|
|
29
21
|
super().__init__(manager)
|
|
@@ -31,20 +23,17 @@ class ManagedWebSocketConnection(ManagedResource):
|
|
|
31
23
|
self.logger = logging.getLogger(__name__)
|
|
32
24
|
|
|
33
25
|
async def cleanup(self) -> None:
|
|
34
|
-
"""Clean up WebSocket connection."""
|
|
35
26
|
if not self._closed and not self.websocket.closed:
|
|
36
27
|
self._closed = True
|
|
37
28
|
|
|
38
29
|
try:
|
|
39
30
|
await asyncio.wait_for(self.websocket.close(), timeout=5.0)
|
|
40
31
|
except (TimeoutError, websockets.ConnectionClosed):
|
|
41
|
-
# Expected when connection is already closed or times out
|
|
42
32
|
pass
|
|
43
33
|
except Exception as e:
|
|
44
34
|
self.logger.warning(f"Error closing WebSocket connection: {e}")
|
|
45
35
|
|
|
46
36
|
async def send_safe(self, message: str) -> bool:
|
|
47
|
-
"""Send message safely, returning True if successful."""
|
|
48
37
|
if self._closed or self.websocket.closed:
|
|
49
38
|
return False
|
|
50
39
|
|
|
@@ -61,8 +50,6 @@ class ManagedWebSocketConnection(ManagedResource):
|
|
|
61
50
|
|
|
62
51
|
|
|
63
52
|
class ManagedHTTPClient(ManagedResource):
|
|
64
|
-
"""HTTP client session with automatic cleanup."""
|
|
65
|
-
|
|
66
53
|
def __init__(
|
|
67
54
|
self,
|
|
68
55
|
session: aiohttp.ClientSession,
|
|
@@ -72,7 +59,6 @@ class ManagedHTTPClient(ManagedResource):
|
|
|
72
59
|
self.session = session
|
|
73
60
|
|
|
74
61
|
async def cleanup(self) -> None:
|
|
75
|
-
"""Clean up HTTP client session."""
|
|
76
62
|
if not self._closed and not self.session.closed:
|
|
77
63
|
self._closed = True
|
|
78
64
|
|
|
@@ -83,8 +69,6 @@ class ManagedHTTPClient(ManagedResource):
|
|
|
83
69
|
|
|
84
70
|
|
|
85
71
|
class ManagedWebSocketServer(ManagedResource):
|
|
86
|
-
"""WebSocket server with comprehensive lifecycle management."""
|
|
87
|
-
|
|
88
72
|
def __init__(
|
|
89
73
|
self,
|
|
90
74
|
port: int,
|
|
@@ -94,7 +78,7 @@ class ManagedWebSocketServer(ManagedResource):
|
|
|
94
78
|
super().__init__(manager)
|
|
95
79
|
self.port = port
|
|
96
80
|
self.host = host
|
|
97
|
-
self.server: t.Any | None = None
|
|
81
|
+
self.server: t.Any | None = None
|
|
98
82
|
self.connections: set[ManagedWebSocketConnection] = set()
|
|
99
83
|
self.logger = logging.getLogger(__name__)
|
|
100
84
|
self._server_task: asyncio.Task[t.Any] | None = None
|
|
@@ -103,13 +87,11 @@ class ManagedWebSocketServer(ManagedResource):
|
|
|
103
87
|
self,
|
|
104
88
|
handler: t.Callable[[t.Any], t.Awaitable[None]],
|
|
105
89
|
) -> None:
|
|
106
|
-
"""Start the WebSocket server."""
|
|
107
90
|
if self.server:
|
|
108
91
|
return
|
|
109
92
|
|
|
110
|
-
# Wrap handler to manage connections
|
|
111
93
|
async def managed_handler(
|
|
112
|
-
websocket: t.Any,
|
|
94
|
+
websocket: t.Any,
|
|
113
95
|
) -> None:
|
|
114
96
|
managed_conn = ManagedWebSocketConnection(websocket, self.manager)
|
|
115
97
|
self.connections.add(managed_conn)
|
|
@@ -124,26 +106,24 @@ class ManagedWebSocketServer(ManagedResource):
|
|
|
124
106
|
managed_handler,
|
|
125
107
|
self.host,
|
|
126
108
|
self.port,
|
|
127
|
-
# Configure timeouts and limits
|
|
128
109
|
ping_interval=20,
|
|
129
110
|
ping_timeout=10,
|
|
130
111
|
close_timeout=10,
|
|
131
|
-
max_size=2**20,
|
|
112
|
+
max_size=2**20,
|
|
132
113
|
max_queue=32,
|
|
133
114
|
)
|
|
134
115
|
|
|
135
|
-
self.logger.info(f"WebSocket server started on {self.host}:{self.port}")
|
|
116
|
+
self.logger.info(f"WebSocket server started on {self.host}: {self.port}")
|
|
136
117
|
|
|
137
118
|
async def cleanup(self) -> None:
|
|
138
|
-
"""Clean up WebSocket server and all connections."""
|
|
139
119
|
if self._closed:
|
|
140
120
|
return
|
|
141
121
|
self._closed = True
|
|
142
122
|
|
|
143
|
-
# Close all active connections
|
|
144
123
|
if self.connections:
|
|
145
124
|
close_tasks = [
|
|
146
|
-
asyncio.create_task(conn.cleanup())
|
|
125
|
+
asyncio.create_task(conn.cleanup())
|
|
126
|
+
for conn in list[t.Any](self.connections)
|
|
147
127
|
]
|
|
148
128
|
|
|
149
129
|
if close_tasks:
|
|
@@ -151,7 +131,6 @@ class ManagedWebSocketServer(ManagedResource):
|
|
|
151
131
|
|
|
152
132
|
self.connections.clear()
|
|
153
133
|
|
|
154
|
-
# Close server
|
|
155
134
|
if self.server:
|
|
156
135
|
try:
|
|
157
136
|
self.server.close()
|
|
@@ -163,13 +142,10 @@ class ManagedWebSocketServer(ManagedResource):
|
|
|
163
142
|
self.server = None
|
|
164
143
|
|
|
165
144
|
def get_connection_count(self) -> int:
|
|
166
|
-
"""Get the number of active connections."""
|
|
167
145
|
return len([conn for conn in self.connections if not conn._closed])
|
|
168
146
|
|
|
169
147
|
|
|
170
148
|
class ManagedSubprocess(ManagedResource):
|
|
171
|
-
"""Subprocess with enhanced lifecycle management and resource cleanup."""
|
|
172
|
-
|
|
173
149
|
def __init__(
|
|
174
150
|
self,
|
|
175
151
|
process: subprocess.Popen[bytes],
|
|
@@ -183,19 +159,16 @@ class ManagedSubprocess(ManagedResource):
|
|
|
183
159
|
self._monitor_task: asyncio.Task[t.Any] | None = None
|
|
184
160
|
|
|
185
161
|
async def start_monitoring(self) -> None:
|
|
186
|
-
"""Start monitoring the process for unexpected termination."""
|
|
187
162
|
if self._monitor_task:
|
|
188
163
|
return
|
|
189
164
|
|
|
190
165
|
self._monitor_task = asyncio.create_task(self._monitor_process())
|
|
191
166
|
|
|
192
167
|
async def _monitor_process(self) -> None:
|
|
193
|
-
"""Monitor process health and log unexpected terminations."""
|
|
194
168
|
try:
|
|
195
169
|
while self.process.poll() is None:
|
|
196
170
|
await asyncio.sleep(5.0)
|
|
197
171
|
|
|
198
|
-
# Process has terminated
|
|
199
172
|
return_code = self.process.returncode
|
|
200
173
|
if return_code != 0:
|
|
201
174
|
self.logger.warning(
|
|
@@ -207,12 +180,10 @@ class ManagedSubprocess(ManagedResource):
|
|
|
207
180
|
self.logger.warning(f"Error monitoring process: {e}")
|
|
208
181
|
|
|
209
182
|
async def cleanup(self) -> None:
|
|
210
|
-
"""Clean up subprocess with graceful termination."""
|
|
211
183
|
if self._closed:
|
|
212
184
|
return
|
|
213
185
|
self._closed = True
|
|
214
186
|
|
|
215
|
-
# Cancel monitoring
|
|
216
187
|
if self._monitor_task and not self._monitor_task.done():
|
|
217
188
|
self._monitor_task.cancel()
|
|
218
189
|
try:
|
|
@@ -220,10 +191,8 @@ class ManagedSubprocess(ManagedResource):
|
|
|
220
191
|
except asyncio.CancelledError:
|
|
221
192
|
pass
|
|
222
193
|
|
|
223
|
-
# Clean up process
|
|
224
194
|
if self.process.poll() is None:
|
|
225
195
|
try:
|
|
226
|
-
# Try graceful termination first
|
|
227
196
|
self.process.terminate()
|
|
228
197
|
|
|
229
198
|
try:
|
|
@@ -232,7 +201,6 @@ class ManagedSubprocess(ManagedResource):
|
|
|
232
201
|
f"Process {self.process.pid} terminated gracefully"
|
|
233
202
|
)
|
|
234
203
|
except subprocess.TimeoutExpired:
|
|
235
|
-
# Force kill if graceful termination fails
|
|
236
204
|
self.process.kill()
|
|
237
205
|
try:
|
|
238
206
|
self.process.wait(timeout=2.0)
|
|
@@ -243,7 +211,6 @@ class ManagedSubprocess(ManagedResource):
|
|
|
243
211
|
)
|
|
244
212
|
|
|
245
213
|
except ProcessLookupError:
|
|
246
|
-
# Process already terminated
|
|
247
214
|
pass
|
|
248
215
|
except Exception as e:
|
|
249
216
|
self.logger.warning(
|
|
@@ -251,13 +218,10 @@ class ManagedSubprocess(ManagedResource):
|
|
|
251
218
|
)
|
|
252
219
|
|
|
253
220
|
def is_running(self) -> bool:
|
|
254
|
-
"""Check if the process is still running."""
|
|
255
221
|
return not self._closed and self.process.poll() is None
|
|
256
222
|
|
|
257
223
|
|
|
258
224
|
class NetworkResourceManager:
|
|
259
|
-
"""Manager for network-related resources with health monitoring."""
|
|
260
|
-
|
|
261
225
|
def __init__(self) -> None:
|
|
262
226
|
self.resource_manager = ResourceManager()
|
|
263
227
|
self.logger = logging.getLogger(__name__)
|
|
@@ -267,7 +231,6 @@ class NetworkResourceManager:
|
|
|
267
231
|
port: int,
|
|
268
232
|
host: str = "127.0.0.1",
|
|
269
233
|
) -> ManagedWebSocketServer:
|
|
270
|
-
"""Create a managed WebSocket server."""
|
|
271
234
|
server = ManagedWebSocketServer(port, host, self.resource_manager)
|
|
272
235
|
return server
|
|
273
236
|
|
|
@@ -276,7 +239,6 @@ class NetworkResourceManager:
|
|
|
276
239
|
timeout: aiohttp.ClientTimeout | None = None,
|
|
277
240
|
**kwargs: t.Any,
|
|
278
241
|
) -> ManagedHTTPClient:
|
|
279
|
-
"""Create a managed HTTP client session."""
|
|
280
242
|
timeout = timeout or aiohttp.ClientTimeout(total=30.0)
|
|
281
243
|
session = aiohttp.ClientSession(timeout=timeout, **kwargs)
|
|
282
244
|
return ManagedHTTPClient(session, self.resource_manager)
|
|
@@ -286,16 +248,14 @@ class NetworkResourceManager:
|
|
|
286
248
|
process: subprocess.Popen[bytes],
|
|
287
249
|
timeout: float = 30.0,
|
|
288
250
|
) -> ManagedSubprocess:
|
|
289
|
-
"""Create a managed subprocess."""
|
|
290
251
|
return ManagedSubprocess(process, timeout, self.resource_manager)
|
|
291
252
|
|
|
292
253
|
async def check_port_available(self, port: int, host: str = "127.0.0.1") -> bool:
|
|
293
|
-
"""Check if a port is available for binding."""
|
|
294
254
|
try:
|
|
295
255
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
296
256
|
sock.settimeout(1.0)
|
|
297
257
|
result = sock.connect_ex((host, port))
|
|
298
|
-
return result != 0
|
|
258
|
+
return result != 0
|
|
299
259
|
except Exception:
|
|
300
260
|
return False
|
|
301
261
|
|
|
@@ -306,7 +266,6 @@ class NetworkResourceManager:
|
|
|
306
266
|
timeout: float = 30.0,
|
|
307
267
|
poll_interval: float = 0.5,
|
|
308
268
|
) -> bool:
|
|
309
|
-
"""Wait for a port to become available (service to start)."""
|
|
310
269
|
start_time = time.time()
|
|
311
270
|
|
|
312
271
|
while time.time() - start_time < timeout:
|
|
@@ -314,7 +273,7 @@ class NetworkResourceManager:
|
|
|
314
273
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
315
274
|
sock.settimeout(1.0)
|
|
316
275
|
result = sock.connect_ex((host, port))
|
|
317
|
-
if result == 0:
|
|
276
|
+
if result == 0:
|
|
318
277
|
return True
|
|
319
278
|
|
|
320
279
|
await asyncio.sleep(poll_interval)
|
|
@@ -322,7 +281,6 @@ class NetworkResourceManager:
|
|
|
322
281
|
return False
|
|
323
282
|
|
|
324
283
|
async def cleanup_all(self) -> None:
|
|
325
|
-
"""Clean up all managed network resources."""
|
|
326
284
|
await self.resource_manager.cleanup_all()
|
|
327
285
|
|
|
328
286
|
async def __aenter__(self) -> "NetworkResourceManager":
|
|
@@ -337,14 +295,12 @@ class NetworkResourceManager:
|
|
|
337
295
|
await self.cleanup_all()
|
|
338
296
|
|
|
339
297
|
|
|
340
|
-
# Context manager helpers for common network resource patterns
|
|
341
298
|
@contextlib.asynccontextmanager
|
|
342
299
|
async def with_websocket_server(
|
|
343
300
|
port: int,
|
|
344
301
|
handler: t.Callable[[t.Any], t.Awaitable[None]],
|
|
345
302
|
host: str = "127.0.0.1",
|
|
346
|
-
):
|
|
347
|
-
"""Context manager for a WebSocket server with automatic cleanup."""
|
|
303
|
+
) -> t.AsyncIterator[t.Any]:
|
|
348
304
|
async with NetworkResourceManager() as manager:
|
|
349
305
|
server = await manager.create_websocket_server(port, host)
|
|
350
306
|
try:
|
|
@@ -355,8 +311,7 @@ async def with_websocket_server(
|
|
|
355
311
|
|
|
356
312
|
|
|
357
313
|
@contextlib.asynccontextmanager
|
|
358
|
-
async def with_http_client(**kwargs: t.Any):
|
|
359
|
-
"""Context manager for an HTTP client with automatic cleanup."""
|
|
314
|
+
async def with_http_client(**kwargs: t.Any) -> t.AsyncIterator[t.Any]:
|
|
360
315
|
async with NetworkResourceManager() as manager:
|
|
361
316
|
client = await manager.create_http_client(**kwargs)
|
|
362
317
|
try:
|
|
@@ -370,10 +325,8 @@ async def with_managed_subprocess(
|
|
|
370
325
|
command: list[str],
|
|
371
326
|
timeout: float = 30.0,
|
|
372
327
|
**popen_kwargs: t.Any,
|
|
373
|
-
):
|
|
374
|
-
"""Context manager for a subprocess with automatic cleanup."""
|
|
328
|
+
) -> t.AsyncIterator[t.Any]:
|
|
375
329
|
async with NetworkResourceManager() as manager:
|
|
376
|
-
# Ensure we get bytes output for consistent type handling
|
|
377
330
|
process = subprocess.Popen[bytes](command, text=False, **popen_kwargs)
|
|
378
331
|
managed_proc = manager.create_subprocess(process, timeout)
|
|
379
332
|
try:
|
|
@@ -384,8 +337,6 @@ async def with_managed_subprocess(
|
|
|
384
337
|
|
|
385
338
|
|
|
386
339
|
class WebSocketHealthMonitor:
|
|
387
|
-
"""Health monitor for WebSocket connections and servers."""
|
|
388
|
-
|
|
389
340
|
def __init__(self, check_interval: float = 30.0) -> None:
|
|
390
341
|
self.check_interval = check_interval
|
|
391
342
|
self.monitored_servers: list[ManagedWebSocketServer] = []
|
|
@@ -393,23 +344,19 @@ class WebSocketHealthMonitor:
|
|
|
393
344
|
self._monitor_task: asyncio.Task[t.Any] | None = None
|
|
394
345
|
|
|
395
346
|
def add_server(self, server: ManagedWebSocketServer) -> None:
|
|
396
|
-
"""Add a server to health monitoring."""
|
|
397
347
|
self.monitored_servers.append(server)
|
|
398
348
|
|
|
399
349
|
def remove_server(self, server: ManagedWebSocketServer) -> None:
|
|
400
|
-
"""Remove a server from health monitoring."""
|
|
401
350
|
if server in self.monitored_servers:
|
|
402
351
|
self.monitored_servers.remove(server)
|
|
403
352
|
|
|
404
353
|
async def start_monitoring(self) -> None:
|
|
405
|
-
"""Start health monitoring."""
|
|
406
354
|
if self._monitor_task:
|
|
407
355
|
return
|
|
408
356
|
|
|
409
357
|
self._monitor_task = asyncio.create_task(self._monitor_loop())
|
|
410
358
|
|
|
411
359
|
async def stop_monitoring(self) -> None:
|
|
412
|
-
"""Stop health monitoring."""
|
|
413
360
|
if self._monitor_task and not self._monitor_task.done():
|
|
414
361
|
self._monitor_task.cancel()
|
|
415
362
|
try:
|
|
@@ -418,7 +365,6 @@ class WebSocketHealthMonitor:
|
|
|
418
365
|
pass
|
|
419
366
|
|
|
420
367
|
async def _monitor_loop(self) -> None:
|
|
421
|
-
"""Main monitoring loop."""
|
|
422
368
|
try:
|
|
423
369
|
while True:
|
|
424
370
|
for server in self.monitored_servers.copy():
|
|
@@ -433,37 +379,26 @@ class WebSocketHealthMonitor:
|
|
|
433
379
|
pass
|
|
434
380
|
|
|
435
381
|
async def _check_server_health(self, server: ManagedWebSocketServer) -> None:
|
|
436
|
-
"""Check health of a specific server."""
|
|
437
382
|
if server._closed:
|
|
438
383
|
self.remove_server(server)
|
|
439
384
|
return
|
|
440
385
|
|
|
441
386
|
connection_count = server.get_connection_count()
|
|
442
387
|
|
|
443
|
-
# Log health metrics
|
|
444
388
|
self.logger.debug(
|
|
445
|
-
f"Server {server.host}:{server.port} - "
|
|
389
|
+
f"Server {server.host}: {server.port} - "
|
|
446
390
|
f"Active connections: {connection_count}"
|
|
447
391
|
)
|
|
448
392
|
|
|
449
|
-
# Could add more sophisticated health checks here:
|
|
450
|
-
# - Memory usage monitoring
|
|
451
|
-
# - Connection rate limiting
|
|
452
|
-
# - Error rate monitoring
|
|
453
|
-
# - Automatic restart on failure
|
|
454
|
-
|
|
455
393
|
|
|
456
|
-
# Global network resource cleanup
|
|
457
394
|
_global_network_managers: list[NetworkResourceManager] = []
|
|
458
395
|
|
|
459
396
|
|
|
460
397
|
def register_network_manager(manager: NetworkResourceManager) -> None:
|
|
461
|
-
"""Register a network resource manager for global cleanup."""
|
|
462
398
|
_global_network_managers.append(manager)
|
|
463
399
|
|
|
464
400
|
|
|
465
401
|
async def cleanup_all_network_resources() -> None:
|
|
466
|
-
"""Clean up all globally registered network resource managers."""
|
|
467
402
|
cleanup_tasks = [
|
|
468
403
|
asyncio.create_task(manager.cleanup_all())
|
|
469
404
|
for manager in _global_network_managers
|