Skip to content

How to Build a Currency Converter in Python

Learn to create a Python currency converter using free APIs like ExchangeRate-API, handle real-time exchange rates with caching, and build CLI tools with robust error handling.

Converting currencies programmatically requires fetching real-time exchange rates from external APIs. Python's requests library makes this straightforward, enabling integration with various free and paid exchange rate services.

Using a Free Exchange Rate API

The ExchangeRate-API provides free access to current exchange rates:

import requests

def convert_currency(amount, from_currency, to_currency):
    url = f"https://api.exchangerate-api.com/v4/latest/{from_currency}"
    
    response = requests.get(url)
    response.raise_for_status()
    
    data = response.json()
    rate = data["rates"][to_currency]
    
    return amount * rate

result = convert_currency(100, "USD", "EUR")
print(f"100 USD = {result:.2f} EUR")

Output:

100 USD = 84.20 EUR

Building a Robust Converter Class

A production-ready implementation with caching and error handling:

import requests
from datetime import datetime, timedelta
from typing import Optional

class CurrencyConverter:
    def __init__(self, base_url="https://api.exchangerate-api.com/v4/latest"):
        self.base_url = base_url
        self.cache = {}
        self.cache_duration = timedelta(hours=1)
    
    def _fetch_rates(self, base_currency: str) -> dict:
        cache_key = base_currency.upper()
        
        if cache_key in self.cache:
            cached_data, timestamp = self.cache[cache_key]
            if datetime.now() - timestamp < self.cache_duration:
                return cached_data
        
        url = f"{self.base_url}/{base_currency}"
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        
        data = response.json()
        self.cache[cache_key] = (data["rates"], datetime.now())
        
        return data["rates"]
    
    def convert(self, amount: float, from_curr: str, to_curr: str) -> Optional[float]:
        try:
            rates = self._fetch_rates(from_curr.upper())
            target_rate = rates.get(to_curr.upper())
            
            if target_rate is None:
                raise ValueError(f"Unknown currency: {to_curr}")
            
            return amount * target_rate
        except requests.RequestException as e:
            print(f"API request failed: {e}")
            return None
        except (KeyError, ValueError) as e:
            print(f"Conversion error: {e}")
            return None
    
    def get_rate(self, from_curr: str, to_curr: str) -> Optional[float]:
        return self.convert(1, from_curr, to_curr)

converter = CurrencyConverter()
print(converter.convert(100, "USD", "EUR"))
print(converter.get_rate("GBP", "JPY"))

Output:

84.2
209.2

Using the forex-python Library

For a simpler abstraction without manual API handling:

pip install forex-python
from forex_python.converter import CurrencyRates, CurrencyCodes

def convert_with_forex(amount, from_curr, to_curr):
    rates = CurrencyRates()
    codes = CurrencyCodes()
    
    converted = rates.convert(from_curr, to_curr, amount)
    symbol = codes.get_symbol(to_curr)
    
    return converted, symbol

amount, symbol = convert_with_forex(100, "USD", "EUR")
print(f"{symbol}{amount:.2f}")

Output:

€84.22

Command-Line Interface Application

A complete CLI tool with validation and error handling:

import requests
import sys

class CurrencyConverterCLI:
    API_URL = "https://api.exchangerate-api.com/v4/latest"
    
    def __init__(self):
        self.supported_currencies = None
    
    def fetch_supported_currencies(self, base="USD"):
        try:
            response = requests.get(f"{self.API_URL}/{base}", timeout=10)
            response.raise_for_status()
            self.supported_currencies = list(response.json()["rates"].keys())
            self.supported_currencies.append(base)
            return True
        except requests.RequestException:
            return False
    
    def validate_currency(self, currency):
        if self.supported_currencies is None:
            self.fetch_supported_currencies()
        return currency.upper() in self.supported_currencies
    
    def convert(self, amount, from_curr, to_curr):
        url = f"{self.API_URL}/{from_curr.upper()}"
        
        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            
            rates = response.json()["rates"]
            rate = rates.get(to_curr.upper())
            
            if rate is None:
                return None, f"Currency {to_curr} not found"
            
            return amount * rate, None
            
        except requests.Timeout:
            return None, "Request timed out"
        except requests.RequestException as e:
            return None, f"API error: {e}"
    
    def run(self):
        print("=== Currency Converter ===\n")
        
        try:
            from_curr = input("From currency (e.g., USD): ").strip().upper()
            if not self.validate_currency(from_curr):
                print(f"Invalid currency: {from_curr}")
                return
            
            to_curr = input("To currency (e.g., EUR): ").strip().upper()
            if not self.validate_currency(to_curr):
                print(f"Invalid currency: {to_curr}")
                return
            
            amount_str = input("Amount: ").strip()
            amount = float(amount_str)
            
            if amount < 0:
                print("Amount must be positive")
                return
            
            result, error = self.convert(amount, from_curr, to_curr)
            
            if error:
                print(f"Error: {error}")
            else:
                print(f"\n{amount:,.2f} {from_curr} = {result:,.2f} {to_curr}")
                
        except ValueError:
            print("Invalid amount entered")
        except KeyboardInterrupt:
            print("\nCancelled")

if __name__ == "__main__":
    cli = CurrencyConverterCLI()
    cli.run()

Output:

=== Currency Converter ===

From currency (e.g., USD): USD
To currency (e.g., EUR): EUR
Amount: 100

100.00 USD = 84.20 EUR

Multi-Currency Conversion

Convert an amount to multiple currencies at once:

import requests

def convert_to_multiple(amount, from_curr, to_currencies):
    url = f"https://api.exchangerate-api.com/v4/latest/{from_curr}"
    
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    
    rates = response.json()["rates"]
    
    results = {}
    for currency in to_currencies:
        rate = rates.get(currency.upper())
        if rate:
            results[currency.upper()] = amount * rate
    
    return results

conversions = convert_to_multiple(1000, "USD", ["EUR", "GBP", "JPY", "CAD"])

print("1000 USD equals:")
for currency, value in conversions.items():
    print(f"  {currency}: {value:,.2f}")

Output:

1000 USD equals:
  EUR: 842.00
  GBP: 733.00
  JPY: 153,320.00
  CAD: 1,360.00

Handling API Rate Limits

Implement exponential backoff for rate-limited APIs:

import requests
import time
from functools import wraps

def retry_with_backoff(max_retries=3, base_delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except requests.exceptions.HTTPError as e:
                    if e.response.status_code == 429:  # Too Many Requests
                        delay = base_delay * (2 ** attempt)
                        print(f"Rate limited. Waiting {delay}s...")
                        time.sleep(delay)
                    else:
                        raise
            raise Exception("Max retries exceeded")
        return wrapper
    return decorator

@retry_with_backoff(max_retries=3)
def fetch_rate(base, target):
    url = f"https://api.exchangerate-api.com/v4/latest/{base}"
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response.json()["rates"][target]

Method Comparison

Method Reliability Speed Cost
Direct API calls High Fast Free tier available
forex-python Medium Slower Free
Paid API services Very High Fast Subscription

For production applications, use direct API calls with proper error handling and caching. Free APIs like ExchangeRate-API are suitable for moderate traffic, while high-volume applications should consider paid services with better rate limits and SLAs.

Navigation