Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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 try and except 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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

  1. Install the library with pip install requests, then import it at the top of the file.
  2. 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.
  3. Check the HTTP status code before reading the body. Treat anything other than a successful response as a failure and report it.
  4. Parse the body with response.json().
  5. 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.
  6. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • 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.

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
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.