|
|
""" |
|
|
Smart Provider API Router |
|
|
Exposes smart provider service with rate limiting, caching, and intelligent fallback |
|
|
""" |
|
|
|
|
|
from fastapi import APIRouter, HTTPException, Query |
|
|
from fastapi.responses import JSONResponse |
|
|
from typing import List, Optional |
|
|
import logging |
|
|
|
|
|
from backend.services.smart_provider_service import get_smart_provider_service |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
router = APIRouter(prefix="/api/smart-providers", tags=["Smart Providers"]) |
|
|
|
|
|
|
|
|
@router.get("/market-prices") |
|
|
async def get_market_prices( |
|
|
symbols: Optional[str] = Query(None, description="Comma-separated list of symbols (e.g., BTC,ETH,BNB)"), |
|
|
limit: int = Query(100, ge=1, le=250, description="Number of results to return") |
|
|
): |
|
|
""" |
|
|
Get market prices with smart provider fallback |
|
|
|
|
|
Features: |
|
|
- Smart provider rotation (Binance → CoinCap → CoinGecko) |
|
|
- Automatic rate limit handling with exponential backoff |
|
|
- Provider-specific caching (30s to 5min) |
|
|
- 429 error prevention for CoinGecko |
|
|
""" |
|
|
try: |
|
|
service = get_smart_provider_service() |
|
|
|
|
|
|
|
|
symbol_list = None |
|
|
if symbols: |
|
|
symbol_list = [s.strip().upper() for s in symbols.split(',')] |
|
|
|
|
|
|
|
|
result = await service.get_market_prices(symbols=symbol_list, limit=limit) |
|
|
|
|
|
return JSONResponse(content={ |
|
|
"success": True, |
|
|
"data": result['data'], |
|
|
"meta": { |
|
|
"source": result['source'], |
|
|
"cached": result.get('cached', False), |
|
|
"timestamp": result['timestamp'], |
|
|
"count": len(result['data']), |
|
|
"error": result.get('error') |
|
|
} |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error fetching market prices: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/provider-stats") |
|
|
async def get_provider_stats(): |
|
|
""" |
|
|
Get statistics for all providers |
|
|
|
|
|
Returns: |
|
|
- Provider health status |
|
|
- Success/failure rates |
|
|
- Rate limit hits |
|
|
- Backoff status |
|
|
- Cache statistics |
|
|
""" |
|
|
try: |
|
|
service = get_smart_provider_service() |
|
|
stats = service.get_provider_stats() |
|
|
|
|
|
return JSONResponse(content={ |
|
|
"success": True, |
|
|
"stats": stats |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error fetching provider stats: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/reset-provider/{provider_name}") |
|
|
async def reset_provider(provider_name: str): |
|
|
""" |
|
|
Reset a specific provider's backoff and stats |
|
|
|
|
|
Use this to manually reset a provider that's in backoff mode |
|
|
""" |
|
|
try: |
|
|
service = get_smart_provider_service() |
|
|
service.reset_provider(provider_name) |
|
|
|
|
|
return JSONResponse(content={ |
|
|
"success": True, |
|
|
"message": f"Provider {provider_name} reset successfully" |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error resetting provider: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/clear-cache") |
|
|
async def clear_cache(): |
|
|
""" |
|
|
Clear all cached data |
|
|
|
|
|
Use this to force fresh data from providers |
|
|
""" |
|
|
try: |
|
|
service = get_smart_provider_service() |
|
|
service.clear_cache() |
|
|
|
|
|
return JSONResponse(content={ |
|
|
"success": True, |
|
|
"message": "Cache cleared successfully" |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error clearing cache: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/health") |
|
|
async def health_check(): |
|
|
""" |
|
|
Check health of smart provider service |
|
|
""" |
|
|
try: |
|
|
service = get_smart_provider_service() |
|
|
stats = service.get_provider_stats() |
|
|
|
|
|
|
|
|
available_count = sum( |
|
|
1 for p in stats['providers'].values() |
|
|
if p.get('is_available', False) |
|
|
) |
|
|
|
|
|
total_count = len(stats['providers']) |
|
|
|
|
|
return JSONResponse(content={ |
|
|
"success": True, |
|
|
"status": "healthy" if available_count > 0 else "degraded", |
|
|
"available_providers": available_count, |
|
|
"total_providers": total_count, |
|
|
"cache_entries": stats['cache']['valid_entries'] |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error checking health: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
__all__ = ["router"] |
|
|
|