Python Snippets

Real-time Currency Exchange Rate Fetcher with Caching

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")

What This Code Does

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:

  1. Class-based approach (CurrencyExchangeRateFetcher): A full-featured class with explicit cache management, error handling, and timestamp validation
  2. Function-based approach (get_exchange_rate_simple): A simplified version using a decorator for automatic caching

Key Features

Why It’s Useful

Currency conversion is a common requirement in financial applications, e-commerce platforms, and international business tools. This snippet solves several real-world problems:

  1. Rate Limiting: Many free exchange rate APIs have strict limits; caching helps stay within those limits
  2. Performance: Cached results are returned instantly, improving user experience
  3. Reliability: Comprehensive error handling prevents application crashes
  4. Cost Efficiency: Reduces API usage, which is important for paid services

How to Run It

  1. Get an API Key: Sign up for a free API key at https://exchangerate-api.com
  2. Install Dependencies: pip install requests
  3. Replace the API Key: Update YOUR_API_KEY_HERE with your actual API key
  4. Run the Script: Execute with python currency_fetcher.py

Customization Options

The caching mechanism ensures you won’t exceed API rate limits while providing fast responses for frequently requested currency pairs.