Files
AccountForger-neuerUpload/utils/result_decorators.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

292 Zeilen
11 KiB
Python

"""
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
)