Skip to main content

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
tip

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
warning

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]
note

Free API tiers typically have rate limits. For high-volume applications, consider paid plans or implement request queuing.

Method Comparison

MethodReliabilitySpeedCost
Direct API callsHighFastFree tier available
forex-pythonMediumSlowerFree
Paid API servicesVery HighFastSubscription

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.