How to Build a Currency Converter in Python
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()
# Check cache
if cache_key in self.cache:
cached_data, timestamp = self.cache[cache_key]
if datetime.now() - timestamp < self.cache_duration:
return cached_data
# Fetch fresh 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)
# Usage
converter = CurrencyConverter()
print(converter.convert(100, "USD", "EUR"))
print(converter.get_rate("GBP", "JPY"))
Output:
84.2
209.2
Caching exchange rates reduces API calls and improves performance. Most free APIs update rates daily, so hourly cache refresh is sufficient for non-critical applications.
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
The forex-python library relies on web scraping, which can be slower and less reliable than direct API calls. For production applications, prefer a dedicated API service.
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]
Free API tiers typically have rate limits. For high-volume applications, consider paid plans or implement request queuing.
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.