pomera-ai-commander 1.1.1 → 1.2.2
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.
- package/LICENSE +21 -21
- package/README.md +105 -680
- package/bin/pomera-ai-commander.js +62 -62
- package/core/__init__.py +65 -65
- package/core/app_context.py +482 -482
- package/core/async_text_processor.py +421 -421
- package/core/backup_manager.py +655 -655
- package/core/backup_recovery_manager.py +1199 -1033
- package/core/content_hash_cache.py +508 -508
- package/core/context_menu.py +313 -313
- package/core/data_directory.py +549 -0
- package/core/data_validator.py +1066 -1066
- package/core/database_connection_manager.py +744 -744
- package/core/database_curl_settings_manager.py +608 -608
- package/core/database_promera_ai_settings_manager.py +446 -446
- package/core/database_schema.py +411 -411
- package/core/database_schema_manager.py +395 -395
- package/core/database_settings_manager.py +1507 -1507
- package/core/database_settings_manager_interface.py +456 -456
- package/core/dialog_manager.py +734 -734
- package/core/diff_utils.py +239 -0
- package/core/efficient_line_numbers.py +540 -510
- package/core/error_handler.py +746 -746
- package/core/error_service.py +431 -431
- package/core/event_consolidator.py +511 -511
- package/core/mcp/__init__.py +43 -43
- package/core/mcp/find_replace_diff.py +334 -0
- package/core/mcp/protocol.py +288 -288
- package/core/mcp/schema.py +251 -251
- package/core/mcp/server_stdio.py +299 -299
- package/core/mcp/tool_registry.py +2699 -2345
- package/core/memento.py +275 -0
- package/core/memory_efficient_text_widget.py +711 -711
- package/core/migration_manager.py +914 -914
- package/core/migration_test_suite.py +1085 -1085
- package/core/migration_validator.py +1143 -1143
- package/core/optimized_find_replace.py +714 -714
- package/core/optimized_pattern_engine.py +424 -424
- package/core/optimized_search_highlighter.py +552 -552
- package/core/performance_monitor.py +674 -674
- package/core/persistence_manager.py +712 -712
- package/core/progressive_stats_calculator.py +632 -632
- package/core/regex_pattern_cache.py +529 -529
- package/core/regex_pattern_library.py +350 -350
- package/core/search_operation_manager.py +434 -434
- package/core/settings_defaults_registry.py +1087 -1087
- package/core/settings_integrity_validator.py +1111 -1111
- package/core/settings_serializer.py +557 -557
- package/core/settings_validator.py +1823 -1823
- package/core/smart_stats_calculator.py +709 -709
- package/core/statistics_update_manager.py +619 -619
- package/core/stats_config_manager.py +858 -858
- package/core/streaming_text_handler.py +723 -723
- package/core/task_scheduler.py +596 -596
- package/core/update_pattern_library.py +168 -168
- package/core/visibility_monitor.py +596 -596
- package/core/widget_cache.py +498 -498
- package/mcp.json +51 -61
- package/migrate_data.py +127 -0
- package/package.json +64 -57
- package/pomera.py +7883 -7482
- package/pomera_mcp_server.py +183 -144
- package/requirements.txt +33 -0
- package/scripts/Dockerfile.alpine +43 -0
- package/scripts/Dockerfile.gui-test +54 -0
- package/scripts/Dockerfile.linux +43 -0
- package/scripts/Dockerfile.test-linux +80 -0
- package/scripts/Dockerfile.ubuntu +39 -0
- package/scripts/README.md +53 -0
- package/scripts/build-all.bat +113 -0
- package/scripts/build-docker.bat +53 -0
- package/scripts/build-docker.sh +55 -0
- package/scripts/build-optimized.bat +101 -0
- package/scripts/build.sh +78 -0
- package/scripts/docker-compose.test.yml +27 -0
- package/scripts/docker-compose.yml +32 -0
- package/scripts/postinstall.js +62 -0
- package/scripts/requirements-minimal.txt +33 -0
- package/scripts/test-linux-simple.bat +28 -0
- package/scripts/validate-release-workflow.py +450 -0
- package/tools/__init__.py +4 -4
- package/tools/ai_tools.py +2891 -2891
- package/tools/ascii_art_generator.py +352 -352
- package/tools/base64_tools.py +183 -183
- package/tools/base_tool.py +511 -511
- package/tools/case_tool.py +308 -308
- package/tools/column_tools.py +395 -395
- package/tools/cron_tool.py +884 -884
- package/tools/curl_history.py +600 -600
- package/tools/curl_processor.py +1207 -1207
- package/tools/curl_settings.py +502 -502
- package/tools/curl_tool.py +5467 -5467
- package/tools/diff_viewer.py +1817 -1072
- package/tools/email_extraction_tool.py +248 -248
- package/tools/email_header_analyzer.py +425 -425
- package/tools/extraction_tools.py +250 -250
- package/tools/find_replace.py +2289 -1750
- package/tools/folder_file_reporter.py +1463 -1463
- package/tools/folder_file_reporter_adapter.py +480 -480
- package/tools/generator_tools.py +1216 -1216
- package/tools/hash_generator.py +255 -255
- package/tools/html_tool.py +656 -656
- package/tools/jsonxml_tool.py +729 -729
- package/tools/line_tools.py +419 -419
- package/tools/markdown_tools.py +561 -561
- package/tools/mcp_widget.py +1417 -1417
- package/tools/notes_widget.py +978 -973
- package/tools/number_base_converter.py +372 -372
- package/tools/regex_extractor.py +571 -571
- package/tools/slug_generator.py +310 -310
- package/tools/sorter_tools.py +458 -458
- package/tools/string_escape_tool.py +392 -392
- package/tools/text_statistics_tool.py +365 -365
- package/tools/text_wrapper.py +430 -430
- package/tools/timestamp_converter.py +421 -421
- package/tools/tool_loader.py +710 -710
- package/tools/translator_tools.py +522 -522
- package/tools/url_link_extractor.py +261 -261
- package/tools/url_parser.py +204 -204
- package/tools/whitespace_tools.py +355 -355
- package/tools/word_frequency_counter.py +146 -146
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/app_context.cpython-313.pyc +0 -0
- package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
- package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
- package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
- package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/error_service.cpython-313.pyc +0 -0
- package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
- package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
- package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
- package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
- package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
- package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
- package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
- package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
- package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
- package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
- package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
- package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
package/core/error_service.py
CHANGED
|
@@ -1,431 +1,431 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Error Service - Unified error handling for the application.
|
|
3
|
-
|
|
4
|
-
This module provides centralized error handling to ensure consistent
|
|
5
|
-
logging, user notification, and error tracking across all components.
|
|
6
|
-
|
|
7
|
-
Author: Pomera AI Commander Team
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
import traceback
|
|
12
|
-
from typing import Optional, Callable, Any, Dict, List
|
|
13
|
-
from enum import Enum
|
|
14
|
-
from dataclasses import dataclass, field
|
|
15
|
-
from functools import wraps
|
|
16
|
-
from datetime import datetime
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ErrorSeverity(Enum):
|
|
20
|
-
"""Error severity levels."""
|
|
21
|
-
DEBUG = "debug"
|
|
22
|
-
INFO = "info"
|
|
23
|
-
WARNING = "warning"
|
|
24
|
-
ERROR = "error"
|
|
25
|
-
CRITICAL = "critical"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclass
|
|
29
|
-
class ErrorContext:
|
|
30
|
-
"""
|
|
31
|
-
Context information for an error.
|
|
32
|
-
|
|
33
|
-
Attributes:
|
|
34
|
-
operation: What operation was being performed (e.g., "Loading settings")
|
|
35
|
-
component: Which component/module the error occurred in (e.g., "Settings")
|
|
36
|
-
user_message: User-friendly message to display (if different from technical error)
|
|
37
|
-
technical_details: Additional technical information for logging
|
|
38
|
-
recoverable: Whether the application can continue after this error
|
|
39
|
-
"""
|
|
40
|
-
operation: str
|
|
41
|
-
component: str = ""
|
|
42
|
-
user_message: str = ""
|
|
43
|
-
technical_details: str = ""
|
|
44
|
-
recoverable: bool = True
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@dataclass
|
|
48
|
-
class ErrorRecord:
|
|
49
|
-
"""Record of an error for tracking and statistics."""
|
|
50
|
-
timestamp: datetime
|
|
51
|
-
operation: str
|
|
52
|
-
component: str
|
|
53
|
-
error_type: str
|
|
54
|
-
error_message: str
|
|
55
|
-
severity: ErrorSeverity
|
|
56
|
-
stack_trace: str = ""
|
|
57
|
-
|
|
58
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
59
|
-
"""Convert to dictionary for serialization."""
|
|
60
|
-
return {
|
|
61
|
-
'timestamp': self.timestamp.isoformat(),
|
|
62
|
-
'operation': self.operation,
|
|
63
|
-
'component': self.component,
|
|
64
|
-
'error_type': self.error_type,
|
|
65
|
-
'error_message': self.error_message,
|
|
66
|
-
'severity': self.severity.value,
|
|
67
|
-
'stack_trace': self.stack_trace
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class ErrorService:
|
|
72
|
-
"""
|
|
73
|
-
Centralized error handling service.
|
|
74
|
-
|
|
75
|
-
Provides consistent error handling across the application:
|
|
76
|
-
- Logging with appropriate severity
|
|
77
|
-
- User notification via dialog manager
|
|
78
|
-
- Error tracking and statistics
|
|
79
|
-
- Configurable error suppression
|
|
80
|
-
|
|
81
|
-
Usage:
|
|
82
|
-
error_service = ErrorService(logger, dialog_manager)
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
|
-
risky_operation()
|
|
86
|
-
except Exception as e:
|
|
87
|
-
error_service.handle(
|
|
88
|
-
e,
|
|
89
|
-
ErrorContext(
|
|
90
|
-
operation="Processing text",
|
|
91
|
-
component="TextProcessor",
|
|
92
|
-
user_message="Failed to process the text."
|
|
93
|
-
)
|
|
94
|
-
)
|
|
95
|
-
"""
|
|
96
|
-
|
|
97
|
-
def __init__(self, logger: logging.Logger, dialog_manager=None):
|
|
98
|
-
"""
|
|
99
|
-
Initialize the error service.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
logger: Application logger
|
|
103
|
-
dialog_manager: Optional DialogManager for user notifications
|
|
104
|
-
"""
|
|
105
|
-
self.logger = logger
|
|
106
|
-
self.dialog_manager = dialog_manager
|
|
107
|
-
|
|
108
|
-
# Error tracking
|
|
109
|
-
self._error_history: List[ErrorRecord] = []
|
|
110
|
-
self._error_counts: Dict[str, int] = {}
|
|
111
|
-
self._max_history_size = 100
|
|
112
|
-
|
|
113
|
-
# Configuration
|
|
114
|
-
self._suppress_dialogs = False
|
|
115
|
-
self._log_stack_traces = True
|
|
116
|
-
|
|
117
|
-
def handle(self,
|
|
118
|
-
error: Exception,
|
|
119
|
-
context: ErrorContext,
|
|
120
|
-
severity: ErrorSeverity = ErrorSeverity.ERROR,
|
|
121
|
-
show_dialog: bool = True,
|
|
122
|
-
reraise: bool = False) -> None:
|
|
123
|
-
"""
|
|
124
|
-
Handle an error with consistent logging and user notification.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
error: The exception that occurred
|
|
128
|
-
context: Context information about the error
|
|
129
|
-
severity: Error severity level
|
|
130
|
-
show_dialog: Whether to show a dialog to the user
|
|
131
|
-
reraise: Whether to re-raise the exception after handling
|
|
132
|
-
"""
|
|
133
|
-
# Build log message
|
|
134
|
-
log_parts = []
|
|
135
|
-
if context.component:
|
|
136
|
-
log_parts.append(f"[{context.component}]")
|
|
137
|
-
log_parts.append(f"{context.operation}:")
|
|
138
|
-
log_parts.append(str(error))
|
|
139
|
-
if context.technical_details:
|
|
140
|
-
log_parts.append(f"| Details: {context.technical_details}")
|
|
141
|
-
|
|
142
|
-
log_msg = " ".join(log_parts)
|
|
143
|
-
|
|
144
|
-
# Get stack trace for severe errors
|
|
145
|
-
stack_trace = ""
|
|
146
|
-
if severity in [ErrorSeverity.ERROR, ErrorSeverity.CRITICAL] and self._log_stack_traces:
|
|
147
|
-
stack_trace = traceback.format_exc()
|
|
148
|
-
|
|
149
|
-
# Log with appropriate level
|
|
150
|
-
log_method = getattr(self.logger, severity.value, self.logger.error)
|
|
151
|
-
if stack_trace and severity in [ErrorSeverity.ERROR, ErrorSeverity.CRITICAL]:
|
|
152
|
-
log_method(log_msg, exc_info=True)
|
|
153
|
-
else:
|
|
154
|
-
log_method(log_msg)
|
|
155
|
-
|
|
156
|
-
# Track error
|
|
157
|
-
self._track_error(error, context, severity, stack_trace)
|
|
158
|
-
|
|
159
|
-
# Show user dialog if requested and not suppressed
|
|
160
|
-
if show_dialog and not self._suppress_dialogs:
|
|
161
|
-
self._show_error_dialog(error, context, severity)
|
|
162
|
-
|
|
163
|
-
# Re-raise if requested
|
|
164
|
-
if reraise:
|
|
165
|
-
raise error
|
|
166
|
-
|
|
167
|
-
def handle_with_fallback(self,
|
|
168
|
-
error: Exception,
|
|
169
|
-
context: ErrorContext,
|
|
170
|
-
fallback_value: Any,
|
|
171
|
-
severity: ErrorSeverity = ErrorSeverity.WARNING) -> Any:
|
|
172
|
-
"""
|
|
173
|
-
Handle an error and return a fallback value.
|
|
174
|
-
|
|
175
|
-
Useful for non-critical operations where you want to continue
|
|
176
|
-
with a default value rather than failing.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
error: The exception that occurred
|
|
180
|
-
context: Context information
|
|
181
|
-
fallback_value: Value to return instead of raising
|
|
182
|
-
severity: Error severity level (defaults to WARNING)
|
|
183
|
-
|
|
184
|
-
Returns:
|
|
185
|
-
The fallback value
|
|
186
|
-
"""
|
|
187
|
-
self.handle(error, context, severity, show_dialog=False, reraise=False)
|
|
188
|
-
return fallback_value
|
|
189
|
-
|
|
190
|
-
def log_warning(self,
|
|
191
|
-
message: str,
|
|
192
|
-
context: ErrorContext,
|
|
193
|
-
show_dialog: bool = False) -> None:
|
|
194
|
-
"""
|
|
195
|
-
Log a warning message (not from an exception).
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
message: Warning message
|
|
199
|
-
context: Context information
|
|
200
|
-
show_dialog: Whether to show a dialog to the user
|
|
201
|
-
"""
|
|
202
|
-
log_parts = []
|
|
203
|
-
if context.component:
|
|
204
|
-
log_parts.append(f"[{context.component}]")
|
|
205
|
-
log_parts.append(f"{context.operation}:")
|
|
206
|
-
log_parts.append(message)
|
|
207
|
-
|
|
208
|
-
self.logger.warning(" ".join(log_parts))
|
|
209
|
-
|
|
210
|
-
if show_dialog and not self._suppress_dialogs and self.dialog_manager:
|
|
211
|
-
user_msg = context.user_message or message
|
|
212
|
-
self.dialog_manager.show_warning(context.operation, user_msg)
|
|
213
|
-
|
|
214
|
-
def log_info(self, message: str, context: ErrorContext) -> None:
|
|
215
|
-
"""
|
|
216
|
-
Log an info message with context.
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
message: Info message
|
|
220
|
-
context: Context information
|
|
221
|
-
"""
|
|
222
|
-
log_parts = []
|
|
223
|
-
if context.component:
|
|
224
|
-
log_parts.append(f"[{context.component}]")
|
|
225
|
-
log_parts.append(f"{context.operation}:")
|
|
226
|
-
log_parts.append(message)
|
|
227
|
-
|
|
228
|
-
self.logger.info(" ".join(log_parts))
|
|
229
|
-
|
|
230
|
-
def _show_error_dialog(self,
|
|
231
|
-
error: Exception,
|
|
232
|
-
context: ErrorContext,
|
|
233
|
-
severity: ErrorSeverity) -> None:
|
|
234
|
-
"""Show error dialog to user if dialog manager is available."""
|
|
235
|
-
if not self.dialog_manager:
|
|
236
|
-
return
|
|
237
|
-
|
|
238
|
-
# Determine user message
|
|
239
|
-
user_msg = context.user_message or str(error)
|
|
240
|
-
title = context.operation or "Error"
|
|
241
|
-
|
|
242
|
-
# Show appropriate dialog type
|
|
243
|
-
if severity == ErrorSeverity.WARNING:
|
|
244
|
-
self.dialog_manager.show_warning(title, user_msg)
|
|
245
|
-
elif severity in [ErrorSeverity.ERROR, ErrorSeverity.CRITICAL]:
|
|
246
|
-
self.dialog_manager.show_error(title, user_msg)
|
|
247
|
-
# INFO and DEBUG don't show dialogs
|
|
248
|
-
|
|
249
|
-
def _track_error(self,
|
|
250
|
-
error: Exception,
|
|
251
|
-
context: ErrorContext,
|
|
252
|
-
severity: ErrorSeverity,
|
|
253
|
-
stack_trace: str = "") -> None:
|
|
254
|
-
"""Track error for statistics and history."""
|
|
255
|
-
# Create error record
|
|
256
|
-
record = ErrorRecord(
|
|
257
|
-
timestamp=datetime.now(),
|
|
258
|
-
operation=context.operation,
|
|
259
|
-
component=context.component,
|
|
260
|
-
error_type=type(error).__name__,
|
|
261
|
-
error_message=str(error),
|
|
262
|
-
severity=severity,
|
|
263
|
-
stack_trace=stack_trace
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
# Add to history
|
|
267
|
-
self._error_history.append(record)
|
|
268
|
-
|
|
269
|
-
# Trim history if needed
|
|
270
|
-
if len(self._error_history) > self._max_history_size:
|
|
271
|
-
self._error_history = self._error_history[-self._max_history_size:]
|
|
272
|
-
|
|
273
|
-
# Update counts
|
|
274
|
-
key = f"{context.operation}:{type(error).__name__}"
|
|
275
|
-
self._error_counts[key] = self._error_counts.get(key, 0) + 1
|
|
276
|
-
|
|
277
|
-
def get_error_stats(self) -> Dict[str, Any]:
|
|
278
|
-
"""
|
|
279
|
-
Get error statistics.
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
Dictionary with error counts and recent errors
|
|
283
|
-
"""
|
|
284
|
-
return {
|
|
285
|
-
'total_errors': sum(self._error_counts.values()),
|
|
286
|
-
'error_counts': self._error_counts.copy(),
|
|
287
|
-
'recent_errors': [e.to_dict() for e in self._error_history[-10:]],
|
|
288
|
-
'errors_by_severity': self._count_by_severity()
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
def _count_by_severity(self) -> Dict[str, int]:
|
|
292
|
-
"""Count errors by severity level."""
|
|
293
|
-
counts = {s.value: 0 for s in ErrorSeverity}
|
|
294
|
-
for record in self._error_history:
|
|
295
|
-
counts[record.severity.value] += 1
|
|
296
|
-
return counts
|
|
297
|
-
|
|
298
|
-
def get_recent_errors(self, count: int = 10) -> List[ErrorRecord]:
|
|
299
|
-
"""
|
|
300
|
-
Get recent error records.
|
|
301
|
-
|
|
302
|
-
Args:
|
|
303
|
-
count: Number of recent errors to return
|
|
304
|
-
|
|
305
|
-
Returns:
|
|
306
|
-
List of recent ErrorRecord objects
|
|
307
|
-
"""
|
|
308
|
-
return self._error_history[-count:]
|
|
309
|
-
|
|
310
|
-
def clear_history(self) -> None:
|
|
311
|
-
"""Clear error history and counts."""
|
|
312
|
-
self._error_history.clear()
|
|
313
|
-
self._error_counts.clear()
|
|
314
|
-
|
|
315
|
-
def suppress_dialogs(self, suppress: bool = True) -> None:
|
|
316
|
-
"""
|
|
317
|
-
Suppress or enable error dialogs.
|
|
318
|
-
|
|
319
|
-
Useful during batch operations or testing.
|
|
320
|
-
|
|
321
|
-
Args:
|
|
322
|
-
suppress: True to suppress dialogs, False to enable
|
|
323
|
-
"""
|
|
324
|
-
self._suppress_dialogs = suppress
|
|
325
|
-
|
|
326
|
-
def set_dialog_manager(self, dialog_manager) -> None:
|
|
327
|
-
"""
|
|
328
|
-
Set or update the dialog manager.
|
|
329
|
-
|
|
330
|
-
Args:
|
|
331
|
-
dialog_manager: DialogManager instance
|
|
332
|
-
"""
|
|
333
|
-
self.dialog_manager = dialog_manager
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def error_handler(operation: str,
|
|
337
|
-
component: str = "",
|
|
338
|
-
show_dialog: bool = True,
|
|
339
|
-
fallback: Any = None,
|
|
340
|
-
severity: ErrorSeverity = ErrorSeverity.ERROR):
|
|
341
|
-
"""
|
|
342
|
-
Decorator for consistent error handling on methods.
|
|
343
|
-
|
|
344
|
-
The decorated method's class must have an 'error_service' attribute.
|
|
345
|
-
|
|
346
|
-
Usage:
|
|
347
|
-
class MyClass:
|
|
348
|
-
def __init__(self):
|
|
349
|
-
self.error_service = get_error_service()
|
|
350
|
-
|
|
351
|
-
@error_handler("Loading settings", component="Settings")
|
|
352
|
-
def load_settings(self):
|
|
353
|
-
# risky code here
|
|
354
|
-
pass
|
|
355
|
-
|
|
356
|
-
@error_handler("Parsing data", fallback=[])
|
|
357
|
-
def parse_data(self):
|
|
358
|
-
# returns [] on error
|
|
359
|
-
pass
|
|
360
|
-
|
|
361
|
-
Args:
|
|
362
|
-
operation: Description of the operation
|
|
363
|
-
component: Component/module name
|
|
364
|
-
show_dialog: Whether to show error dialog
|
|
365
|
-
fallback: Value to return on error (None = reraise)
|
|
366
|
-
severity: Error severity level
|
|
367
|
-
"""
|
|
368
|
-
def decorator(func: Callable) -> Callable:
|
|
369
|
-
@wraps(func)
|
|
370
|
-
def wrapper(self, *args, **kwargs):
|
|
371
|
-
try:
|
|
372
|
-
return func(self, *args, **kwargs)
|
|
373
|
-
except Exception as e:
|
|
374
|
-
context = ErrorContext(
|
|
375
|
-
operation=operation,
|
|
376
|
-
component=component,
|
|
377
|
-
user_message=str(e)
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
if hasattr(self, 'error_service') and self.error_service:
|
|
381
|
-
if fallback is not None:
|
|
382
|
-
return self.error_service.handle_with_fallback(
|
|
383
|
-
e, context, fallback, severity
|
|
384
|
-
)
|
|
385
|
-
else:
|
|
386
|
-
self.error_service.handle(
|
|
387
|
-
e, context, severity,
|
|
388
|
-
show_dialog=show_dialog,
|
|
389
|
-
reraise=True
|
|
390
|
-
)
|
|
391
|
-
else:
|
|
392
|
-
# Fallback if error service not available
|
|
393
|
-
if hasattr(self, 'logger') and self.logger:
|
|
394
|
-
self.logger.error(f"{operation}: {e}")
|
|
395
|
-
if fallback is not None:
|
|
396
|
-
return fallback
|
|
397
|
-
raise
|
|
398
|
-
return wrapper
|
|
399
|
-
return decorator
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
# Global instance
|
|
403
|
-
_error_service: Optional[ErrorService] = None
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
def get_error_service() -> Optional[ErrorService]:
|
|
407
|
-
"""Get the global error service instance."""
|
|
408
|
-
return _error_service
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
def init_error_service(logger: logging.Logger, dialog_manager=None) -> ErrorService:
|
|
412
|
-
"""
|
|
413
|
-
Initialize and return the global error service.
|
|
414
|
-
|
|
415
|
-
Args:
|
|
416
|
-
logger: Application logger
|
|
417
|
-
dialog_manager: Optional DialogManager for user notifications
|
|
418
|
-
|
|
419
|
-
Returns:
|
|
420
|
-
Initialized ErrorService instance
|
|
421
|
-
"""
|
|
422
|
-
global _error_service
|
|
423
|
-
_error_service = ErrorService(logger, dialog_manager)
|
|
424
|
-
return _error_service
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
def shutdown_error_service() -> None:
|
|
428
|
-
"""Shutdown the global error service."""
|
|
429
|
-
global _error_service
|
|
430
|
-
_error_service = None
|
|
431
|
-
|
|
1
|
+
"""
|
|
2
|
+
Error Service - Unified error handling for the application.
|
|
3
|
+
|
|
4
|
+
This module provides centralized error handling to ensure consistent
|
|
5
|
+
logging, user notification, and error tracking across all components.
|
|
6
|
+
|
|
7
|
+
Author: Pomera AI Commander Team
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import traceback
|
|
12
|
+
from typing import Optional, Callable, Any, Dict, List
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from functools import wraps
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ErrorSeverity(Enum):
|
|
20
|
+
"""Error severity levels."""
|
|
21
|
+
DEBUG = "debug"
|
|
22
|
+
INFO = "info"
|
|
23
|
+
WARNING = "warning"
|
|
24
|
+
ERROR = "error"
|
|
25
|
+
CRITICAL = "critical"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ErrorContext:
|
|
30
|
+
"""
|
|
31
|
+
Context information for an error.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
operation: What operation was being performed (e.g., "Loading settings")
|
|
35
|
+
component: Which component/module the error occurred in (e.g., "Settings")
|
|
36
|
+
user_message: User-friendly message to display (if different from technical error)
|
|
37
|
+
technical_details: Additional technical information for logging
|
|
38
|
+
recoverable: Whether the application can continue after this error
|
|
39
|
+
"""
|
|
40
|
+
operation: str
|
|
41
|
+
component: str = ""
|
|
42
|
+
user_message: str = ""
|
|
43
|
+
technical_details: str = ""
|
|
44
|
+
recoverable: bool = True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ErrorRecord:
|
|
49
|
+
"""Record of an error for tracking and statistics."""
|
|
50
|
+
timestamp: datetime
|
|
51
|
+
operation: str
|
|
52
|
+
component: str
|
|
53
|
+
error_type: str
|
|
54
|
+
error_message: str
|
|
55
|
+
severity: ErrorSeverity
|
|
56
|
+
stack_trace: str = ""
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
59
|
+
"""Convert to dictionary for serialization."""
|
|
60
|
+
return {
|
|
61
|
+
'timestamp': self.timestamp.isoformat(),
|
|
62
|
+
'operation': self.operation,
|
|
63
|
+
'component': self.component,
|
|
64
|
+
'error_type': self.error_type,
|
|
65
|
+
'error_message': self.error_message,
|
|
66
|
+
'severity': self.severity.value,
|
|
67
|
+
'stack_trace': self.stack_trace
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ErrorService:
|
|
72
|
+
"""
|
|
73
|
+
Centralized error handling service.
|
|
74
|
+
|
|
75
|
+
Provides consistent error handling across the application:
|
|
76
|
+
- Logging with appropriate severity
|
|
77
|
+
- User notification via dialog manager
|
|
78
|
+
- Error tracking and statistics
|
|
79
|
+
- Configurable error suppression
|
|
80
|
+
|
|
81
|
+
Usage:
|
|
82
|
+
error_service = ErrorService(logger, dialog_manager)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
risky_operation()
|
|
86
|
+
except Exception as e:
|
|
87
|
+
error_service.handle(
|
|
88
|
+
e,
|
|
89
|
+
ErrorContext(
|
|
90
|
+
operation="Processing text",
|
|
91
|
+
component="TextProcessor",
|
|
92
|
+
user_message="Failed to process the text."
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, logger: logging.Logger, dialog_manager=None):
|
|
98
|
+
"""
|
|
99
|
+
Initialize the error service.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
logger: Application logger
|
|
103
|
+
dialog_manager: Optional DialogManager for user notifications
|
|
104
|
+
"""
|
|
105
|
+
self.logger = logger
|
|
106
|
+
self.dialog_manager = dialog_manager
|
|
107
|
+
|
|
108
|
+
# Error tracking
|
|
109
|
+
self._error_history: List[ErrorRecord] = []
|
|
110
|
+
self._error_counts: Dict[str, int] = {}
|
|
111
|
+
self._max_history_size = 100
|
|
112
|
+
|
|
113
|
+
# Configuration
|
|
114
|
+
self._suppress_dialogs = False
|
|
115
|
+
self._log_stack_traces = True
|
|
116
|
+
|
|
117
|
+
def handle(self,
|
|
118
|
+
error: Exception,
|
|
119
|
+
context: ErrorContext,
|
|
120
|
+
severity: ErrorSeverity = ErrorSeverity.ERROR,
|
|
121
|
+
show_dialog: bool = True,
|
|
122
|
+
reraise: bool = False) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Handle an error with consistent logging and user notification.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
error: The exception that occurred
|
|
128
|
+
context: Context information about the error
|
|
129
|
+
severity: Error severity level
|
|
130
|
+
show_dialog: Whether to show a dialog to the user
|
|
131
|
+
reraise: Whether to re-raise the exception after handling
|
|
132
|
+
"""
|
|
133
|
+
# Build log message
|
|
134
|
+
log_parts = []
|
|
135
|
+
if context.component:
|
|
136
|
+
log_parts.append(f"[{context.component}]")
|
|
137
|
+
log_parts.append(f"{context.operation}:")
|
|
138
|
+
log_parts.append(str(error))
|
|
139
|
+
if context.technical_details:
|
|
140
|
+
log_parts.append(f"| Details: {context.technical_details}")
|
|
141
|
+
|
|
142
|
+
log_msg = " ".join(log_parts)
|
|
143
|
+
|
|
144
|
+
# Get stack trace for severe errors
|
|
145
|
+
stack_trace = ""
|
|
146
|
+
if severity in [ErrorSeverity.ERROR, ErrorSeverity.CRITICAL] and self._log_stack_traces:
|
|
147
|
+
stack_trace = traceback.format_exc()
|
|
148
|
+
|
|
149
|
+
# Log with appropriate level
|
|
150
|
+
log_method = getattr(self.logger, severity.value, self.logger.error)
|
|
151
|
+
if stack_trace and severity in [ErrorSeverity.ERROR, ErrorSeverity.CRITICAL]:
|
|
152
|
+
log_method(log_msg, exc_info=True)
|
|
153
|
+
else:
|
|
154
|
+
log_method(log_msg)
|
|
155
|
+
|
|
156
|
+
# Track error
|
|
157
|
+
self._track_error(error, context, severity, stack_trace)
|
|
158
|
+
|
|
159
|
+
# Show user dialog if requested and not suppressed
|
|
160
|
+
if show_dialog and not self._suppress_dialogs:
|
|
161
|
+
self._show_error_dialog(error, context, severity)
|
|
162
|
+
|
|
163
|
+
# Re-raise if requested
|
|
164
|
+
if reraise:
|
|
165
|
+
raise error
|
|
166
|
+
|
|
167
|
+
def handle_with_fallback(self,
|
|
168
|
+
error: Exception,
|
|
169
|
+
context: ErrorContext,
|
|
170
|
+
fallback_value: Any,
|
|
171
|
+
severity: ErrorSeverity = ErrorSeverity.WARNING) -> Any:
|
|
172
|
+
"""
|
|
173
|
+
Handle an error and return a fallback value.
|
|
174
|
+
|
|
175
|
+
Useful for non-critical operations where you want to continue
|
|
176
|
+
with a default value rather than failing.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
error: The exception that occurred
|
|
180
|
+
context: Context information
|
|
181
|
+
fallback_value: Value to return instead of raising
|
|
182
|
+
severity: Error severity level (defaults to WARNING)
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
The fallback value
|
|
186
|
+
"""
|
|
187
|
+
self.handle(error, context, severity, show_dialog=False, reraise=False)
|
|
188
|
+
return fallback_value
|
|
189
|
+
|
|
190
|
+
def log_warning(self,
|
|
191
|
+
message: str,
|
|
192
|
+
context: ErrorContext,
|
|
193
|
+
show_dialog: bool = False) -> None:
|
|
194
|
+
"""
|
|
195
|
+
Log a warning message (not from an exception).
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
message: Warning message
|
|
199
|
+
context: Context information
|
|
200
|
+
show_dialog: Whether to show a dialog to the user
|
|
201
|
+
"""
|
|
202
|
+
log_parts = []
|
|
203
|
+
if context.component:
|
|
204
|
+
log_parts.append(f"[{context.component}]")
|
|
205
|
+
log_parts.append(f"{context.operation}:")
|
|
206
|
+
log_parts.append(message)
|
|
207
|
+
|
|
208
|
+
self.logger.warning(" ".join(log_parts))
|
|
209
|
+
|
|
210
|
+
if show_dialog and not self._suppress_dialogs and self.dialog_manager:
|
|
211
|
+
user_msg = context.user_message or message
|
|
212
|
+
self.dialog_manager.show_warning(context.operation, user_msg)
|
|
213
|
+
|
|
214
|
+
def log_info(self, message: str, context: ErrorContext) -> None:
|
|
215
|
+
"""
|
|
216
|
+
Log an info message with context.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
message: Info message
|
|
220
|
+
context: Context information
|
|
221
|
+
"""
|
|
222
|
+
log_parts = []
|
|
223
|
+
if context.component:
|
|
224
|
+
log_parts.append(f"[{context.component}]")
|
|
225
|
+
log_parts.append(f"{context.operation}:")
|
|
226
|
+
log_parts.append(message)
|
|
227
|
+
|
|
228
|
+
self.logger.info(" ".join(log_parts))
|
|
229
|
+
|
|
230
|
+
def _show_error_dialog(self,
|
|
231
|
+
error: Exception,
|
|
232
|
+
context: ErrorContext,
|
|
233
|
+
severity: ErrorSeverity) -> None:
|
|
234
|
+
"""Show error dialog to user if dialog manager is available."""
|
|
235
|
+
if not self.dialog_manager:
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
# Determine user message
|
|
239
|
+
user_msg = context.user_message or str(error)
|
|
240
|
+
title = context.operation or "Error"
|
|
241
|
+
|
|
242
|
+
# Show appropriate dialog type
|
|
243
|
+
if severity == ErrorSeverity.WARNING:
|
|
244
|
+
self.dialog_manager.show_warning(title, user_msg)
|
|
245
|
+
elif severity in [ErrorSeverity.ERROR, ErrorSeverity.CRITICAL]:
|
|
246
|
+
self.dialog_manager.show_error(title, user_msg)
|
|
247
|
+
# INFO and DEBUG don't show dialogs
|
|
248
|
+
|
|
249
|
+
def _track_error(self,
|
|
250
|
+
error: Exception,
|
|
251
|
+
context: ErrorContext,
|
|
252
|
+
severity: ErrorSeverity,
|
|
253
|
+
stack_trace: str = "") -> None:
|
|
254
|
+
"""Track error for statistics and history."""
|
|
255
|
+
# Create error record
|
|
256
|
+
record = ErrorRecord(
|
|
257
|
+
timestamp=datetime.now(),
|
|
258
|
+
operation=context.operation,
|
|
259
|
+
component=context.component,
|
|
260
|
+
error_type=type(error).__name__,
|
|
261
|
+
error_message=str(error),
|
|
262
|
+
severity=severity,
|
|
263
|
+
stack_trace=stack_trace
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Add to history
|
|
267
|
+
self._error_history.append(record)
|
|
268
|
+
|
|
269
|
+
# Trim history if needed
|
|
270
|
+
if len(self._error_history) > self._max_history_size:
|
|
271
|
+
self._error_history = self._error_history[-self._max_history_size:]
|
|
272
|
+
|
|
273
|
+
# Update counts
|
|
274
|
+
key = f"{context.operation}:{type(error).__name__}"
|
|
275
|
+
self._error_counts[key] = self._error_counts.get(key, 0) + 1
|
|
276
|
+
|
|
277
|
+
def get_error_stats(self) -> Dict[str, Any]:
|
|
278
|
+
"""
|
|
279
|
+
Get error statistics.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Dictionary with error counts and recent errors
|
|
283
|
+
"""
|
|
284
|
+
return {
|
|
285
|
+
'total_errors': sum(self._error_counts.values()),
|
|
286
|
+
'error_counts': self._error_counts.copy(),
|
|
287
|
+
'recent_errors': [e.to_dict() for e in self._error_history[-10:]],
|
|
288
|
+
'errors_by_severity': self._count_by_severity()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
def _count_by_severity(self) -> Dict[str, int]:
|
|
292
|
+
"""Count errors by severity level."""
|
|
293
|
+
counts = {s.value: 0 for s in ErrorSeverity}
|
|
294
|
+
for record in self._error_history:
|
|
295
|
+
counts[record.severity.value] += 1
|
|
296
|
+
return counts
|
|
297
|
+
|
|
298
|
+
def get_recent_errors(self, count: int = 10) -> List[ErrorRecord]:
|
|
299
|
+
"""
|
|
300
|
+
Get recent error records.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
count: Number of recent errors to return
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of recent ErrorRecord objects
|
|
307
|
+
"""
|
|
308
|
+
return self._error_history[-count:]
|
|
309
|
+
|
|
310
|
+
def clear_history(self) -> None:
|
|
311
|
+
"""Clear error history and counts."""
|
|
312
|
+
self._error_history.clear()
|
|
313
|
+
self._error_counts.clear()
|
|
314
|
+
|
|
315
|
+
def suppress_dialogs(self, suppress: bool = True) -> None:
|
|
316
|
+
"""
|
|
317
|
+
Suppress or enable error dialogs.
|
|
318
|
+
|
|
319
|
+
Useful during batch operations or testing.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
suppress: True to suppress dialogs, False to enable
|
|
323
|
+
"""
|
|
324
|
+
self._suppress_dialogs = suppress
|
|
325
|
+
|
|
326
|
+
def set_dialog_manager(self, dialog_manager) -> None:
|
|
327
|
+
"""
|
|
328
|
+
Set or update the dialog manager.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
dialog_manager: DialogManager instance
|
|
332
|
+
"""
|
|
333
|
+
self.dialog_manager = dialog_manager
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def error_handler(operation: str,
|
|
337
|
+
component: str = "",
|
|
338
|
+
show_dialog: bool = True,
|
|
339
|
+
fallback: Any = None,
|
|
340
|
+
severity: ErrorSeverity = ErrorSeverity.ERROR):
|
|
341
|
+
"""
|
|
342
|
+
Decorator for consistent error handling on methods.
|
|
343
|
+
|
|
344
|
+
The decorated method's class must have an 'error_service' attribute.
|
|
345
|
+
|
|
346
|
+
Usage:
|
|
347
|
+
class MyClass:
|
|
348
|
+
def __init__(self):
|
|
349
|
+
self.error_service = get_error_service()
|
|
350
|
+
|
|
351
|
+
@error_handler("Loading settings", component="Settings")
|
|
352
|
+
def load_settings(self):
|
|
353
|
+
# risky code here
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
@error_handler("Parsing data", fallback=[])
|
|
357
|
+
def parse_data(self):
|
|
358
|
+
# returns [] on error
|
|
359
|
+
pass
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
operation: Description of the operation
|
|
363
|
+
component: Component/module name
|
|
364
|
+
show_dialog: Whether to show error dialog
|
|
365
|
+
fallback: Value to return on error (None = reraise)
|
|
366
|
+
severity: Error severity level
|
|
367
|
+
"""
|
|
368
|
+
def decorator(func: Callable) -> Callable:
|
|
369
|
+
@wraps(func)
|
|
370
|
+
def wrapper(self, *args, **kwargs):
|
|
371
|
+
try:
|
|
372
|
+
return func(self, *args, **kwargs)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
context = ErrorContext(
|
|
375
|
+
operation=operation,
|
|
376
|
+
component=component,
|
|
377
|
+
user_message=str(e)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if hasattr(self, 'error_service') and self.error_service:
|
|
381
|
+
if fallback is not None:
|
|
382
|
+
return self.error_service.handle_with_fallback(
|
|
383
|
+
e, context, fallback, severity
|
|
384
|
+
)
|
|
385
|
+
else:
|
|
386
|
+
self.error_service.handle(
|
|
387
|
+
e, context, severity,
|
|
388
|
+
show_dialog=show_dialog,
|
|
389
|
+
reraise=True
|
|
390
|
+
)
|
|
391
|
+
else:
|
|
392
|
+
# Fallback if error service not available
|
|
393
|
+
if hasattr(self, 'logger') and self.logger:
|
|
394
|
+
self.logger.error(f"{operation}: {e}")
|
|
395
|
+
if fallback is not None:
|
|
396
|
+
return fallback
|
|
397
|
+
raise
|
|
398
|
+
return wrapper
|
|
399
|
+
return decorator
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# Global instance
|
|
403
|
+
_error_service: Optional[ErrorService] = None
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_error_service() -> Optional[ErrorService]:
|
|
407
|
+
"""Get the global error service instance."""
|
|
408
|
+
return _error_service
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def init_error_service(logger: logging.Logger, dialog_manager=None) -> ErrorService:
|
|
412
|
+
"""
|
|
413
|
+
Initialize and return the global error service.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
logger: Application logger
|
|
417
|
+
dialog_manager: Optional DialogManager for user notifications
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Initialized ErrorService instance
|
|
421
|
+
"""
|
|
422
|
+
global _error_service
|
|
423
|
+
_error_service = ErrorService(logger, dialog_manager)
|
|
424
|
+
return _error_service
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def shutdown_error_service() -> None:
|
|
428
|
+
"""Shutdown the global error service."""
|
|
429
|
+
global _error_service
|
|
430
|
+
_error_service = None
|
|
431
|
+
|