diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 9f57bbaa..b9161f5c 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -200,6 +200,9 @@ ALLOWED_TARGET_BRANCHES = [] # GLOBALE HELPER FUNCTIONS (PART 1: Retry Decorator) # ============================================================================== +# Imports für den Retry Decorator (um NameErrors zu vermeiden) +from openai.error import AuthenticationError, OpenAIError, RateLimitError, APIError, Timeout, InvalidRequestError, ServiceUnavailableError # Beispielhafte spezifische Fehler + # Logger fuer den Retry Decorator selbst (Nutzt den globalen Root Logger) decorator_logger = logging.getLogger(__name__ + ".Retry") @@ -229,7 +232,7 @@ def retry_on_failure(func): # Fehler loggen und weitergeben, wenn keine Retries konfiguriert sind decorator_logger.error(f"FEHLER bei '{effective_func_name}' (keine Retries konfiguriert). {type(e).__name__} - {str(e)[:150]}...") # Log traceback fuer unerwartete Fehler (nicht die spezifischen API/Netzwerkfehler) - if not isinstance(e, (requests.exceptions.RequestException, gspread.exceptions.APIError, openai.error.OpenAIError, wikipedia.exceptions.WikipediaException)): + if not isinstance(e, (requests.exceptions.RequestException, gspread.exceptions.APIError, OpenAIError, wikipedia.exceptions.WikipediaException)): # <<< GEÄNDERT (openai.error.OpenAIError -> OpenAIError) decorator_logger.exception("Details zum Fehler:") raise e # Re-raise the exception @@ -245,7 +248,7 @@ def retry_on_failure(func): return func(*args, **kwargs) # Call the original function # Spezifische Exceptions, die ein Retry nicht rechtfertigen (permanente Fehler) - except (gspread.exceptions.SpreadsheetNotFound, openai.error.AuthenticationError, ValueError) as e: + except (gspread.exceptions.SpreadsheetNotFound, AuthenticationError, ValueError) as e: # <<< GEÄNDERT (openai.error.AuthenticationError -> AuthenticationError) # Diese Fehler deuten auf ein permanentes Problem hin (falsche URL, falscher Key, falsche Eingabe) decorator_logger.critical(f"❌ ENDGUELTIGER FEHLER bei '{effective_func_name}': Permanentes Problem erkannt. {type(e).__name__} - {str(e)[:150]}...") decorator_logger.exception("Details:") # Log traceback fuer permanente Fehler @@ -269,20 +272,24 @@ def retry_on_failure(func): # Fangen Sie andere wiederholbare Exceptions (Netzwerk, Rate Limit, Timeout etc.) - except (requests.exceptions.RequestException, gspread.exceptions.APIError, openai.error.OpenAIError, wikipedia.exceptions.WikipediaException) as e: + except (requests.exceptions.RequestException, gspread.exceptions.APIError, OpenAIError, wikipedia.exceptions.WikipediaException) as e: # <<< GEÄNDERT (openai.error.OpenAIError -> OpenAIError) error_msg = str(e) error_type = type(e).__name__ if attempt < max_retries_config - 1: # Wenn nicht der letzte Versuch wait_time = base_delay * (2 ** attempt) + random.uniform(0, 1) # Exponentieller Backoff mit Jitter # Loggen Sie den spezifischen Fehler und die Wartezeit - if isinstance(e, gspread.exceptions.APIError) and hasattr(e, 'response') and e.response is not None and e.response.status_code == 429: + if isinstance(e, RateLimitError): # <<< GEÄNDERT (Prüfung auf spezifischen OpenAI Fehler) decorator_logger.warning(f"🚦 RATE LIMIT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") + elif isinstance(e, Timeout) and isinstance(e, OpenAIError): # <<< GEÄNDERT (Prüfung auf OpenAI Timeout) + decorator_logger.warning(f"⏰ OPENAI TIMEOUT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") + elif isinstance(e, gspread.exceptions.APIError) and hasattr(e, 'response') and e.response is not None and e.response.status_code == 429: # <<< GEÄNDERT (Gspread Rate Limit Check) + decorator_logger.warning(f"🚦 GSPREAD RATE LIMIT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") elif isinstance(e, requests.exceptions.Timeout): - decorator_logger.warning(f"⏰ TIMEOUT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") + decorator_logger.warning(f"⏰ REQUESTS TIMEOUT ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") elif isinstance(e, requests.exceptions.RequestException): # Allgemeine RequestException decorator_logger.warning(f"🌐 NETZWERKFEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") - elif isinstance(e, openai.error.OpenAIError): # Allgemeine OpenAI Fehler + elif isinstance(e, OpenAIError): # Allgemeine OpenAI Fehler decorator_logger.warning(f"🤖 OPENAI FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") elif isinstance(e, wikipedia.exceptions.WikipediaException): # Allgemeine Wikipedia Fehler decorator_logger.warning(f"📚 WIKIPEDIA FEHLER ({error_type}) bei '{effective_func_name}' (Versuch {attempt+1}/{max_retries_config}). {error_msg[:150]}... Warte {wait_time:.2f}s...") @@ -305,10 +312,8 @@ def retry_on_failure(func): # und eine Exception immer zu einer raise e Anweisung führt. raise RuntimeError(f"Retry decorator logic error: Loop completed unexpectedly for {effective_func_name}. This should not happen.") - return wrapper # Gibt die Wrapper-Funktion zurück - # ============================================================================== # Ende Retry Decorator Block # ==============================================================================