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