The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
A currency converter is a practical first project because one short script exercises most core Python ideas: values and variables, user input, number conversion, functions, conditionals, and error handling. Once that works, it can also teach HTTP requests and JSON. This guide builds the converter in two stages. The first version uses a small fixed table of rates and needs nothing but Python. The second replaces that table with rates fetched from a published exchange-rate API. The first stage teaches the logic; the second shows how real data arrives and how it can fail.
Stage 1: A converter with fixed rates
Fixed rates simplify the problem. You store a few exchange rates in a dictionary, ask the user for an amount and two currency codes, and calculate the result. The numbers below are illustrative sample values for practice, not current market rates.
Store the rates and state the assumption
Each value in the dictionary is the number of units of that currency equal to one US dollar:
RATES = {"USD": 1.0, "EUR": 0.92, "GBP": 0.79}
The assumption is that these rates never change. They will be wrong the next day, and that is the reason the second stage exists. Writing the assumption down in a comment at the top of the file keeps you honest about what the program does.
#1 Best Overall
Write the conversion as a function
def convert(amount, from_code, to_code, rates):
amount_in_usd = amount / rates[from_code]
return amount_in_usd * rates[to_code]
Conversion is the amount multiplied by a rate. When neither currency is the base currency, the calculation goes through the base: divide to get US dollars, then multiply by the target rate. For example, 100 EUR to GBP is 100 ÷ 0.92 × 0.79, which is about 85.87 at these sample rates.
Keep this function free of input() and print(). A function that only takes values and returns a value can be checked by calling it with known inputs, and it works the same way when you later swap in live rates.
Validate input before you calculate
- Amount: convert the text to a number inside
tryandexcept ValueError, then reject anything that is not greater than zero. - Currency codes: remove surrounding spaces, convert to uppercase, and check that the code is a key in
RATES. If it is not, print the list of supported codes.
def read_amount(text):
try:
amount = float(text)
except ValueError:
return None
if amount <= 0:
return None
return amount
The function returns None for bad input and leaves the message to the caller, which keeps it focused on one job. Floats are acceptable for a fixed-rate exercise. The second stage switches to Decimal for the arithmetic.
Rank #2
Stage 2: Replace fixed rates with a published rate
An API-backed version usually sends an HTTP GET request, checks the status code, parses a JSON response, and reads the rate for the currency you asked for. The Python guides from two exchange-rate providers follow this pattern. One of them states that you do not need an SDK; the other says you need a free account and an API key.
Choose a documented provider
Use the provider’s own documentation as the reference for its endpoint, parameters, and response fields. Field names and addresses differ between services, and they can change, so copy them from the current documentation rather than from a tutorial. The comparison table later in this guide lists what each provider’s pages state.
Make the request and check the response
- Install the library with
pip install requests, then import it at the top of the file. - Send a GET request to the latest-rates address in the provider’s documentation. Pass the base currency and any target currency as query parameters in the form the documentation specifies.
- Check the HTTP status code before reading the body. Treat anything other than a successful response as a failure and report it.
- Parse the body with
response.json(). - Confirm that the fields you expect are present, such as the object that holds the rates, the key for your target currency, and the date, before you index into them.
- Multiply the amount by the rate for the target currency. Use the direction of the rate as the provider documents it: some responses give units of the target currency per unit of the base, and others use the reverse.
Parse rates with Decimal
Frankfurter’s guide recommends parsing rates with Decimal. Its position is that floats are fine for display but wrong for accounting. This tutorial is a learning exercise, not accounting software, but the habit is worth building early:
from decimal import Decimal, InvalidOperation
def read_amount_decimal(text):
try:
amount = Decimal(text)
except InvalidOperation:
return None
if not amount.is_finite() or amount <= 0:
return None
return amount
rate = Decimal(str(raw_rate))
converted = (amount * rate).quantize(Decimal("0.01"))
Passing the string the user typed into Decimal avoids the binary rounding that comes with float input. quantize rounds the result to two decimal places for display. The variable raw_rate stands for the value you read from the parsed response.
Show the date with every result
When the response includes a date or timestamp, print it next to the converted amount. A figure without a date hides how old the rate is, and the reader cannot judge whether it is still useful.
What a provider’s rate does and does not tell you
A published reference rate is not necessarily the rate a traveler or customer receives. A bank, card issuer, or exchange service may apply its own rate, margin, or fee. Your converter shows the provider’s figure, and your program should say so.
“Current” also needs qualifying. Frankfurter states that its latest blended rates change as providers publish, at most a few times a working day. Its pinned rates for a given official source follow that publisher’s own schedule and can lag a blended latest rate. The following table compares what each provider’s pages state. Update schedules, plan terms, and endpoint behavior are set by the providers and change over time, so check the current pages before you rely on them.
| Provider | API key or account | SDK or direct request | Update schedule (as stated by the provider) | Historical rates | Conversion endpoint |
|---|---|---|---|---|---|
| Frankfurter | No key needed; its Python guide uses a plain requests call | Direct request; the documentation says “You don’t need an SDK.” | Latest blended rates change as providers publish, at most a few times a working day | Pinned historical rates documented | Not stated in the reviewed guide; you multiply the amount yourself |
| ExchangeRate-API | Free account and API key required, per its Python guide | Direct GET request | Not stated in the reviewed guide | Not stated in the reviewed guide | Not stated in the reviewed guide |
| currencyapi | Not stated in the reviewed page | Both an SDK and direct requests | Daily to minutely, per the provider’s page | Not stated in the reviewed page | Documented, but the provider’s page says it is not available on the free plan |
Error handling and failure behavior
A converter that depends on a network service has more ways to fail than a fixed-rate one. Plan for each of these cases and print a clear message for each:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problems- Invalid currency code: Frankfurter documents a response for an invalid currency code. Check the status and body for that case, and show your supported-codes message instead of letting the program crash. Do not depend on exact error wording unless the documentation specifies it.
- HTTP or API failure: if the status is not successful, print a message such as “Could not get rates right now” and stop the conversion. Do not silently substitute the fixed rates from stage one, because the reader would receive an answer with no warning.
- Missing field: check that the key exists before you index it, so a changed response shape produces a readable message rather than a traceback.
- Network failure: wrap the request so connection problems are caught, and set a timeout so the program does not wait indefinitely.
import requests
try:
response = requests.get(url, params=params, timeout=10)
except requests.RequestException:
print("Could not reach the rate service.")
The variable url stands for the latest-rates address from the provider’s documentation, and params holds the query values the documentation specifies.
Best Value
Troubleshooting common symptoms
| Symptom | Likely cause | What to check |
|---|---|---|
| The result is far too large or too small | The rate is in the opposite direction from the one your formula assumes | The base currency in the response and whether the rate means units of target per base or the reverse |
| KeyError when reading a currency | The code is not in the response, or it was not normalised | Print the keys the response returned and confirm your code is uppercase |
| Authorisation or access error | The API key is missing, wrong, or sent in the wrong place | The key’s environment variable is set, and the key is passed the way the provider’s guide shows |
| The same figure appears every run | Fixed rates from stage one are still in use, or a cached response is being reused | Which code path runs, and the timestamp stored with the cache |
| The result differs from a bank’s figure | The provider’s published rate is not the rate the bank applies | This is expected; show the provider’s date and label the figure as a reference rate |
Keep API keys out of your source code and cache responses sensibly
For a key-based service, store the key in an environment variable and read it with os.environ.get. Do not paste the key into a file you might publish or share. Frankfurter’s guide recommends short caching for latest rates and allows long caching for pinned historical rates, because a historical rate for a fixed date does not change. A simple cache stores the response with the time it was fetched and reuses it only within a short window you choose.
Extensions after the command-line version works
Add these only once the command-line version runs correctly from start to finish:
- A Tkinter interface: one entry box for the amount, two dropdowns for currency codes, and a label for the result. Check that your Python installation includes Tkinter before you start.
- A conversion history: append each conversion, with its date and rate, to a list, then write the list to a file when the program exits.
- A cache: apply the caching rules above, and make sure the cached response still displays its fetch time.
Each extension reuses the same conversion function you wrote in stage one, which is why keeping that function separate from input and output pays off.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

