import requests
import json
from datetime import datetime, timedelta
from typing import Dict, Optional
import functools
import time
class CurrencyExchangeRateFetcher:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://v6.exchangerate-api.com/v6"
self.cache: Dict[str, Dict] = {}
self.cache_duration = 3600 # 1 hour in seconds
def _is_cache_valid(self, cache_key: str) -> bool:
"""Check if cached data is still valid"""
if cache_key not in self.cache:
return False
cached_time = self.cache[cache_key]['timestamp']
return datetime.now() - cached_time < timedelta(seconds=self.cache_duration)
def _create_cache_key(self, base_currency: str, target_currency: str) -> str:
"""Create a unique cache key"""
return f"{base_currency}_{target_currency}"
def get_exchange_rate(self, base_currency: str, target_currency: str) -> Optional[float]:
"""
Fetch exchange rate with caching support
Returns None if request fails or data is unavailable
"""
cache_key = self._create_cache_key(base_currency, target_currency)
# Return cached value if still valid
if self._is_cache_valid(cache_key):
return self.cache[cache_key]['rate']
try:
# Make API request
url = f"{self.base_url}/{self.api_key}/pair/{base_currency}/{target_currency}"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if data.get('result') == 'success':
rate = data.get('conversion_rate')
# Cache the result
self.cache[cache_key] = {
'rate': rate,
'timestamp': datetime.now()
}
return rate
else:
print(f"API Error: {data.get('error-type', 'Unknown error')}")
return None
except requests.exceptions.RequestException as e:
print(f"Network error: {e}")
return None
except json.JSONDecodeError as e:
print(f"JSON parsing error: {e}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
# Decorator for caching function results
def cache_with_ttl(ttl_seconds: int = 3600):
def decorator(func):
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Create cache key from arguments
key = str(args) + str(sorted(kwargs.items()))
current_time = time.time()
# Check if cached result exists and is still valid
if key in cache:
result, timestamp = cache[key]
if current_time - timestamp < ttl_seconds:
return result
# Call function and cache result
result = func(*args, **kwargs)
cache[key] = (result, current_time)
return result
return wrapper
return decorator
# Simplified version using the decorator
@cache_with_ttl(ttl_seconds=3600)
def get_exchange_rate_simple(api_key: str, base: str, target: str) -> Optional[float]:
"""Simplified exchange rate fetcher with automatic caching"""
try:
url = f"https://v6.exchangerate-api.com/v6/{api_key}/pair/{base}/{target}"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
return data.get('conversion_rate') if data.get('result') == 'success' else None
except Exception as e:
print(f"Error fetching exchange rate: {e}")
return None
# Example usage
if __name__ == "__main__":
# Replace with your actual API key from https://exchangerate-api.com
API_KEY = "YOUR_API_KEY_HERE"
# Using the class-based approach
fetcher = CurrencyExchangeRateFetcher(API_KEY)
# Fetch some exchange rates
rates_to_fetch = [
("USD", "EUR"),
("USD", "GBP"),
("EUR", "JPY"),
("USD", "CAD")
]
print("Fetching exchange rates...")
for base, target in rates_to_fetch:
rate = fetcher.get_exchange_rate(base, target)
if rate:
print(f"1 {base} = {rate:.6f} {target}")
else:
print(f"Failed to fetch rate for {base} to {target}")
# Demonstrate caching - second call should be faster
print("\nFetching again (should use cache)...")
start_time = time.time()
rate = fetcher.get_exchange_rate("USD", "EUR")
end_time = time.time()
print(f"1 USD = {rate:.6f} EUR (took {end_time - start_time:.4f} seconds)")
# Using the simplified function
print("\nUsing simplified function:")
rate = get_exchange_rate_simple(API_KEY, "USD", "EUR")
print(f"1 USD = {rate:.6f} EUR")
This snippet provides a robust solution for fetching real-time currency exchange rates with built-in caching to avoid excessive API calls. It includes two implementations:
CurrencyExchangeRateFetcher): A full-featured class with explicit cache management, error handling, and timestamp validationget_exchange_rate_simple): A simplified version using a decorator for automatic cachingCurrency conversion is a common requirement in financial applications, e-commerce platforms, and international business tools. This snippet solves several real-world problems:
pip install requestsYOUR_API_KEY_HERE with your actual API keypython currency_fetcher.pycache_duration to change how long rates are cachedttl_seconds parameter in the decorator versionrates_to_fetch listThe caching mechanism ensures you won’t exceed API rate limits while providing fast responses for frequently requested currency pairs.