Initial commit
Dieser Commit ist enthalten in:
292
utils/result_decorators.py
Normale Datei
292
utils/result_decorators.py
Normale Datei
@ -0,0 +1,292 @@
|
||||
"""
|
||||
Result Enhancement Decorators - Backward-compatible result standardization
|
||||
Erweitert bestehende Methoden ohne sie zu ändern
|
||||
"""
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
from typing import Any, Callable, Union
|
||||
from domain.value_objects.operation_result import OperationResult, CommonErrorCodes
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def result_enhanced(preserve_original: bool = True,
|
||||
error_code_mapping: dict = None,
|
||||
capture_metadata: bool = True):
|
||||
"""
|
||||
Decorator der bestehende Methoden erweitert ohne sie zu ändern.
|
||||
|
||||
Args:
|
||||
preserve_original: Ob die Original-Methode verfügbar bleiben soll
|
||||
error_code_mapping: Mapping von Exception-Types zu Error-Codes
|
||||
capture_metadata: Ob Metadaten (Timing, Thread-Info) erfasst werden sollen
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs) -> OperationResult:
|
||||
start_time = time.time() if capture_metadata else None
|
||||
metadata = {}
|
||||
|
||||
if capture_metadata:
|
||||
metadata.update({
|
||||
'function_name': func.__name__,
|
||||
'thread_id': threading.current_thread().ident,
|
||||
'thread_name': threading.current_thread().name
|
||||
})
|
||||
|
||||
try:
|
||||
# Original-Methode aufrufen
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
if capture_metadata:
|
||||
metadata['execution_time'] = time.time() - start_time
|
||||
|
||||
# Boolean results zu OperationResult erweitern
|
||||
if isinstance(result, bool):
|
||||
return OperationResult.from_legacy_boolean(
|
||||
result=result,
|
||||
success_data=result if result else None,
|
||||
error_message="Operation returned False" if not result else None
|
||||
)
|
||||
|
||||
# Dict results erweitern
|
||||
elif isinstance(result, dict) and 'success' in result:
|
||||
op_result = OperationResult.from_legacy_dict(result)
|
||||
if capture_metadata:
|
||||
op_result.metadata.update(metadata)
|
||||
return op_result
|
||||
|
||||
# None als Fehler behandeln
|
||||
elif result is None:
|
||||
return OperationResult.error_result(
|
||||
message="Method returned None",
|
||||
code=CommonErrorCodes.BROWSER_ERROR,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
# Alle anderen Results als Erfolg
|
||||
else:
|
||||
return OperationResult.success_result(
|
||||
data=result,
|
||||
metadata=metadata,
|
||||
legacy_result=result
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
if capture_metadata:
|
||||
metadata.update({
|
||||
'execution_time': time.time() - start_time,
|
||||
'exception_occurred_at': time.time()
|
||||
})
|
||||
|
||||
# Error code mapping anwenden
|
||||
error_code = None
|
||||
if error_code_mapping:
|
||||
error_code = error_code_mapping.get(type(e), type(e).__name__)
|
||||
|
||||
return OperationResult.from_exception(
|
||||
exception=e,
|
||||
code=error_code,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
# Original-Methode verfügbar machen wenn gewünscht
|
||||
if preserve_original:
|
||||
wrapper.original = func
|
||||
wrapper.get_original = lambda: func
|
||||
|
||||
# Zusätzliche Utility-Methoden
|
||||
wrapper.call_original = lambda *args, **kwargs: func(*args, **kwargs)
|
||||
wrapper.is_enhanced = True
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def instagram_result_enhanced(func: Callable) -> Callable:
|
||||
"""
|
||||
Spezialisierter Decorator für Instagram-Operationen
|
||||
"""
|
||||
instagram_error_mapping = {
|
||||
TimeoutError: CommonErrorCodes.NETWORK_TIMEOUT,
|
||||
ConnectionError: CommonErrorCodes.PROXY_ERROR,
|
||||
ValueError: CommonErrorCodes.SELECTOR_NOT_FOUND,
|
||||
RuntimeError: CommonErrorCodes.BROWSER_ERROR,
|
||||
Exception: CommonErrorCodes.BROWSER_ERROR
|
||||
}
|
||||
|
||||
return result_enhanced(
|
||||
preserve_original=True,
|
||||
error_code_mapping=instagram_error_mapping,
|
||||
capture_metadata=True
|
||||
)(func)
|
||||
|
||||
|
||||
def fingerprint_result_enhanced(func: Callable) -> Callable:
|
||||
"""
|
||||
Spezialisierter Decorator für Fingerprint-Operationen
|
||||
"""
|
||||
fingerprint_error_mapping = {
|
||||
FileNotFoundError: CommonErrorCodes.FINGERPRINT_NOT_FOUND,
|
||||
PermissionError: CommonErrorCodes.FINGERPRINT_GENERATION_FAILED,
|
||||
ValueError: CommonErrorCodes.FINGERPRINT_GENERATION_FAILED,
|
||||
RuntimeError: CommonErrorCodes.FINGERPRINT_RACE_CONDITION,
|
||||
Exception: CommonErrorCodes.FINGERPRINT_GENERATION_FAILED
|
||||
}
|
||||
|
||||
return result_enhanced(
|
||||
preserve_original=True,
|
||||
error_code_mapping=fingerprint_error_mapping,
|
||||
capture_metadata=True
|
||||
)(func)
|
||||
|
||||
|
||||
def session_result_enhanced(func: Callable) -> Callable:
|
||||
"""
|
||||
Spezialisierter Decorator für Session-Operationen
|
||||
"""
|
||||
session_error_mapping = {
|
||||
TimeoutError: CommonErrorCodes.SESSION_EXPIRED,
|
||||
ValueError: CommonErrorCodes.SESSION_INVALID,
|
||||
IOError: CommonErrorCodes.SESSION_SAVE_FAILED,
|
||||
Exception: CommonErrorCodes.SESSION_INVALID
|
||||
}
|
||||
|
||||
return result_enhanced(
|
||||
preserve_original=True,
|
||||
error_code_mapping=session_error_mapping,
|
||||
capture_metadata=True
|
||||
)(func)
|
||||
|
||||
|
||||
class ResultEnhancer:
|
||||
"""
|
||||
Utility-Klasse für programmatische Result-Enhancement ohne Decorators
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def enhance_method(obj: Any, method_name: str, enhancement_type: str = "general") -> None:
|
||||
"""
|
||||
Erweitert eine bestehende Methode eines Objekts zur Laufzeit
|
||||
|
||||
Args:
|
||||
obj: Das Objekt dessen Methode erweitert werden soll
|
||||
method_name: Name der zu erweiternden Methode
|
||||
enhancement_type: Art der Erweiterung ("general", "instagram", "fingerprint", "session")
|
||||
"""
|
||||
if not hasattr(obj, method_name):
|
||||
logger.warning(f"Method {method_name} not found on object {obj}")
|
||||
return
|
||||
|
||||
original_method = getattr(obj, method_name)
|
||||
|
||||
# Bereits erweiterte Methoden überspringen
|
||||
if getattr(original_method, 'is_enhanced', False):
|
||||
logger.debug(f"Method {method_name} already enhanced")
|
||||
return
|
||||
|
||||
# Entsprechenden Decorator wählen
|
||||
if enhancement_type == "instagram":
|
||||
enhanced_method = instagram_result_enhanced(original_method)
|
||||
elif enhancement_type == "fingerprint":
|
||||
enhanced_method = fingerprint_result_enhanced(original_method)
|
||||
elif enhancement_type == "session":
|
||||
enhanced_method = session_result_enhanced(original_method)
|
||||
else:
|
||||
enhanced_method = result_enhanced()(original_method)
|
||||
|
||||
# Methode ersetzen
|
||||
setattr(obj, method_name, enhanced_method)
|
||||
|
||||
# Original unter anderem Namen verfügbar machen
|
||||
setattr(obj, f"{method_name}_original", original_method)
|
||||
|
||||
logger.info(f"Enhanced method {method_name} on {type(obj).__name__}")
|
||||
|
||||
@staticmethod
|
||||
def enhance_class_methods(cls: type, method_names: list, enhancement_type: str = "general") -> None:
|
||||
"""
|
||||
Erweitert mehrere Methoden einer Klasse
|
||||
"""
|
||||
for method_name in method_names:
|
||||
if hasattr(cls, method_name):
|
||||
original_method = getattr(cls, method_name)
|
||||
|
||||
if enhancement_type == "instagram":
|
||||
enhanced_method = instagram_result_enhanced(original_method)
|
||||
elif enhancement_type == "fingerprint":
|
||||
enhanced_method = fingerprint_result_enhanced(original_method)
|
||||
elif enhancement_type == "session":
|
||||
enhanced_method = session_result_enhanced(original_method)
|
||||
else:
|
||||
enhanced_method = result_enhanced()(original_method)
|
||||
|
||||
setattr(cls, method_name, enhanced_method)
|
||||
setattr(cls, f"{method_name}_original", original_method)
|
||||
|
||||
logger.info(f"Enhanced class method {cls.__name__}.{method_name}")
|
||||
|
||||
|
||||
class BatchResultWrapper:
|
||||
"""
|
||||
Wrapper für Batch-Operationen mit einheitlicher Result-Struktur
|
||||
"""
|
||||
|
||||
def __init__(self, operation_name: str = "batch_operation"):
|
||||
self.operation_name = operation_name
|
||||
self.results = []
|
||||
self.success_count = 0
|
||||
self.error_count = 0
|
||||
|
||||
def add_result(self, result: Union[OperationResult, bool, dict, Any]) -> None:
|
||||
"""Fügt ein Result zur Batch hinzu"""
|
||||
if isinstance(result, OperationResult):
|
||||
op_result = result
|
||||
elif isinstance(result, bool):
|
||||
op_result = OperationResult.from_legacy_boolean(result)
|
||||
elif isinstance(result, dict) and 'success' in result:
|
||||
op_result = OperationResult.from_legacy_dict(result)
|
||||
else:
|
||||
op_result = OperationResult.success_result(data=result)
|
||||
|
||||
self.results.append(op_result)
|
||||
|
||||
if op_result.success:
|
||||
self.success_count += 1
|
||||
else:
|
||||
self.error_count += 1
|
||||
|
||||
def get_batch_result(self) -> OperationResult:
|
||||
"""Gibt das Gesamtergebnis der Batch zurück"""
|
||||
total_count = len(self.results)
|
||||
success_rate = self.success_count / total_count if total_count > 0 else 0
|
||||
|
||||
metadata = {
|
||||
'total_operations': total_count,
|
||||
'successful_operations': self.success_count,
|
||||
'failed_operations': self.error_count,
|
||||
'success_rate': success_rate,
|
||||
'operation_name': self.operation_name
|
||||
}
|
||||
|
||||
# Batch als erfolgreich bewerten wenn > 50% erfolgreich
|
||||
batch_success = success_rate > 0.5
|
||||
|
||||
if batch_success:
|
||||
return OperationResult.success_result(
|
||||
data={
|
||||
'results': [r.to_dict() for r in self.results],
|
||||
'summary': metadata
|
||||
},
|
||||
metadata=metadata
|
||||
)
|
||||
else:
|
||||
error_messages = [r.error_message for r in self.results if not r.success]
|
||||
return OperationResult.error_result(
|
||||
message=f"Batch operation failed: {'; '.join(error_messages[:3])}...",
|
||||
code="BATCH_OPERATION_FAILED",
|
||||
metadata=metadata
|
||||
)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren