Datavision / backend /utils /exchange_rates.py
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
4.49 kB
"""
Real-time Exchange Rate Service
Uses free exchange rate API with 1-hour caching
"""
import aiohttp
from datetime import datetime, timedelta
from typing import Dict, Optional
import os
# Cache for exchange rates
_rate_cache: Dict = {
"rates": {},
"base": "USD",
"last_updated": None
}
# Cache duration in hours
CACHE_DURATION_HOURS = 1
async def get_exchange_rates(base_currency: str = "USD") -> Dict[str, float]:
"""
Fetch real-time exchange rates from free API
Results are cached for 1 hour to minimize API calls
Args:
base_currency: Base currency code (default: USD)
Returns:
Dictionary of currency codes to exchange rates
"""
global _rate_cache
# Check if cache is valid
if (
_rate_cache["last_updated"]
and _rate_cache["base"] == base_currency
and datetime.now() - _rate_cache["last_updated"] < timedelta(hours=CACHE_DURATION_HOURS)
):
print(f"📊 Using cached exchange rates for {base_currency}")
return _rate_cache["rates"]
try:
# Free API options (no API key required):
# 1. exchangerate-api.com (free tier)
# 2. open.er-api.com (free tier)
async with aiohttp.ClientSession() as session:
# Using open.er-api.com (no API key needed)
url = f"https://open.er-api.com/v6/latest/{base_currency}"
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response:
if response.status == 200:
data = await response.json()
if data.get("result") == "success":
rates = data.get("rates", {})
# Update cache
_rate_cache = {
"rates": rates,
"base": base_currency,
"last_updated": datetime.now()
}
print(f"✅ Fetched fresh exchange rates for {base_currency}")
return rates
else:
print(f"⚠️ API returned error: {data.get('error-type')}")
else:
print(f"⚠️ API returned status {response.status}")
except Exception as e:
print(f"❌ Failed to fetch exchange rates: {e}")
# Return fallback rates if API fails
return get_fallback_rates(base_currency)
def get_fallback_rates(base: str = "USD") -> Dict[str, float]:
"""
Fallback exchange rates when API is unavailable
These are approximate rates and should be updated periodically
"""
# Approximate rates as of Dec 2024
usd_rates = {
"USD": 1.0,
"EUR": 0.92,
"GBP": 0.79,
"INR": 84.50,
"JPY": 154.0,
"AUD": 1.54,
"CAD": 1.42,
"CHF": 0.88,
"CNY": 7.24,
"SGD": 1.35,
"AED": 3.67,
"SAR": 3.75,
}
if base == "USD":
return usd_rates
# Convert to requested base
if base in usd_rates:
base_rate = usd_rates[base]
return {
currency: rate / base_rate
for currency, rate in usd_rates.items()
}
return usd_rates
async def convert_currency(
amount: float,
from_currency: str,
to_currency: str
) -> float:
"""
Convert amount from one currency to another
Args:
amount: Amount to convert
from_currency: Source currency code
to_currency: Target currency code
Returns:
Converted amount
"""
if from_currency == to_currency:
return amount
rates = await get_exchange_rates(from_currency)
rate = rates.get(to_currency, 1.0)
return round(amount * rate, 2)
def get_cache_status() -> Dict:
"""Get current cache status for debugging"""
return {
"cached": _rate_cache["last_updated"] is not None,
"base": _rate_cache["base"],
"last_updated": _rate_cache["last_updated"].isoformat() if _rate_cache["last_updated"] else None,
"rates_count": len(_rate_cache["rates"]),
"cache_duration_hours": CACHE_DURATION_HOURS
}
# Popular currency pairs for display
POPULAR_CURRENCIES = ["USD", "EUR", "GBP", "INR", "JPY", "AUD", "CAD", "CHF", "CNY", "SGD", "AED"]