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.
Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.
Predict and Run 10 min · Investigate 10 min · Modify 15 min · Make 10 min
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.
Reading nested JSON. Ask students to print reply, then reply["daily"], one level at a time, instead of telling them the path.
| What you will see | A question to ask | The fix |
|---|---|---|
| KeyError: 'temperature_2m_max'. | Which dictionary holds the temperatures? | reply["daily"]["temperature_2m_max"]. |
| The forecast is for the wrong place. | Is latitude negative south of the equator? | Canberra is latitude -35.28, longitude 149.13. |
| No connection: using the saved forecast, every time. | Does the Check your school page reach Open-Meteo on this device? | The site is blocked here; the saved copy is fine for the lesson, or ask IT to allow api.open-meteo.com. |
One possible solution for each task. Other correct programs are fine.
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")
hottest = 0
for i in range(len(days["time"])):
if days["temperature_2m_max"][i] > days["temperature_2m_max"][hottest]:
hottest = i
print("Hottest: " + days["time"][hottest] + " at " + str(days["temperature_2m_max"][hottest]) + " degrees")
print("Weather data by Open-Meteo.com (CC BY 4.0)")
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,precipitation_sum"}
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"])):
line = days["time"][i] + ": " + str(days["temperature_2m_min"][i]) + " to " + str(days["temperature_2m_max"][i]) + " degrees"
if days["precipitation_sum"][i] > 0:
line = line + ", rain " + str(days["precipitation_sum"][i]) + " mm"
print(line)
print("Weather data by Open-Meteo.com (CC BY 4.0)")
import json
import requests
URL = "https://api.open-meteo.com/v1/forecast"
places = {
"Canberra": {"latitude": -35.28, "longitude": 149.13},
"Darwin": {"latitude": -12.46, "longitude": 130.84},
}
averages = {}
for name in places:
query = {"latitude": places[name]["latitude"], "longitude": places[name]["longitude"], "daily": "temperature_2m_max", "timezone": "auto"}
try:
days = requests.get(URL, params=query, timeout=10).json()["daily"]
total = 0
for t in days["temperature_2m_max"]:
total = total + t
averages[name] = total / len(days["temperature_2m_max"])
print(name + ": " + str(round(averages[name], 1)) + " degrees on average")
except requests.RequestException:
print("Could not reach Open-Meteo for " + name)
if len(averages) == 2:
difference = averages["Darwin"] - averages["Canberra"]
print("Darwin is " + str(round(difference, 1)) + " degrees warmer on average")
print("Weather data by Open-Meteo.com (CC BY 4.0)")
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.
Why does a program that uses an API need a plan for when the API cannot be reached?
Networks, filters and services fail; catching the error and using saved data (or saying so) keeps the program working instead of crashing.
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.