Build a Country Data Explorer CLI

Build a Python CLI that fetches real country data from a live REST API.

Introduction

30 Second Summary

Ever searched for a country's population, capital, or languages and had to click through five different websites to find what you need? Developers solve this by pulling data directly from live sources using code.

In this project, you will build a command-line tool in Python that fetches real country data from a public API, displays it in your terminal, and saves reports to files.

What You'll Build

A command-line Country Data Explorer that connects to the REST Countries API, letting you search for countries by name, browse all countries in a region, and generate sorted population reports saved as JSON files.

By the end of this project, you'll have:

  • A Python script structured with 6+ focused, reusable functions that talk to a live API.
  • Formatted terminal output displaying country details like population, capital, region, and languages.
  • A JSON report file with sorted population data written to disk by your program.
  • Secret Mission: Add a "compare countries" feature that shows two countries side-by-side.

Are there any prerequisites?

You should be comfortable with basic Python syntax: variables, loops, if statements, and print. This project will teach you how to combine those basics into real, structured programs.

Set Up Your Project

Your project folder is ready and the requests library is installed. Now it's time to write actual Python code that talks to a live API.

In this step, you'll create your main script and fire off your first HTTP request to the REST Countries API. You'll see real country data land in your terminal and learn how to navigate its nested structure.

In this step, get ready to:
  • Create your main Python file and test the API connection.
  • Understand the nested data shape the API returns.
Create your main file and test the API connection

Every Python project starts with a file. You'll create the single script that will eventually hold all your explorer functions.

  • In the VS Code Explorer sidebar on the left, hover over the COUNTRY-EXPLORER heading.
  • Click the New File icon (it looks like a page with a plus sign).
  • Type explorer.py and press Enter.

The file opens in the editor. This is where all your code will live.

  • Write your first API test by pasting this code into explorer.py:
import requests

# Make a GET request to the REST Countries API for Germany
response = requests.get("https://restcountries.com/v3.1/name/germany?fields=name,capital,population,region,languages")
print(response.json())

What does this code do?

import requests loads the HTTP library you installed earlier.

requests.get() sends an HTTP GET request to the REST Countries API, asking for Germany's name, capital, population, region, and languages. response.json() converts the raw response into a Python data structure (a list of dictionaries) and prints it.

  • Save explorer.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Run the script in your terminal by running this command:
python3 explorer.py
  • You should see a list containing a dictionary with Germany's data printed in your terminal. It will look something like [{"name": {"common": "Germany", ...}, "capital": ["Berlin"], ...}].

Not seeing any output?

  • Make sure you saved the file before running the command. Unsaved changes won't execute.
  • Check that your terminal is inside the country-explorer folder. Run ls and confirm you see explorer.py in the output.
  • If you see a "ModuleNotFoundError" for requests, the library may not be installed in the Python version you're running. Verify with pip3 install requests==2.34.2.

Help me debug why my explorer.py script is not printing country data.

Understand the data shape you are working with

Look at the output you just printed. This is real-world API data. It is not flat or simple.

What is this data structure?

The API returns a list of dictionaries. Each country dictionary has nested structures that you'll need to navigate throughout this project:

  • name is a dictionary containing common (like "Germany") and official (like "Federal Republic of Germany").
  • capital is a list (not a string). Berlin is stored as ["Berlin"], so you access it with capital[0].
  • languages is a dictionary mapping language codes to names, like {"deu": "German"}.
  • population and region are simple values (an integer and a string).

Your job as a backend engineer is to extract what you need from messy, nested structures like this. Every function you write in the next steps will reach into this shape to pull out specific pieces.

✔️ Awesome, I've got everything!

Great. Double check you've saved explorer.py and that you saw country data printed in your terminal.

ⓧ I'd like to double check the full code

Here is the complete explorer.py file at the end of this step:

import requests

response = requests.get("https://restcountries.com/v3.1/name/germany?fields=name,capital,population,region,languages")
print(response.json())

You've confirmed that your script can reach the API and pull back real data. Next up, you'll turn this test code into proper, reusable functions that fetch and display country information.

Build Your First Functions

Your test script confirmed the API works and you can see raw data in your terminal. Now it is time to structure that code into reusable functions.

Right now, everything lives in one flat script with no organization. Real Python applications split logic into focused functions, each with one job. This makes code easier to test, reuse, and debug.

In this step, get ready to:
  • Write a function that fetches country data from the API with error handling.
  • Write a function that displays country data in a formatted layout.
  • Wire both functions together with a main function and run your program.
Write a fetch_country function

Your first function handles all communication with the REST Countries API. It takes a country name, builds the URL, makes the request, and returns parsed data.

Wrapping the request in a try/except block means your program handles network failures gracefully instead of crashing.

  • In explorer.py, select all the existing code and replace it with the following:
import requests
import json


def fetch_country(name):
    """Fetch country data by name from the REST Countries API."""
    url = f"https://restcountries.com/v3.1/name/{name}?fields=name,capital,population,region,languages"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            print(f"Country '{name}' not found. Check the spelling and try again.")
            return None
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Network error: could not reach the API. Details: {e}")
        return None

What does this code do?

  • import json is added now because you will use it for file output in a later step.
  • The f-string in url inserts the name parameter directly into the API endpoint.
  • timeout=10 tells requests to give up after 10 seconds if the server does not respond.
  • response.raise_for_status() raises an error for any non-success HTTP status code (like 500), which the except block then catches.
  • Returning None on failure gives the calling code a clean way to check if the fetch worked.

Why return None instead of crashing?

When your code calls fetch_country(), it can check if data is None and handle the problem. This is how professional code works. The function reports the issue, and the caller decides what to do next.

  • Save explorer.py.

You will not see output yet. The main function you write in a few minutes will call fetch_country and display the result.

Write a display_country function

This function takes one country dictionary and prints its data in a clean, readable format. It extracts values from nested structures and handles missing data safely.

  • Add the following function below fetch_country, leaving two blank lines between them:
def display_country(country):
    """Display formatted information about a single country."""
    name = country["name"]["common"]
    capital = country["capital"][0] if country["capital"] else "N/A"
    population = country["population"]
    region = country["region"]
    languages = ", ".join(country["languages"].values()) if country["languages"] else "N/A"

    print(f"\n{'=' * 40}")
    print(f"  {name}")
    print(f"{'=' * 40}")
    print(f"  Capital:    {capital}")
    print(f"  Population: {population:,}")
    print(f"  Region:     {region}")
    print(f"  Languages:  {languages}")
    print(f"{'=' * 40}\n")

What does this code do?

  • country["name"]["common"] digs two levels into the nested dictionary to get the common name (e.g. "Germany").
  • country["capital"][0] if country["capital"] else "N/A" safely grabs the first capital from the list, falling back to "N/A" if the list is empty.
  • ", ".join(country["languages"].values()) pulls all language names from the languages dictionary and joins them into a readable string.
  • {population:,} in the f-string adds commas to large numbers (e.g. 83,491,249 instead of 83491249).
  • Save explorer.py.

You will not see output yet. The next chunk adds a main function that ties everything together.

Wire them together with a main function

A main function acts as the entry point for your program. It calls the functions you just wrote in the correct order.

  • Add the following code below display_country, leaving two blank lines between them:
def main():
    data = fetch_country("germany")
    if data is None:
        return
    display_country(data[0])


if __name__ == "__main__":
    main()

What does this code do?

  • fetch_country("germany") calls your function and stores the result (a list of country dictionaries or None).
  • if data is None: return stops the program early if the API call failed.
  • data[0] grabs the first country from the list (the API always returns a list, even for one result).
  • if __name__ == "__main__": ensures main() only runs when you execute the file directly, not when it is imported elsewhere.
  • Save explorer.py.
  • Run your program by executing this command in the terminal:
python3 explorer.py

You should see formatted Germany data in your terminal with a box of equals signs, the country name, capital, population with commas, region, and languages.

Not seeing formatted output?

  • Make sure you deleted the old test code (the bare response = requests.get(...) and print(response.json()) lines). Only the function-based code should remain.
  • Check that if __name__ == "__main__": is at the very bottom of the file with no indentation.
  • If you see IndentationError, make sure every line inside each function uses exactly 4 spaces of indentation.

ask me for help debugging my explorer.py output

✔️ Awesome, I've got everything!

Great. Double check you have saved explorer.py and confirmed the formatted output in your terminal.

ⓧ I'd like to double check the full code

Here is the complete explorer.py at this point:

import requests
import json


def fetch_country(name):
    """Fetch country data by name from the REST Countries API."""
    url = f"https://restcountries.com/v3.1/name/{name}?fields=name,capital,population,region,languages"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            print(f"Country '{name}' not found. Check the spelling and try again.")
            return None
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Network error: could not reach the API. Details: {e}")
        return None


def display_country(country):
    """Display formatted information about a single country."""
    name = country["name"]["common"]
    capital = country["capital"][0] if country["capital"] else "N/A"
    population = country["population"]
    region = country["region"]
    languages = ", ".join(country["languages"].values()) if country["languages"] else "N/A"

    print(f"\n{'=' * 40}")
    print(f"  {name}")
    print(f"{'=' * 40}")
    print(f"  Capital:    {capital}")
    print(f"  Population: {population:,}")
    print(f"  Region:     {region}")
    print(f"  Languages:  {languages}")
    print(f"{'=' * 40}\n")


def main():
    data = fetch_country("germany")
    if data is None:
        return
    display_country(data[0])


if __name__ == "__main__":
    main()

Your code is now structured into focused, reusable functions. Next up, you will add user input so the explorer can look up any country, and build a region browsing feature.

Add User Input and Region Browsing

Your explorer can fetch and display country data. The problem is that it only knows about Germany.

In this step, you will make the tool interactive so users can search for any country they want. You will also add a new feature that fetches all countries in a region and displays them as a sorted population table.

In this step, get ready to:
  • Replace the hardcoded country name with live user input.
  • Write a function that fetches all countries in a region.
  • Write a function that displays a sorted population summary.
Replace the hardcoded country with user input

Right now, main() always passes "germany" to fetch_country(). Python's built-in input() function pauses the program and waits for the user to type something, returning it as a string.

  • In explorer.py, scroll down to the main() function.
  • Find this code:
def main():
    data = fetch_country("germany")
    if data is None:
        return
    display_country(data[0])
  • Replace the entire main() function with this updated version:
def main():
    name = input("Enter country name: ").strip()
    data = fetch_country(name)
    if data:
        display_country(data[0])

What does this code do?

  • input("Enter country name: ") pauses the program and displays a prompt. The user types a country name and presses Enter.
  • .strip() removes any accidental spaces the user typed before or after the country name.
  • if data: checks that the API returned valid data before trying to display it. If fetch_country() returned None (due to a 404 or network error), the condition fails and nothing is displayed.
  • Save explorer.py.
  • Run the script by entering this command in your terminal:
python3 explorer.py
  • When prompted, type japan and press Enter. You should see formatted data for Japan (capital, population, region, languages).
  • Run it again and type a misspelled name like xyz123. You should see the "not found" error message from your fetch_country() function.

Program exits without asking for input?

  • Make sure your if __name__ == "__main__": block at the bottom still calls main().
  • Check that you replaced the old main() function body and did not accidentally leave two def main(): definitions in the file.

Still stuck? Ask for help.

Write a fetch_region function

The REST Countries API also has a region endpoint that returns every country in a given region (like Europe or Asia). You will write a new function that calls this endpoint using the same error handling pattern you already built.

  • In explorer.py, add this new function between display_country() and main():
def fetch_region(region):
    """Fetch all countries in a given region."""
    url = f"https://restcountries.com/v3.1/region/{region}?fields=name,capital,population,languages"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            print(f"Region '{region}' not found.")
            return None
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Network error: could not reach the API. Details: {e}")
        return None

What does this code do?

  • The URL targets the /region/{region} endpoint, which returns a list of all countries in that region.
  • The ?fields=name,capital,population,languages query parameter tells the API to only send back the fields you need, keeping responses fast and small.
  • The error handling is identical to fetch_country(). You check for 404 status codes, call raise_for_status() for other HTTP errors, and catch network-level exceptions with requests.exceptions.RequestException.
  • Save explorer.py.
  • Verify the function works by running a quick test in your terminal:
python3 -c "from explorer import fetch_region; data = fetch_region('europe'); print(len(data), 'countries found')"

You should see output like 53 countries found (the exact number may vary slightly).

Seeing a ModuleNotFoundError?

  • Make sure you are running the command from inside the country-explorer folder where explorer.py lives.
  • Check that the function name is spelled exactly fetch_region with an underscore.

Help me fix my fetch_region import error.

Write a display_region_summary function

Fetching data is only half the job. You need a function that takes the raw list of country dictionaries and displays them as a clean, sorted table in the terminal.

  • In explorer.py, add this function directly below fetch_region() and above main():
def display_region_summary(countries):
    """Display a sorted summary of countries in a region."""
    sorted_countries = sorted(countries, key=lambda c: c["population"], reverse=True)

    print(f"\n{'No.':<5}{'Country':<30}{'Population':>15}")
    print("-" * 50)

    for i, country in enumerate(sorted_countries, 1):
        name = country["name"]["common"]
        population = country["population"]
        print(f"{i:<5}{name:<30}{population:>15,}")

    print("-" * 50)
    print(f"Total countries: {len(sorted_countries)}\n")

What does this code do?

  • sorted() with key=lambda c: c["population"] sorts the list by each country's population. The reverse=True flag puts the highest population first.
  • The f-string format specifiers (:<5, :<30, :>15) create aligned columns. The < means left-aligned and > means right-aligned.
  • The :, in {population:>15,} adds comma separators to large numbers (e.g. 83,491,249 instead of 83491249).
  • enumerate(sorted_countries, 1) gives you both an index starting at 1 and each country, so you can print numbered rows.
  • Save explorer.py.
  • Test the full region flow by running this command:
python3 -c "from explorer import fetch_region, display_region_summary; data = fetch_region('asia'); display_region_summary(data)"

You should see a numbered table with Asian countries sorted by population, with China or India at the top and a total count at the bottom.

Seeing a KeyError or TypeError?

  • A KeyError on "population" means the API response does not contain that field. Double-check that the URL in fetch_region() includes ?fields=name,capital,population,languages with no spaces.
  • A TypeError: 'NoneType' means fetch_region() returned None. Check your internet connection and make sure the region name is lowercase (e.g. asia not Asia).

Help me debug my display_region_summary output.

✔️ Awesome, I've got everything!

Great. Double check you have saved explorer.py and both test commands ran successfully.

ⓧ I'd like to double check the full code

Here is the complete explorer.py at this point. Compare yours against it:

import requests
import json


def fetch_country(name):
    """Fetch country data by name from the REST Countries API."""
    url = f"https://restcountries.com/v3.1/name/{name}?fields=name,capital,population,region,languages"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            print(f"Country '{name}' not found. Check the spelling and try again.")
            return None
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Network error: could not reach the API. Details: {e}")
        return None


def display_country(country):
    """Display formatted information about a single country."""
    name = country["name"]["common"]
    capital = country["capital"][0] if country["capital"] else "N/A"
    population = country["population"]
    region = country["region"]
    languages = ", ".join(country["languages"].values()) if country["languages"] else "N/A"

    print(f"\n{'=' * 40}")
    print(f"  {name}")
    print(f"{'=' * 40}")
    print(f"  Capital:    {capital}")
    print(f"  Population: {population:,}")
    print(f"  Region:     {region}")
    print(f"  Languages:  {languages}")
    print(f"{'=' * 40}\n")


def fetch_region(region):
    """Fetch all countries in a given region."""
    url = f"https://restcountries.com/v3.1/region/{region}?fields=name,capital,population,languages"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            print(f"Region '{region}' not found.")
            return None
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Network error: could not reach the API. Details: {e}")
        return None


def display_region_summary(countries):
    """Display a sorted summary of countries in a region."""
    sorted_countries = sorted(countries, key=lambda c: c["population"], reverse=True)

    print(f"\n{'No.':<5}{'Country':<30}{'Population':>15}")
    print("-" * 50)

    for i, country in enumerate(sorted_countries, 1):
        name = country["name"]["common"]
        population = country["population"]
        print(f"{i:<5}{name:<30}{population:>15,}")

    print("-" * 50)
    print(f"Total countries: {len(sorted_countries)}\n")


def main():
    name = input("Enter country name: ").strip()
    data = fetch_country(name)
    if data:
        display_country(data[0])


if __name__ == "__main__":
    main()

Your explorer can now look up any country and browse entire regions. Next up, you will build a menu system that ties all these features together and add the ability to save reports as JSON files.

Build the Menu System and File Output

Your individual functions are working. You can look up a country by name and browse all countries in a region.

But right now, your script does one thing and exits. In this step, you will build an interactive menu that keeps running until the user decides to quit. You will also add a feature that saves region data to a JSON file on disk.

In this step, get ready to:
  • Create a run_menu function with a persistent loop.
  • Write a save_report function that exports data to JSON.
  • Add input validation so the app handles bad input gracefully.
Create a menu loop

Right now your main() function asks for one country and exits. A real CLI app needs a loop that keeps offering choices until the user decides to leave.

  • In explorer.py, find and delete the entire main() function. It looks like this:
def main():
    name = input("Enter country name: ").strip()
    data = fetch_country(name)
    if data:
        display_country(data[0])
  • Replace it with this new run_menu() function by pasting the following code in the same location (after display_region_summary):
def run_menu():
    """Run the interactive menu loop."""
    print("\n\U0001f30d Country Data Explorer")
    print("========================\n")

    while True:
        # Print the menu options each time the loop runs
        print("[1] Search for a country")
        print("[2] Browse a region")
        print("[3] Save region report")
        print("[4] Quit")

        choice = input("\nYour choice: ").strip()

        if choice == "1":
            name = input("Enter country name: ").strip()
            data = fetch_country(name)
            if data:
                display_country(data[0])

        elif choice == "2":
            region = input("Enter region: ").strip().lower()
            countries = fetch_region(region)
            if countries:
                display_region_summary(countries)

        elif choice == "3":
            region = input("Enter region to save report for: ").strip().lower()
            save_report(region)

        elif choice == "4":
            print("\nGoodbye! Happy exploring. \U0001f30d\n")
            break
  • Also update the last line of the file. Find:
if __name__ == "__main__":
    main()
  • Replace main() with run_menu(), so it now looks like this:
if __name__ == "__main__":
    run_menu()

What does this code do?

The while True loop keeps the menu running indefinitely. Each iteration prints the four options and waits for the user to type a number.

The if/elif chain routes each choice to the appropriate function. When the user picks option 4, break exits the loop and ends the program.

  • Save explorer.py.
  • Run the script by running:
python3 explorer.py
  • You should see the menu appear. Test option 1 (search a country) and option 4 (quit). Skip option 3 for now because save_report does not exist yet.

Menu not appearing?

Make sure you deleted the old main() function completely. If both main() and run_menu() exist, Python will still call whichever one the guard references.

Check that the last line reads run_menu() and not main().

help me debug my menu loop

Write a save_report function

Option 3 in your menu calls save_report() which does not exist yet. This function will fetch region data, extract only the fields you need, sort by population, and write the result to a JSON file.

  • In explorer.py, add the following function above run_menu() (after display_region_summary):
def save_report(region):
    """Save a population report for a region as a JSON file."""
    countries = fetch_region(region)
    if countries is None:
        return

    # Build a clean list with only the fields we need
    report = []
    for country in countries:
        report.append({
            "name": country["name"]["common"],
            "capital": country["capital"][0] if country["capital"] else "N/A",
            "population": country["population"]
        })

    # Sort by population, largest first
    report.sort(key=lambda c: c["population"], reverse=True)

    # Write the sorted report to a JSON file
    filename = f"{region}_report.json"
    with open(filename, "w") as f:
        json.dump(report, f, indent=2)

    print(f"\nReport saved to {filename} ({len(report)} countries)\n")

What does this code do?

This function calls your existing fetch_region() to get the raw data, then builds a simplified list of dictionaries. Each dictionary keeps only name, capital, and population.

After sorting by population descending, json.dump() writes the list to a file with indent=2 for readability. The filename matches the region name, so browsing Europe produces europe_report.json.

  • Save explorer.py.
  • Run the script again:
python3 explorer.py
  • Choose option 3 and type europe when prompted.
  • You should see a confirmation message like Report saved to europe_report.json (53 countries).
  • Confirm the file was created by running:
ls
  • You should see europe_report.json listed alongside explorer.py.

Not seeing the JSON file?

Make sure save_report is defined above run_menu in your file. Python reads top to bottom, so functions must be defined before they are called.

Also check that import json is still at the top of your file. Without it, json.dump() will throw a NameError.

help me debug my save_report function

Add input validation and a graceful exit

Your menu works, but it has no guardrails. If a user types 5 or an empty string, nothing happens. If they type a misspelled region, the API returns an error. Good CLI tools guide users toward valid input.

  • In explorer.py, replace your entire run_menu() function with this updated version that includes validation:
def run_menu():
    """Run the interactive menu loop."""
    valid_regions = ["africa", "americas", "asia", "europe", "oceania"]

    print("\n\U0001f30d Country Data Explorer")
    print("========================\n")

    while True:
        print("[1] Search for a country")
        print("[2] Browse a region")
        print("[3] Save region report")
        print("[4] Quit")

        choice = input("\nYour choice: ").strip()

        if choice == "1":
            name = input("Enter country name: ").strip()
            # Don't call the API with an empty string
            if not name:
                print("Please enter a country name.\n")
                continue
            data = fetch_country(name)
            if data:
                display_country(data[0])

        elif choice == "2":
            # Show valid options before asking for input
            print(f"Available regions: {', '.join(valid_regions)}")
            region = input("Enter region: ").strip().lower()
            if region not in valid_regions:
                print(f"Invalid region. Choose from: {', '.join(valid_regions)}\n")
                continue
            countries = fetch_region(region)
            if countries:
                display_region_summary(countries)

        elif choice == "3":
            print(f"Available regions: {', '.join(valid_regions)}")
            region = input("Enter region to save report for: ").strip().lower()
            if region not in valid_regions:
                print(f"Invalid region. Choose from: {', '.join(valid_regions)}\n")
                continue
            save_report(region)

        elif choice == "4":
            print("\nGoodbye! Happy exploring. \U0001f30d\n")
            break

        else:
            # Catch any input that isn't 1-4
            print("Invalid choice. Please enter 1, 2, 3, or 4.\n")

What did the validation add?

The valid_regions list at the top defines the five regions the API recognizes. Before making an API call, the code checks whether the user's input is in that list. If not, it prints the valid options and continue restarts the loop.

The if not name check catches empty input on the country search. The else clause at the bottom handles any choice that is not 1 through 4. These small checks prevent confusing API errors from reaching the user.

  • Save explorer.py.
  • Run the script:
python3 explorer.py
  • Test the validation by typing 7 at the menu. You should see Invalid choice. Please enter 1, 2, 3, or 4. printed.
  • Choose option 2 and type mars as the region. You should see the list of valid regions printed.
  • Choose option 4 to confirm the goodbye message appears and the program exits.

Validation not catching bad input?

Make sure you replaced the ENTIRE run_menu function, including the first line def run_menu():. If parts of the old version remain below the new one, Python will use whichever definition comes last.

Check that continue is indented inside the if block, not at the while level. Incorrect indentation can skip the validation check entirely.

help me debug my input validation

✔️ Awesome, I've got everything!

Great. Double check you have saved explorer.py and that option 4 exits cleanly.

ⓧ I'd like to double check the full code

import requests
import json


def fetch_country(name):
    """Fetch country data by name from the REST Countries API."""
    url = f"https://restcountries.com/v3.1/name/{name}?fields=name,capital,population,region,languages"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            print(f"Country '{name}' not found. Check the spelling and try again.")
            return None
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Network error: could not reach the API. Details: {e}")
        return None


def display_country(country):
    """Display formatted information about a single country."""
    name = country["name"]["common"]
    capital = country["capital"][0] if country["capital"] else "N/A"
    population = country["population"]
    region = country["region"]
    languages = ", ".join(country["languages"].values()) if country["languages"] else "N/A"

    print(f"\n{'=' * 40}")
    print(f"  {name}")
    print(f"{'=' * 40}")
    print(f"  Capital:    {capital}")
    print(f"  Population: {population:,}")
    print(f"  Region:     {region}")
    print(f"  Languages:  {languages}")
    print(f"{'=' * 40}\n")


def fetch_region(region):
    """Fetch all countries in a given region."""
    url = f"https://restcountries.com/v3.1/region/{region}?fields=name,capital,population,languages"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 404:
            print(f"Region '{region}' not found.")
            return None
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Network error: could not reach the API. Details: {e}")
        return None


def display_region_summary(countries):
    """Display a sorted summary of countries in a region."""
    sorted_countries = sorted(countries, key=lambda c: c["population"], reverse=True)

    print(f"\n{'No.':<5}{'Country':<30}{'Population':>15}")
    print("-" * 50)

    for i, country in enumerate(sorted_countries, 1):
        name = country["name"]["common"]
        population = country["population"]
        print(f"{i:<5}{name:<30}{population:>15,}")

    print("-" * 50)
    print(f"Total countries: {len(sorted_countries)}\n")


def save_report(region):
    """Save a population report for a region as a JSON file."""
    countries = fetch_region(region)
    if countries is None:
        return

    report = []
    for country in countries:
        report.append({
            "name": country["name"]["common"],
            "capital": country["capital"][0] if country["capital"] else "N/A",
            "population": country["population"]
        })

    report.sort(key=lambda c: c["population"], reverse=True)

    filename = f"{region}_report.json"
    with open(filename, "w") as f:
        json.dump(report, f, indent=2)

    print(f"\nReport saved to {filename} ({len(report)} countries)\n")


def run_menu():
    """Run the interactive menu loop."""
    valid_regions = ["africa", "americas", "asia", "europe", "oceania"]

    print("\n\U0001f30d Country Data Explorer")
    print("========================\n")

    while True:
        print("[1] Search for a country")
        print("[2] Browse a region")
        print("[3] Save region report")
        print("[4] Quit")

        choice = input("\nYour choice: ").strip()

        if choice == "1":
            name = input("Enter country name: ").strip()
            if not name:
                print("Please enter a country name.\n")
                continue
            data = fetch_country(name)
            if data:
                display_country(data[0])

        elif choice == "2":
            print(f"Available regions: {', '.join(valid_regions)}")
            region = input("Enter region: ").strip().lower()
            if region not in valid_regions:
                print(f"Invalid region. Choose from: {', '.join(valid_regions)}\n")
                continue
            countries = fetch_region(region)
            if countries:
                display_region_summary(countries)

        elif choice == "3":
            print(f"Available regions: {', '.join(valid_regions)}")
            region = input("Enter region to save report for: ").strip().lower()
            if region not in valid_regions:
                print(f"Invalid region. Choose from: {', '.join(valid_regions)}\n")
                continue
            save_report(region)

        elif choice == "4":
            print("\nGoodbye! Happy exploring. \U0001f30d\n")
            break

        else:
            print("Invalid choice. Please enter 1, 2, 3, or 4.\n")


if __name__ == "__main__":
    run_menu()

Your Country Data Explorer is now a complete, interactive application. It searches, browses, saves files, validates input, and exits cleanly. Next up, you will extend it with a secret challenge that compares two countries side by side.

Secret mission

Add a "Compare Countries" Feature

Your explorer can look up one country at a time. But what if you want to see how two countries stack up against each other? Add a comparison feature that fetches two countries and displays them side-by-side in a formatted table.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and uses a free API, so there are no ongoing costs. Decide whether to keep your files for future reference or delete them entirely.

Resources you used:

  • The country-explorer folder (contains explorer.py and any generated JSON report files).
  • The requests Python package (installed via pip).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your Country Data Explorer or extend it further.

  • Your country-explorer folder stays on your Desktop.
  • The requests library remains installed and available for other Python projects.

Pause - I'll come back to this later

There are no running processes to stop. Your project is already paused since the script only runs when you execute it.

  • Your files will be waiting for you in the country-explorer folder on your Desktop whenever you want to return.

Delete - I don't want to use this again

Remove all project files and the installed package to start fresh.

  • Delete the project folder by running this command in your terminal:

macOS

rm -rf ~/Desktop/country-explorer

Windows

rmdir /s /q %USERPROFILE%\Desktop\country-explorer
  • Uninstall the requests library by running:
pip3 uninstall requests
  • Type y when prompted to confirm the uninstall.

Keep requests if you use Python regularly

The requests library is used by many Python projects. If you plan to do more Python work, you may want to leave it installed.

That's a Wrap!

That's a Wrap!

Nice work! You just built a real Python application from scratch. Not a tutorial script. A structured application with functions, error handling, and live API integration.

You've learned how to:

  • Connected to a live REST API using the requests library, parsed nested JSON responses, and handled network errors gracefully with try/except.
  • Decomposed a real problem into 6+ focused, reusable functions that each handle one responsibility.
  • Built an interactive menu system with input validation, sorted data processing, and JSON file output.
  • Secret Mission: Compared data from multiple API calls and displayed formatted side-by-side output.

Ready to quiz yourself?