omnimancer-cli 0.1.0__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.
Files changed (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,492 @@
1
+ """
2
+ Error handling for read-before-write functionality.
3
+
4
+ This module provides comprehensive error handling for read-before-write operations,
5
+ including custom exceptions, error recovery strategies, and error context management.
6
+ """
7
+
8
+ import logging
9
+ from dataclasses import dataclass
10
+ from datetime import datetime
11
+ from enum import Enum
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ReadBeforeWriteErrorType(Enum):
18
+ """Types of errors that can occur in read-before-write operations."""
19
+
20
+ FILE_READ_ERROR = "file_read_error"
21
+ FILE_WRITE_ERROR = "file_write_error"
22
+ USER_INTERFACE_ERROR = "user_interface_error"
23
+ PERMISSION_ERROR = "permission_error"
24
+ ENCODING_ERROR = "encoding_error"
25
+ CONTENT_VALIDATION_ERROR = "content_validation_error"
26
+ CALLBACK_ERROR = "callback_error"
27
+ DIFF_GENERATION_ERROR = "diff_generation_error"
28
+ PREVIEW_ERROR = "preview_error"
29
+ USER_REJECTION = "user_rejection"
30
+ TIMEOUT_ERROR = "timeout_error"
31
+
32
+
33
+ class RecoveryStrategy(Enum):
34
+ """Strategies for recovering from read-before-write errors."""
35
+
36
+ RETRY = "retry"
37
+ FALLBACK_TO_REGULAR_WRITE = "fallback_to_regular_write"
38
+ SKIP_OPERATION = "skip_operation"
39
+ PROMPT_USER = "prompt_user"
40
+ LOG_AND_CONTINUE = "log_and_continue"
41
+ ABORT_OPERATION = "abort_operation"
42
+
43
+
44
+ @dataclass
45
+ class ErrorContext:
46
+ """Context information for read-before-write errors."""
47
+
48
+ error_type: ReadBeforeWriteErrorType
49
+ file_path: str
50
+ operation: str # 'create' or 'modify'
51
+ encoding: str
52
+ content_length: int
53
+ timestamp: datetime
54
+ original_exception: Optional[Exception] = None
55
+ recovery_attempted: bool = False
56
+ recovery_strategy: Optional[RecoveryStrategy] = None
57
+ recovery_success: Optional[bool] = None
58
+ additional_context: Dict[str, Any] = None
59
+
60
+ def __post_init__(self):
61
+ if self.additional_context is None:
62
+ self.additional_context = {}
63
+
64
+
65
+ class ReadBeforeWriteError(Exception):
66
+ """Base exception for read-before-write operations."""
67
+
68
+ def __init__(
69
+ self,
70
+ message: str,
71
+ error_type: ReadBeforeWriteErrorType,
72
+ context: Optional[ErrorContext] = None,
73
+ original_exception: Optional[Exception] = None,
74
+ ):
75
+ super().__init__(message)
76
+ self.message = message
77
+ self.error_type = error_type
78
+ self.context = context
79
+ self.original_exception = original_exception
80
+
81
+ def to_dict(self) -> Dict[str, Any]:
82
+ """Convert error to dictionary for serialization."""
83
+ return {
84
+ "message": self.message,
85
+ "error_type": self.error_type.value,
86
+ "context": {
87
+ "file_path": self.context.file_path if self.context else None,
88
+ "operation": self.context.operation if self.context else None,
89
+ "encoding": self.context.encoding if self.context else None,
90
+ "timestamp": (
91
+ self.context.timestamp.isoformat() if self.context else None
92
+ ),
93
+ "recovery_attempted": (
94
+ self.context.recovery_attempted if self.context else False
95
+ ),
96
+ "recovery_strategy": (
97
+ self.context.recovery_strategy.value
98
+ if self.context and self.context.recovery_strategy
99
+ else None
100
+ ),
101
+ "recovery_success": (
102
+ self.context.recovery_success if self.context else None
103
+ ),
104
+ "additional_context": (
105
+ self.context.additional_context if self.context else {}
106
+ ),
107
+ },
108
+ "original_exception": (
109
+ str(self.original_exception) if self.original_exception else None
110
+ ),
111
+ }
112
+
113
+
114
+ class FileReadError(ReadBeforeWriteError):
115
+ """Error reading existing file content."""
116
+
117
+ def __init__(self, file_path: str, original_exception: Exception):
118
+ context = ErrorContext(
119
+ error_type=ReadBeforeWriteErrorType.FILE_READ_ERROR,
120
+ file_path=file_path,
121
+ operation="read",
122
+ encoding="unknown",
123
+ content_length=0,
124
+ timestamp=datetime.now(),
125
+ original_exception=original_exception,
126
+ )
127
+ super().__init__(
128
+ f"Failed to read existing content from {file_path}: {str(original_exception)}",
129
+ ReadBeforeWriteErrorType.FILE_READ_ERROR,
130
+ context,
131
+ original_exception,
132
+ )
133
+
134
+
135
+ class FileWriteError(ReadBeforeWriteError):
136
+ """Error writing file content after review."""
137
+
138
+ def __init__(self, file_path: str, original_exception: Exception):
139
+ context = ErrorContext(
140
+ error_type=ReadBeforeWriteErrorType.FILE_WRITE_ERROR,
141
+ file_path=file_path,
142
+ operation="write",
143
+ encoding="unknown",
144
+ content_length=0,
145
+ timestamp=datetime.now(),
146
+ original_exception=original_exception,
147
+ )
148
+ super().__init__(
149
+ f"Failed to write reviewed content to {file_path}: {str(original_exception)}",
150
+ ReadBeforeWriteErrorType.FILE_WRITE_ERROR,
151
+ context,
152
+ original_exception,
153
+ )
154
+
155
+
156
+ class UserInterfaceError(ReadBeforeWriteError):
157
+ """Error in user interface interaction."""
158
+
159
+ def __init__(self, message: str, original_exception: Optional[Exception] = None):
160
+ context = ErrorContext(
161
+ error_type=ReadBeforeWriteErrorType.USER_INTERFACE_ERROR,
162
+ file_path="unknown",
163
+ operation="ui_interaction",
164
+ encoding="unknown",
165
+ content_length=0,
166
+ timestamp=datetime.now(),
167
+ original_exception=original_exception,
168
+ )
169
+ super().__init__(
170
+ f"User interface error: {message}",
171
+ ReadBeforeWriteErrorType.USER_INTERFACE_ERROR,
172
+ context,
173
+ original_exception,
174
+ )
175
+
176
+
177
+ class CallbackError(ReadBeforeWriteError):
178
+ """Error in user review callback execution."""
179
+
180
+ def __init__(self, file_path: str, original_exception: Exception):
181
+ context = ErrorContext(
182
+ error_type=ReadBeforeWriteErrorType.CALLBACK_ERROR,
183
+ file_path=file_path,
184
+ operation="callback",
185
+ encoding="unknown",
186
+ content_length=0,
187
+ timestamp=datetime.now(),
188
+ original_exception=original_exception,
189
+ )
190
+ super().__init__(
191
+ f"Error in user review callback for {file_path}: {str(original_exception)}",
192
+ ReadBeforeWriteErrorType.CALLBACK_ERROR,
193
+ context,
194
+ original_exception,
195
+ )
196
+
197
+
198
+ class ContentValidationError(ReadBeforeWriteError):
199
+ """Error validating file content."""
200
+
201
+ def __init__(self, file_path: str, validation_issue: str):
202
+ context = ErrorContext(
203
+ error_type=ReadBeforeWriteErrorType.CONTENT_VALIDATION_ERROR,
204
+ file_path=file_path,
205
+ operation="validation",
206
+ encoding="unknown",
207
+ content_length=0,
208
+ timestamp=datetime.now(),
209
+ additional_context={"validation_issue": validation_issue},
210
+ )
211
+ super().__init__(
212
+ f"Content validation failed for {file_path}: {validation_issue}",
213
+ ReadBeforeWriteErrorType.CONTENT_VALIDATION_ERROR,
214
+ context,
215
+ )
216
+
217
+
218
+ class DiffGenerationError(ReadBeforeWriteError):
219
+ """Error generating content diff."""
220
+
221
+ def __init__(self, file_path: str, original_exception: Exception):
222
+ context = ErrorContext(
223
+ error_type=ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR,
224
+ file_path=file_path,
225
+ operation="diff_generation",
226
+ encoding="unknown",
227
+ content_length=0,
228
+ timestamp=datetime.now(),
229
+ original_exception=original_exception,
230
+ )
231
+ super().__init__(
232
+ f"Failed to generate diff for {file_path}: {str(original_exception)}",
233
+ ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR,
234
+ context,
235
+ original_exception,
236
+ )
237
+
238
+
239
+ class UserRejectionError(ReadBeforeWriteError):
240
+ """User rejected the file modification."""
241
+
242
+ def __init__(self, file_path: str, reason: str):
243
+ context = ErrorContext(
244
+ error_type=ReadBeforeWriteErrorType.USER_REJECTION,
245
+ file_path=file_path,
246
+ operation="user_decision",
247
+ encoding="unknown",
248
+ content_length=0,
249
+ timestamp=datetime.now(),
250
+ additional_context={"rejection_reason": reason},
251
+ )
252
+ super().__init__(
253
+ f"User rejected modification of {file_path}: {reason}",
254
+ ReadBeforeWriteErrorType.USER_REJECTION,
255
+ context,
256
+ )
257
+
258
+
259
+ class ReadBeforeWriteErrorHandler:
260
+ """
261
+ Comprehensive error handler for read-before-write operations.
262
+
263
+ Provides error recovery strategies, logging, and fallback mechanisms.
264
+ """
265
+
266
+ def __init__(self, enable_recovery: bool = True, log_errors: bool = True):
267
+ """
268
+ Initialize error handler.
269
+
270
+ Args:
271
+ enable_recovery: Whether to attempt error recovery
272
+ log_errors: Whether to log errors
273
+ """
274
+ self.enable_recovery = enable_recovery
275
+ self.log_errors = log_errors
276
+ self.error_history: List[ReadBeforeWriteError] = []
277
+
278
+ # Recovery strategy mappings
279
+ self.recovery_strategies = {
280
+ ReadBeforeWriteErrorType.FILE_READ_ERROR: RecoveryStrategy.FALLBACK_TO_REGULAR_WRITE,
281
+ ReadBeforeWriteErrorType.FILE_WRITE_ERROR: RecoveryStrategy.RETRY,
282
+ ReadBeforeWriteErrorType.USER_INTERFACE_ERROR: RecoveryStrategy.PROMPT_USER,
283
+ ReadBeforeWriteErrorType.PERMISSION_ERROR: RecoveryStrategy.ABORT_OPERATION,
284
+ ReadBeforeWriteErrorType.ENCODING_ERROR: RecoveryStrategy.PROMPT_USER,
285
+ ReadBeforeWriteErrorType.CONTENT_VALIDATION_ERROR: RecoveryStrategy.PROMPT_USER,
286
+ ReadBeforeWriteErrorType.CALLBACK_ERROR: RecoveryStrategy.FALLBACK_TO_REGULAR_WRITE,
287
+ ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR: RecoveryStrategy.LOG_AND_CONTINUE,
288
+ ReadBeforeWriteErrorType.PREVIEW_ERROR: RecoveryStrategy.LOG_AND_CONTINUE,
289
+ ReadBeforeWriteErrorType.USER_REJECTION: RecoveryStrategy.SKIP_OPERATION,
290
+ ReadBeforeWriteErrorType.TIMEOUT_ERROR: RecoveryStrategy.PROMPT_USER,
291
+ }
292
+
293
+ def handle_error(
294
+ self,
295
+ error: ReadBeforeWriteError,
296
+ retry_count: int = 0,
297
+ max_retries: int = 2,
298
+ ) -> Dict[str, Any]:
299
+ """
300
+ Handle a read-before-write error with appropriate recovery strategy.
301
+
302
+ Args:
303
+ error: The error to handle
304
+ retry_count: Current retry attempt count
305
+ max_retries: Maximum number of retries allowed
306
+
307
+ Returns:
308
+ Dict with error handling result and recovery information
309
+ """
310
+ # Record error in history
311
+ self.error_history.append(error)
312
+
313
+ # Log error if enabled
314
+ if self.log_errors:
315
+ self._log_error(error)
316
+
317
+ # Get recovery strategy
318
+ strategy = self.recovery_strategies.get(
319
+ error.error_type, RecoveryStrategy.ABORT_OPERATION
320
+ )
321
+
322
+ # Apply recovery strategy
323
+ recovery_result = self._apply_recovery_strategy(
324
+ error, strategy, retry_count, max_retries
325
+ )
326
+
327
+ # Update error context with recovery information
328
+ if error.context:
329
+ error.context.recovery_attempted = True
330
+ error.context.recovery_strategy = strategy
331
+ error.context.recovery_success = recovery_result.get("success", False)
332
+
333
+ return {
334
+ "error": error.to_dict(),
335
+ "recovery_strategy": strategy.value,
336
+ "recovery_result": recovery_result,
337
+ "should_retry": recovery_result.get("should_retry", False),
338
+ "should_abort": recovery_result.get("should_abort", False),
339
+ "fallback_action": recovery_result.get("fallback_action"),
340
+ }
341
+
342
+ def _apply_recovery_strategy(
343
+ self,
344
+ error: ReadBeforeWriteError,
345
+ strategy: RecoveryStrategy,
346
+ retry_count: int,
347
+ max_retries: int,
348
+ ) -> Dict[str, Any]:
349
+ """Apply the specified recovery strategy."""
350
+ if not self.enable_recovery:
351
+ return {
352
+ "success": False,
353
+ "message": "Error recovery disabled",
354
+ "should_abort": True,
355
+ }
356
+
357
+ if strategy == RecoveryStrategy.RETRY:
358
+ if retry_count < max_retries:
359
+ return {
360
+ "success": True,
361
+ "message": f"Retrying operation (attempt {retry_count + 1}/{max_retries})",
362
+ "should_retry": True,
363
+ }
364
+ else:
365
+ return {
366
+ "success": False,
367
+ "message": f"Maximum retries ({max_retries}) exceeded",
368
+ "should_abort": True,
369
+ }
370
+
371
+ elif strategy == RecoveryStrategy.FALLBACK_TO_REGULAR_WRITE:
372
+ return {
373
+ "success": True,
374
+ "message": "Falling back to regular write operation without review",
375
+ "fallback_action": "regular_write",
376
+ }
377
+
378
+ elif strategy == RecoveryStrategy.SKIP_OPERATION:
379
+ return {
380
+ "success": True,
381
+ "message": "Skipping operation due to user rejection or other reason",
382
+ "fallback_action": "skip",
383
+ }
384
+
385
+ elif strategy == RecoveryStrategy.LOG_AND_CONTINUE:
386
+ return {
387
+ "success": True,
388
+ "message": "Error logged, continuing with operation",
389
+ "fallback_action": "continue_with_warning",
390
+ }
391
+
392
+ elif strategy == RecoveryStrategy.PROMPT_USER:
393
+ return {
394
+ "success": False,
395
+ "message": "User intervention required",
396
+ "fallback_action": "prompt_user",
397
+ }
398
+
399
+ elif strategy == RecoveryStrategy.ABORT_OPERATION:
400
+ return {
401
+ "success": False,
402
+ "message": "Operation aborted due to unrecoverable error",
403
+ "should_abort": True,
404
+ }
405
+
406
+ return {
407
+ "success": False,
408
+ "message": f"Unknown recovery strategy: {strategy}",
409
+ "should_abort": True,
410
+ }
411
+
412
+ def _log_error(self, error: ReadBeforeWriteError):
413
+ """Log error with appropriate severity level."""
414
+ log_message = f"Read-before-write error: {error.message}"
415
+
416
+ if error.context:
417
+ log_message += f" [File: {error.context.file_path}, Operation: {error.context.operation}]"
418
+
419
+ # Determine log level based on error type
420
+ if error.error_type in [ReadBeforeWriteErrorType.USER_REJECTION]:
421
+ logger.info(log_message)
422
+ elif error.error_type in [
423
+ ReadBeforeWriteErrorType.DIFF_GENERATION_ERROR,
424
+ ReadBeforeWriteErrorType.PREVIEW_ERROR,
425
+ ]:
426
+ logger.warning(log_message)
427
+ else:
428
+ logger.error(log_message, exc_info=error.original_exception)
429
+
430
+ def get_error_statistics(self) -> Dict[str, Any]:
431
+ """Get statistics about handled errors."""
432
+ if not self.error_history:
433
+ return {"total_errors": 0}
434
+
435
+ error_counts = {}
436
+ recovery_counts = {}
437
+
438
+ for error in self.error_history:
439
+ error_type = error.error_type.value
440
+ error_counts[error_type] = error_counts.get(error_type, 0) + 1
441
+
442
+ if error.context and error.context.recovery_strategy:
443
+ recovery_strategy = error.context.recovery_strategy.value
444
+ recovery_counts[recovery_strategy] = (
445
+ recovery_counts.get(recovery_strategy, 0) + 1
446
+ )
447
+
448
+ successful_recoveries = sum(
449
+ 1
450
+ for error in self.error_history
451
+ if error.context and error.context.recovery_success
452
+ )
453
+
454
+ return {
455
+ "total_errors": len(self.error_history),
456
+ "error_types": error_counts,
457
+ "recovery_strategies_used": recovery_counts,
458
+ "successful_recoveries": successful_recoveries,
459
+ "recovery_success_rate": (
460
+ successful_recoveries / len(self.error_history)
461
+ if self.error_history
462
+ else 0
463
+ ),
464
+ }
465
+
466
+ def clear_error_history(self):
467
+ """Clear the error history."""
468
+ self.error_history.clear()
469
+
470
+ def set_recovery_strategy(
471
+ self, error_type: ReadBeforeWriteErrorType, strategy: RecoveryStrategy
472
+ ):
473
+ """Set custom recovery strategy for an error type."""
474
+ self.recovery_strategies[error_type] = strategy
475
+ logger.info(f"Set recovery strategy for {error_type.value}: {strategy.value}")
476
+
477
+
478
+ # Convenience function for creating error handler
479
+ def create_error_handler(
480
+ enable_recovery: bool = True, log_errors: bool = True
481
+ ) -> ReadBeforeWriteErrorHandler:
482
+ """
483
+ Create a configured error handler for read-before-write operations.
484
+
485
+ Args:
486
+ enable_recovery: Whether to enable error recovery
487
+ log_errors: Whether to log errors
488
+
489
+ Returns:
490
+ Configured ReadBeforeWriteErrorHandler instance
491
+ """
492
+ return ReadBeforeWriteErrorHandler(enable_recovery, log_errors)