← Data detective

Ask an API

Ask a real weather service for the week's forecast, read the JSON it sends back, and fall back to a saved copy when there is no connection.

Weather apps do not measure the weather themselves: they ask an API. Your program can ask the same one.

Optional. This lesson needs the internet: if your school blocks the weather site, the program uses a saved copy instead, and everything still works. An API sends data to programs. Open-Meteo is a free weather API: you send an address with a place's latitude and longitude, and it replies with a forecast in JSON, which Python reads as dictionaries and lists. requests.get(...) asks; .json() turns the reply into Python. The saved copy is read with json.load(f), which does the same for a JSON file.

PredictRead the code and say what it will do before you run anything. A wrong guess is fine: it shows you what to look for.

Predict

weather.py
reply = {"daily": {"time": ["Mon", "Tue"], "temperature_2m_max": [18.5, 22.1]}}
print(reply["daily"]["temperature_2m_max"][1])

A dictionary inside a dictionary, with a list inside that. Follow the square brackets from left to right.

What does it print?

RunRun it and compare the result with your prediction.

Run

Run it. The first line says whether the forecast is live or the saved copy. Then change the latitude and longitude to your own town (look them up on a map; south of the equator, latitude is negative) and run it again.

weather.py
import json
import requests

URL = "https://api.open-meteo.com/v1/forecast"
canberra = {"latitude": -35.28, "longitude": 149.13, "timezone": "Australia/Sydney", "daily": "temperature_2m_max,temperature_2m_min"}

try:
    reply = requests.get(URL, params=canberra, timeout=10).json()
    print("Live forecast from Open-Meteo")
except requests.RequestException:
    with open("weather.json") as f:
        reply = json.load(f)
    print("No connection: using the saved forecast")

days = reply["daily"]
for i in range(len(days["time"])):
    print(days["time"][i] + ": " + str(days["temperature_2m_min"][i]) + " to " + str(days["temperature_2m_max"][i]) + " degrees")
print("Weather data by Open-Meteo.com (CC BY 4.0)")
Try it yourselfYour edits are saved in this browser.

InvestigateTrace how each line of code connects to what you see.

Investigate

weather.py
import json
import requests

URL = "https://api.open-meteo.com/v1/forecast"
canberra = {"latitude": -35.28, "longitude": 149.13, "timezone": "Australia/Sydney", "daily": "temperature_2m_max,temperature_2m_min"}

try:
    reply = requests.get(URL, params=canberra, timeout=10).json()
    print("Live forecast from Open-Meteo")
except requests.RequestException:
    with open("weather.json") as f:
        reply = json.load(f)
    print("No connection: using the saved forecast")

days = reply["daily"]
for i in range(len(days["time"])):
    print(days["time"][i] + ": " + str(days["temperature_2m_min"][i]) + " to " + str(days["temperature_2m_max"][i]) + " degrees")
print("Weather data by Open-Meteo.com (CC BY 4.0)")

What params builds.

Why there is a saved copy.

A level that was skipped.

What does params=canberra do?

Why does the program keep a saved weather.json?

This should print each day's top temperature, but it stops with KeyError: 'temperature_2m_max'. Click the line that is wrong.

ModifyChange the code on purpose and check the result.

Modify

Find the hottest day of the week, and print it with its temperature.

Need a hint?

The biggest-so-far pattern from Lesson 2: hottest = 0 (the first day's place in the list), then replace it when a day's top temperature is higher.

Ask the API for precipitation_sum (rain in mm) as well, and show it on rainy days. The saved copy has it already.

Need a hint?

Add ,precipitation_sum to the "daily" text, then print days["precipitation_sum"][i] on days with rain.

Try it yourselfYour edits are saved in this browser.

Find and print the hottest day of the week.

    Run your program. This ticks when your code does all of the above.

    Ask for the rain too, and show it on the days it rains.

      Run your program. This ticks when your code does all of the above.

      MakeBuild something new using what you have learned. Expect a few tries; that is normal.

      Two places

      Compare the week ahead in two places of your choice: which is warmer on average, and by how much? Catch a failed request so the program says so instead of crashing. (For a bigger challenge, put it behind a Gradio app with a dropdown of places.)

      Try it yourselfYour edits are saved in this browser.

      Compare the week ahead in two places, handling a failed request.

        Run your program. This ticks when your code does all of the above.

        Info

        Where this leads

        The same requests code works in Python on any computer. Most big APIs (maps, music, sport) need a key and an account; Open-Meteo is one of the few that do not.

        Open-Meteo is free for non-commercial use if you credit it. Why does the program print a credit line?

        For teachers

        An optional lesson. Students ask the Open-Meteo weather API for a week's forecast with requests, read the nested JSON, fall back to a saved copy when there is no connection, then find the hottest day, add rain and compare two places. About 45 minutes.

        Open the teacher guide: answers, solutions, slides and a worksheet.

        Students will be able to

        Before class

        Optional: not needed for completing the course. It needs api.open-meteo.com to be reachable from school devices (the Check your school page tests it); if it is blocked, except requests.RequestException: loads weather.json, an example reply in Open-Meteo's format (made up for these lessons, not a real forecast), so the lesson still works. Open-Meteo is free for non-commercial use with credit (CC BY 4.0), needs no key, and allows requests from web pages. In the playground, requests is a small stand-in (get, params, .json(), .status_code, .ok, .raise_for_status()) so the same code runs with the real requests on a computer. The reply is a dictionary; reply["daily"] holds lists (time, temperature_2m_max, ...) where place i in each list is the same day (the bug task skips the daily level). params= builds the query string, the other side of Flask's request.args.

        Running the lesson

        1. Predict: draw the nested reply as boxes inside boxes.
        2. Run: find your school's latitude and longitude on a map together.
        3. Investigate: turn off the network (or use a wrong address) and run: the saved copy takes over.
        4. Info: discuss data licences and API keys.

        The hard part (on purpose)

        Reading nested JSON. Ask students to print reply, then reply["daily"], one level at a time, instead of telling them the path.

        When students get stuck

        Stretch and support

        Stretch: Put the two-place comparison behind a Gradio app with a dropdown of places, or ask Open-Meteo for hourly temperatures and chart today with text bars.

        Support: Give students the hottest-day loop with the comparison line blank, and ask them to finish it.

        Where it leads

        Machine learning with Gradio uses data in the same way: tables in, answers out. Web apps with Flask builds the other side: an API of your own.

        What each step is for

        Now you can
        You reached the endBack to Data detective