spatelier 0.3.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.
- analytics/__init__.py +1 -0
- analytics/reporter.py +497 -0
- cli/__init__.py +1 -0
- cli/app.py +147 -0
- cli/audio.py +129 -0
- cli/cli_analytics.py +320 -0
- cli/cli_utils.py +282 -0
- cli/error_handlers.py +122 -0
- cli/files.py +299 -0
- cli/update.py +325 -0
- cli/video.py +823 -0
- cli/worker.py +615 -0
- core/__init__.py +1 -0
- core/analytics_dashboard.py +368 -0
- core/base.py +303 -0
- core/base_service.py +69 -0
- core/config.py +345 -0
- core/database_service.py +116 -0
- core/decorators.py +263 -0
- core/error_handler.py +210 -0
- core/file_tracker.py +254 -0
- core/interactive_cli.py +366 -0
- core/interfaces.py +166 -0
- core/job_queue.py +437 -0
- core/logger.py +79 -0
- core/package_updater.py +469 -0
- core/progress.py +228 -0
- core/service_factory.py +295 -0
- core/streaming.py +299 -0
- core/worker.py +765 -0
- database/__init__.py +1 -0
- database/connection.py +265 -0
- database/metadata.py +516 -0
- database/models.py +288 -0
- database/repository.py +592 -0
- database/transcription_storage.py +219 -0
- modules/__init__.py +1 -0
- modules/audio/__init__.py +5 -0
- modules/audio/converter.py +197 -0
- modules/video/__init__.py +16 -0
- modules/video/converter.py +191 -0
- modules/video/fallback_extractor.py +334 -0
- modules/video/services/__init__.py +18 -0
- modules/video/services/audio_extraction_service.py +274 -0
- modules/video/services/download_service.py +852 -0
- modules/video/services/metadata_service.py +190 -0
- modules/video/services/playlist_service.py +445 -0
- modules/video/services/transcription_service.py +491 -0
- modules/video/transcription_service.py +385 -0
- modules/video/youtube_api.py +397 -0
- spatelier/__init__.py +33 -0
- spatelier-0.3.0.dist-info/METADATA +260 -0
- spatelier-0.3.0.dist-info/RECORD +59 -0
- spatelier-0.3.0.dist-info/WHEEL +5 -0
- spatelier-0.3.0.dist-info/entry_points.txt +2 -0
- spatelier-0.3.0.dist-info/licenses/LICENSE +21 -0
- spatelier-0.3.0.dist-info/top_level.txt +7 -0
- utils/__init__.py +1 -0
- utils/helpers.py +250 -0
core/decorators.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorators for Spatelier modules.
|
|
3
|
+
|
|
4
|
+
This module provides decorators for common patterns like
|
|
5
|
+
error handling, timing, and validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import functools
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Callable, Optional, Union
|
|
12
|
+
|
|
13
|
+
from core.base import ProcessingResult
|
|
14
|
+
from core.error_handler import get_error_handler
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_duration(seconds: float) -> str:
|
|
18
|
+
"""Format duration in seconds to human-readable format."""
|
|
19
|
+
if seconds < 60:
|
|
20
|
+
return f"{seconds:.1f} seconds"
|
|
21
|
+
elif seconds < 3600:
|
|
22
|
+
minutes = int(seconds // 60)
|
|
23
|
+
remaining_seconds = seconds % 60
|
|
24
|
+
return f"{minutes}m {remaining_seconds:.1f}s"
|
|
25
|
+
else:
|
|
26
|
+
hours = int(seconds // 3600)
|
|
27
|
+
remaining_minutes = int((seconds % 3600) // 60)
|
|
28
|
+
remaining_seconds = seconds % 60
|
|
29
|
+
return f"{hours}h {remaining_minutes}m {remaining_seconds:.1f}s"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def handle_errors(context: str = "", return_result: bool = True, verbose: bool = False):
|
|
33
|
+
"""
|
|
34
|
+
Decorator for automatic error handling.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
context: Context for error reporting
|
|
38
|
+
return_result: Whether to return ProcessingResult on error
|
|
39
|
+
verbose: Enable verbose logging
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def decorator(func: Callable) -> Callable:
|
|
43
|
+
@functools.wraps(func)
|
|
44
|
+
def wrapper(*args, **kwargs) -> Union[Any, ProcessingResult]:
|
|
45
|
+
try:
|
|
46
|
+
return func(*args, **kwargs)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
handler = get_error_handler(verbose=verbose)
|
|
49
|
+
error_result = handler.handle_error(
|
|
50
|
+
e, context, return_result=return_result
|
|
51
|
+
)
|
|
52
|
+
if return_result:
|
|
53
|
+
return error_result
|
|
54
|
+
raise
|
|
55
|
+
|
|
56
|
+
return wrapper
|
|
57
|
+
|
|
58
|
+
return decorator
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def time_operation(verbose: bool = False):
|
|
62
|
+
"""
|
|
63
|
+
Decorator for timing operations.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
verbose: Enable verbose logging
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def decorator(func: Callable) -> Callable:
|
|
70
|
+
@functools.wraps(func)
|
|
71
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
72
|
+
start_time = time.time()
|
|
73
|
+
try:
|
|
74
|
+
result = func(*args, **kwargs)
|
|
75
|
+
duration = time.time() - start_time
|
|
76
|
+
|
|
77
|
+
if verbose:
|
|
78
|
+
print(f"{func.__name__} completed in {format_duration(duration)}")
|
|
79
|
+
|
|
80
|
+
# Add timing to result if it's a ProcessingResult
|
|
81
|
+
if isinstance(result, ProcessingResult):
|
|
82
|
+
result.duration_seconds = duration
|
|
83
|
+
|
|
84
|
+
return result
|
|
85
|
+
except Exception as e:
|
|
86
|
+
duration = time.time() - start_time
|
|
87
|
+
if verbose:
|
|
88
|
+
print(
|
|
89
|
+
f"{func.__name__} failed after {format_duration(duration)}: {e}"
|
|
90
|
+
)
|
|
91
|
+
raise
|
|
92
|
+
|
|
93
|
+
return wrapper
|
|
94
|
+
|
|
95
|
+
return decorator
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def validate_input(
|
|
99
|
+
input_validator: Optional[Callable] = None,
|
|
100
|
+
output_validator: Optional[Callable] = None,
|
|
101
|
+
):
|
|
102
|
+
"""
|
|
103
|
+
Decorator for input/output validation.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
input_validator: Function to validate inputs
|
|
107
|
+
output_validator: Function to validate outputs
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def decorator(func: Callable) -> Callable:
|
|
111
|
+
@functools.wraps(func)
|
|
112
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
113
|
+
# Validate inputs
|
|
114
|
+
if input_validator:
|
|
115
|
+
try:
|
|
116
|
+
input_validator(*args, **kwargs)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise ValueError(f"Input validation failed: {e}")
|
|
119
|
+
|
|
120
|
+
result = func(*args, **kwargs)
|
|
121
|
+
|
|
122
|
+
# Validate outputs
|
|
123
|
+
if output_validator:
|
|
124
|
+
try:
|
|
125
|
+
output_validator(result)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise ValueError(f"Output validation failed: {e}")
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
return wrapper
|
|
132
|
+
|
|
133
|
+
return decorator
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def retry_on_failure(
|
|
137
|
+
max_retries: int = 3,
|
|
138
|
+
delay: float = 1.0,
|
|
139
|
+
backoff_factor: float = 2.0,
|
|
140
|
+
exceptions: tuple = (Exception,),
|
|
141
|
+
):
|
|
142
|
+
"""
|
|
143
|
+
Decorator for retrying operations on failure.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
max_retries: Maximum number of retries
|
|
147
|
+
delay: Initial delay between retries
|
|
148
|
+
backoff_factor: Factor to multiply delay by after each retry
|
|
149
|
+
exceptions: Tuple of exceptions to retry on
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def decorator(func: Callable) -> Callable:
|
|
153
|
+
@functools.wraps(func)
|
|
154
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
155
|
+
current_delay = delay
|
|
156
|
+
last_exception = None
|
|
157
|
+
|
|
158
|
+
for attempt in range(max_retries + 1):
|
|
159
|
+
try:
|
|
160
|
+
return func(*args, **kwargs)
|
|
161
|
+
except exceptions as e:
|
|
162
|
+
last_exception = e
|
|
163
|
+
if attempt < max_retries:
|
|
164
|
+
time.sleep(current_delay)
|
|
165
|
+
current_delay *= backoff_factor
|
|
166
|
+
else:
|
|
167
|
+
raise last_exception
|
|
168
|
+
|
|
169
|
+
return None # Should never reach here
|
|
170
|
+
|
|
171
|
+
return wrapper
|
|
172
|
+
|
|
173
|
+
return decorator
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def log_operation(
|
|
177
|
+
level: str = "INFO", include_args: bool = False, include_result: bool = False
|
|
178
|
+
):
|
|
179
|
+
"""
|
|
180
|
+
Decorator for logging operations.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
level: Log level
|
|
184
|
+
include_args: Whether to include function arguments in log
|
|
185
|
+
include_result: Whether to include result in log
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def decorator(func: Callable) -> Callable:
|
|
189
|
+
@functools.wraps(func)
|
|
190
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
191
|
+
from core.logger import get_logger
|
|
192
|
+
|
|
193
|
+
logger = get_logger(func.__module__)
|
|
194
|
+
|
|
195
|
+
# Log function start
|
|
196
|
+
log_msg = f"Starting {func.__name__}"
|
|
197
|
+
if include_args:
|
|
198
|
+
log_msg += f" with args={args}, kwargs={kwargs}"
|
|
199
|
+
|
|
200
|
+
if level.upper() == "DEBUG":
|
|
201
|
+
logger.debug(log_msg)
|
|
202
|
+
elif level.upper() == "INFO":
|
|
203
|
+
logger.info(log_msg)
|
|
204
|
+
elif level.upper() == "WARNING":
|
|
205
|
+
logger.warning(log_msg)
|
|
206
|
+
elif level.upper() == "ERROR":
|
|
207
|
+
logger.error(log_msg)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
result = func(*args, **kwargs)
|
|
211
|
+
|
|
212
|
+
# Log function completion
|
|
213
|
+
log_msg = f"Completed {func.__name__}"
|
|
214
|
+
if include_result:
|
|
215
|
+
log_msg += f" with result={result}"
|
|
216
|
+
|
|
217
|
+
if level.upper() == "DEBUG":
|
|
218
|
+
logger.debug(log_msg)
|
|
219
|
+
elif level.upper() == "INFO":
|
|
220
|
+
logger.info(log_msg)
|
|
221
|
+
elif level.upper() == "WARNING":
|
|
222
|
+
logger.warning(log_msg)
|
|
223
|
+
elif level.upper() == "ERROR":
|
|
224
|
+
logger.error(log_msg)
|
|
225
|
+
|
|
226
|
+
return result
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(f"Failed {func.__name__}: {e}")
|
|
229
|
+
raise
|
|
230
|
+
|
|
231
|
+
return wrapper
|
|
232
|
+
|
|
233
|
+
return decorator
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def ensure_path_exists(path_arg: str = "path"):
|
|
237
|
+
"""
|
|
238
|
+
Decorator to ensure a path argument exists.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
path_arg: Name of the path argument to validate
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def decorator(func: Callable) -> Callable:
|
|
245
|
+
@functools.wraps(func)
|
|
246
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
247
|
+
# Get the path argument
|
|
248
|
+
if path_arg in kwargs:
|
|
249
|
+
path = kwargs[path_arg]
|
|
250
|
+
else:
|
|
251
|
+
# Try to get from positional args (this is a simplified approach)
|
|
252
|
+
path = args[0] if args else None
|
|
253
|
+
|
|
254
|
+
if path:
|
|
255
|
+
path_obj = Path(path)
|
|
256
|
+
if not path_obj.exists():
|
|
257
|
+
raise FileNotFoundError(f"Path does not exist: {path}")
|
|
258
|
+
|
|
259
|
+
return func(*args, **kwargs)
|
|
260
|
+
|
|
261
|
+
return wrapper
|
|
262
|
+
|
|
263
|
+
return decorator
|
core/error_handler.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralized error handling for Spatelier.
|
|
3
|
+
|
|
4
|
+
This module provides consistent error handling patterns
|
|
5
|
+
across all services and modules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import traceback
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
|
11
|
+
|
|
12
|
+
from core.base import ProcessingResult
|
|
13
|
+
from core.logger import get_logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ErrorHandler:
|
|
17
|
+
"""Centralized error handler for consistent error management."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, logger_name: str = "ErrorHandler", verbose: bool = False):
|
|
20
|
+
"""
|
|
21
|
+
Initialize error handler.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
logger_name: Name for logger
|
|
25
|
+
verbose: Enable verbose logging
|
|
26
|
+
"""
|
|
27
|
+
self.verbose = verbose
|
|
28
|
+
self.logger = get_logger(logger_name, verbose=verbose)
|
|
29
|
+
self.error_handlers: Dict[Type[Exception], Callable] = {}
|
|
30
|
+
self._register_default_handlers()
|
|
31
|
+
|
|
32
|
+
def _register_default_handlers(self):
|
|
33
|
+
"""Register default error handlers."""
|
|
34
|
+
self.error_handlers[FileNotFoundError] = self._handle_file_not_found
|
|
35
|
+
self.error_handlers[PermissionError] = self._handle_permission_error
|
|
36
|
+
self.error_handlers[OSError] = self._handle_os_error
|
|
37
|
+
self.error_handlers[ValueError] = self._handle_value_error
|
|
38
|
+
self.error_handlers[KeyError] = self._handle_key_error
|
|
39
|
+
self.error_handlers[ImportError] = self._handle_import_error
|
|
40
|
+
|
|
41
|
+
def handle_error(
|
|
42
|
+
self, error: Exception, context: str = "", return_result: bool = True, **kwargs
|
|
43
|
+
) -> Optional[ProcessingResult]:
|
|
44
|
+
"""
|
|
45
|
+
Handle an error with appropriate response.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
error: Exception to handle
|
|
49
|
+
context: Context where error occurred
|
|
50
|
+
return_result: Whether to return ProcessingResult
|
|
51
|
+
**kwargs: Additional context for error handling
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
ProcessingResult if return_result=True, None otherwise
|
|
55
|
+
"""
|
|
56
|
+
error_type = type(error)
|
|
57
|
+
|
|
58
|
+
# Log the error
|
|
59
|
+
self.logger.error(f"Error in {context}: {error}")
|
|
60
|
+
if self.verbose:
|
|
61
|
+
self.logger.debug(f"Error traceback: {traceback.format_exc()}")
|
|
62
|
+
|
|
63
|
+
# Get specific handler or use generic handler
|
|
64
|
+
handler = self.error_handlers.get(error_type, self._handle_generic_error)
|
|
65
|
+
result = handler(error, context, **kwargs)
|
|
66
|
+
|
|
67
|
+
if return_result:
|
|
68
|
+
return result
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def _handle_file_not_found(
|
|
72
|
+
self, error: FileNotFoundError, context: str, **kwargs
|
|
73
|
+
) -> ProcessingResult:
|
|
74
|
+
"""Handle FileNotFoundError."""
|
|
75
|
+
file_path = getattr(error, "filename", "unknown file")
|
|
76
|
+
return ProcessingResult.error_result(
|
|
77
|
+
message=f"File not found: {file_path}",
|
|
78
|
+
errors=[f"FileNotFoundError in {context}: {str(error)}"],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _handle_permission_error(
|
|
82
|
+
self, error: PermissionError, context: str, **kwargs
|
|
83
|
+
) -> ProcessingResult:
|
|
84
|
+
"""Handle PermissionError."""
|
|
85
|
+
return ProcessingResult.error_result(
|
|
86
|
+
message="Permission denied",
|
|
87
|
+
errors=[f"PermissionError in {context}: {str(error)}"],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _handle_os_error(
|
|
91
|
+
self, error: OSError, context: str, **kwargs
|
|
92
|
+
) -> ProcessingResult:
|
|
93
|
+
"""Handle OSError."""
|
|
94
|
+
return ProcessingResult.error_result(
|
|
95
|
+
message=f"System error: {error.strerror}",
|
|
96
|
+
errors=[f"OSError in {context}: {str(error)}"],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _handle_value_error(
|
|
100
|
+
self, error: ValueError, context: str, **kwargs
|
|
101
|
+
) -> ProcessingResult:
|
|
102
|
+
"""Handle ValueError."""
|
|
103
|
+
return ProcessingResult.error_result(
|
|
104
|
+
message=f"Invalid value: {str(error)}",
|
|
105
|
+
errors=[f"ValueError in {context}: {str(error)}"],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _handle_key_error(
|
|
109
|
+
self, error: KeyError, context: str, **kwargs
|
|
110
|
+
) -> ProcessingResult:
|
|
111
|
+
"""Handle KeyError."""
|
|
112
|
+
key = str(error).strip("'\"")
|
|
113
|
+
return ProcessingResult.error_result(
|
|
114
|
+
message=f"Missing key: {key}",
|
|
115
|
+
errors=[f"KeyError in {context}: Missing key '{key}'"],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def _handle_import_error(
|
|
119
|
+
self, error: ImportError, context: str, **kwargs
|
|
120
|
+
) -> ProcessingResult:
|
|
121
|
+
"""Handle ImportError."""
|
|
122
|
+
module = getattr(error, "name", "unknown module")
|
|
123
|
+
return ProcessingResult.error_result(
|
|
124
|
+
message=f"Import error: {module}",
|
|
125
|
+
errors=[f"ImportError in {context}: Cannot import {module}"],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def _handle_generic_error(
|
|
129
|
+
self, error: Exception, context: str, **kwargs
|
|
130
|
+
) -> ProcessingResult:
|
|
131
|
+
"""Handle generic errors."""
|
|
132
|
+
return ProcessingResult.error_result(
|
|
133
|
+
message=f"Unexpected error: {type(error).__name__}",
|
|
134
|
+
errors=[f"{type(error).__name__} in {context}: {str(error)}"],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def register_handler(self, exception_type: Type[Exception], handler: Callable):
|
|
138
|
+
"""Register a custom error handler."""
|
|
139
|
+
self.error_handlers[exception_type] = handler
|
|
140
|
+
|
|
141
|
+
def safe_execute(
|
|
142
|
+
self,
|
|
143
|
+
func: Callable,
|
|
144
|
+
context: str = "",
|
|
145
|
+
default_result: Optional[ProcessingResult] = None,
|
|
146
|
+
*args,
|
|
147
|
+
**kwargs,
|
|
148
|
+
) -> ProcessingResult:
|
|
149
|
+
"""
|
|
150
|
+
Safely execute a function with error handling.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
func: Function to execute
|
|
154
|
+
context: Context for error reporting
|
|
155
|
+
default_result: Default result if function fails
|
|
156
|
+
*args: Function arguments
|
|
157
|
+
**kwargs: Function keyword arguments
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
ProcessingResult from function or error result
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
result = func(*args, **kwargs)
|
|
164
|
+
if isinstance(result, ProcessingResult):
|
|
165
|
+
return result
|
|
166
|
+
else:
|
|
167
|
+
return ProcessingResult.success_result(
|
|
168
|
+
message="Operation completed successfully",
|
|
169
|
+
metadata={"result": result},
|
|
170
|
+
)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
error_result = self.handle_error(e, context, return_result=True)
|
|
173
|
+
if default_result:
|
|
174
|
+
return default_result
|
|
175
|
+
return error_result or ProcessingResult.error_result(
|
|
176
|
+
message="Operation failed", errors=[f"Unexpected error in {context}"]
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# Global error handler instance
|
|
181
|
+
_error_handler: Optional[ErrorHandler] = None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def get_error_handler(verbose: bool = False) -> ErrorHandler:
|
|
185
|
+
"""Get global error handler instance."""
|
|
186
|
+
global _error_handler
|
|
187
|
+
if _error_handler is None:
|
|
188
|
+
_error_handler = ErrorHandler(verbose=verbose)
|
|
189
|
+
return _error_handler
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def handle_error(
|
|
193
|
+
error: Exception, context: str = "", verbose: bool = False, **kwargs
|
|
194
|
+
) -> ProcessingResult:
|
|
195
|
+
"""Convenience function for handling errors."""
|
|
196
|
+
handler = get_error_handler(verbose=verbose)
|
|
197
|
+
return handler.handle_error(error, context, **kwargs)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def safe_execute(
|
|
201
|
+
func: Callable,
|
|
202
|
+
context: str = "",
|
|
203
|
+
verbose: bool = False,
|
|
204
|
+
default_result: Optional[ProcessingResult] = None,
|
|
205
|
+
*args,
|
|
206
|
+
**kwargs,
|
|
207
|
+
) -> ProcessingResult:
|
|
208
|
+
"""Convenience function for safe execution."""
|
|
209
|
+
handler = get_error_handler(verbose=verbose)
|
|
210
|
+
return handler.safe_execute(func, context, default_result, *args, **kwargs)
|