ToDo:
1. Aggiungere un aggregator per i dati recuperati dai provider. 2. Lavorare effettivamente all'issue Done: 1. creati test per i provider 2. creato market_providers_api_demo.py per mostrare i dati recuperati dalle api dei providers 3. aggiornato i provider 4. creato il provider binance sia pubblico che con chiave 5. creato error_handler.py per gestire decoratori e utilità: retry automatico, gestione timeout...
This commit is contained in:
236
src/app/markets/error_handler.py
Normal file
236
src/app/markets/error_handler.py
Normal file
@@ -0,0 +1,236 @@
|
||||
"""
|
||||
Modulo per la gestione robusta degli errori nei market providers.
|
||||
|
||||
Fornisce decoratori e utilità per:
|
||||
- Retry automatico con backoff esponenziale
|
||||
- Logging standardizzato degli errori
|
||||
- Gestione di timeout e rate limiting
|
||||
- Fallback tra provider multipli
|
||||
"""
|
||||
|
||||
import time
|
||||
import logging
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Optional, Type, Union, List
|
||||
from requests.exceptions import RequestException, Timeout, ConnectionError
|
||||
from binance.exceptions import BinanceAPIException, BinanceRequestException
|
||||
|
||||
# Configurazione logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MarketAPIError(Exception):
|
||||
"""Eccezione base per errori delle API di mercato."""
|
||||
pass
|
||||
|
||||
class RateLimitError(MarketAPIError):
|
||||
"""Eccezione per errori di rate limiting."""
|
||||
pass
|
||||
|
||||
class AuthenticationError(MarketAPIError):
|
||||
"""Eccezione per errori di autenticazione."""
|
||||
pass
|
||||
|
||||
class DataNotFoundError(MarketAPIError):
|
||||
"""Eccezione quando i dati richiesti non sono disponibili."""
|
||||
pass
|
||||
|
||||
def retry_on_failure(
|
||||
max_retries: int = 3,
|
||||
delay: float = 1.0,
|
||||
backoff_factor: float = 2.0,
|
||||
exceptions: tuple = (RequestException, BinanceAPIException, BinanceRequestException)
|
||||
) -> Callable:
|
||||
"""
|
||||
Decoratore per retry automatico con backoff esponenziale.
|
||||
|
||||
Args:
|
||||
max_retries: Numero massimo di tentativi
|
||||
delay: Delay iniziale in secondi
|
||||
backoff_factor: Fattore di moltiplicazione per il delay
|
||||
exceptions: Tuple di eccezioni da catturare per il retry
|
||||
|
||||
Returns:
|
||||
Decoratore per la funzione
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs) -> Any:
|
||||
last_exception = None
|
||||
current_delay = delay
|
||||
|
||||
for attempt in range(max_retries + 1):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except exceptions as e:
|
||||
last_exception = e
|
||||
|
||||
if attempt == max_retries:
|
||||
logger.error(
|
||||
f"Function {func.__name__} failed after {max_retries + 1} attempts. "
|
||||
f"Last error: {str(e)}"
|
||||
)
|
||||
raise MarketAPIError(f"Max retries exceeded: {str(e)}") from e
|
||||
|
||||
logger.warning(
|
||||
f"Attempt {attempt + 1}/{max_retries + 1} failed for {func.__name__}: {str(e)}. "
|
||||
f"Retrying in {current_delay:.1f}s..."
|
||||
)
|
||||
|
||||
time.sleep(current_delay)
|
||||
current_delay *= backoff_factor
|
||||
except Exception as e:
|
||||
# Per eccezioni non previste, non fare retry
|
||||
logger.error(f"Unexpected error in {func.__name__}: {str(e)}")
|
||||
raise
|
||||
|
||||
# Questo non dovrebbe mai essere raggiunto
|
||||
if last_exception:
|
||||
raise last_exception
|
||||
else:
|
||||
raise MarketAPIError("Unknown error occurred")
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def handle_api_errors(func: Callable) -> Callable:
|
||||
"""
|
||||
Decoratore per gestione standardizzata degli errori API.
|
||||
|
||||
Converte errori specifici dei provider in eccezioni standardizzate.
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs) -> Any:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except BinanceAPIException as e:
|
||||
if e.code == -1021: # Timestamp error
|
||||
raise MarketAPIError(f"Binance timestamp error: {e.message}")
|
||||
elif e.code == -1003: # Rate limit
|
||||
raise RateLimitError(f"Binance rate limit exceeded: {e.message}")
|
||||
elif e.code in [-2014, -2015]: # API key errors
|
||||
raise AuthenticationError(f"Binance authentication error: {e.message}")
|
||||
else:
|
||||
raise MarketAPIError(f"Binance API error [{e.code}]: {e.message}")
|
||||
except ConnectionError as e:
|
||||
raise MarketAPIError(f"Connection error: {str(e)}")
|
||||
except Timeout as e:
|
||||
raise MarketAPIError(f"Request timeout: {str(e)}")
|
||||
except RequestException as e:
|
||||
raise MarketAPIError(f"Request error: {str(e)}")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error in {func.__name__}: {str(e)}")
|
||||
raise MarketAPIError(f"Unexpected error: {str(e)}") from e
|
||||
|
||||
return wrapper
|
||||
|
||||
def safe_execute(
|
||||
func: Callable,
|
||||
default_value: Any = None,
|
||||
log_errors: bool = True,
|
||||
error_message: Optional[str] = None
|
||||
) -> Any:
|
||||
"""
|
||||
Esegue una funzione in modo sicuro, restituendo un valore di default in caso di errore.
|
||||
|
||||
Args:
|
||||
func: Funzione da eseguire
|
||||
default_value: Valore da restituire in caso di errore
|
||||
log_errors: Se loggare gli errori
|
||||
error_message: Messaggio di errore personalizzato
|
||||
|
||||
Returns:
|
||||
Risultato della funzione o valore di default
|
||||
"""
|
||||
try:
|
||||
return func()
|
||||
except Exception as e:
|
||||
if log_errors:
|
||||
message = error_message or f"Error executing {func.__name__}"
|
||||
logger.warning(f"{message}: {str(e)}")
|
||||
return default_value
|
||||
|
||||
class ProviderFallback:
|
||||
"""
|
||||
Classe per gestire il fallback tra provider multipli.
|
||||
"""
|
||||
|
||||
def __init__(self, providers: List[Any]):
|
||||
"""
|
||||
Inizializza con una lista di provider ordinati per priorità.
|
||||
|
||||
Args:
|
||||
providers: Lista di provider ordinati per priorità
|
||||
"""
|
||||
self.providers = providers
|
||||
|
||||
def execute_with_fallback(
|
||||
self,
|
||||
method_name: str,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> Any:
|
||||
"""
|
||||
Esegue un metodo su tutti i provider fino a trovarne uno che funziona.
|
||||
|
||||
Args:
|
||||
method_name: Nome del metodo da chiamare
|
||||
*args: Argomenti posizionali
|
||||
**kwargs: Argomenti nominali
|
||||
|
||||
Returns:
|
||||
Risultato del primo provider che funziona
|
||||
|
||||
Raises:
|
||||
MarketAPIError: Se tutti i provider falliscono
|
||||
"""
|
||||
last_error = None
|
||||
|
||||
for i, provider in enumerate(self.providers):
|
||||
try:
|
||||
if hasattr(provider, method_name):
|
||||
method = getattr(provider, method_name)
|
||||
result = method(*args, **kwargs)
|
||||
|
||||
if i > 0: # Se non è il primo provider
|
||||
logger.info(f"Fallback successful: used provider {type(provider).__name__}")
|
||||
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"Provider {type(provider).__name__} doesn't have method {method_name}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
logger.warning(
|
||||
f"Provider {type(provider).__name__} failed for {method_name}: {str(e)}"
|
||||
)
|
||||
continue
|
||||
|
||||
# Se arriviamo qui, tutti i provider hanno fallito
|
||||
raise MarketAPIError(
|
||||
f"All providers failed for method {method_name}. Last error: {str(last_error)}"
|
||||
)
|
||||
|
||||
def validate_response_data(data: Any, required_fields: Optional[List[str]] = None) -> bool:
|
||||
"""
|
||||
Valida che i dati di risposta contengano i campi richiesti.
|
||||
|
||||
Args:
|
||||
data: Dati da validare
|
||||
required_fields: Lista di campi richiesti
|
||||
|
||||
Returns:
|
||||
True se i dati sono validi, False altrimenti
|
||||
"""
|
||||
if data is None:
|
||||
return False
|
||||
|
||||
if required_fields is None:
|
||||
return True
|
||||
|
||||
if isinstance(data, dict):
|
||||
return all(field in data for field in required_fields)
|
||||
elif hasattr(data, '__dict__'):
|
||||
return all(hasattr(data, field) for field in required_fields)
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user