Weather CLI 🌦️
Your first tool that reaches out to the internet and comes back with something useful: the weather, from a real API, for a city you name on the command line. It ties together HTTP, JSON, argument parsing and secret handling into one genuinely handy program.
📋 Build this
- Take a city name as a command-line argument.
- Fetch current weather from a free API (Open-Meteo needs no key).
- Print temperature and conditions in a clean, readable line.
- Handle a city that is not found, and a network failure, without a stack trace.
Hints, if you want them
Try the spec cold first. Open a hint only when you are properly stuck; the struggle is where the learning is.
Hint 1: Parsing the argument
argparse gives you city as a positional argument plus a free --help (Lesson 27). Structure the code so main(argv=None) is testable.Hint 2: Calling the API
requests.get(url, params={...}, timeout=10), then response.raise_for_status() and response.json() (Lesson 42). Open-Meteo has a geocoding endpoint to turn a city name into coordinates.Hint 3: Failing gracefully
Catch
requests.RequestException for network problems and check whether the geocoding result is empty for an unknown city. Print a friendly message and return a non-zero exit code (Lesson 27).The reference solution
Yours does not need to match this. There are many good ways to build any of these. Compare only after you have your own working.
Reveal the reference solution
#!/usr/bin/env python3
"""weather: current conditions for a city, from Open-Meteo (no key needed)."""
import argparse
import sys
import requests
GEO = "https://geocoding-api.open-meteo.com/v1/search"
FORECAST = "https://api.open-meteo.com/v1/forecast"
def find_city(name):
"""Turn a city name into (lat, lon, label), or None if not found."""
resp = requests.get(GEO, params={"name": name, "count": 1}, timeout=10)
resp.raise_for_status()
results = resp.json().get("results")
if not results:
return None
hit = results[0]
return hit["latitude"], hit["longitude"], f"{hit['name']}, {hit['country']}"
def current_weather(lat, lon):
resp = requests.get(FORECAST, params={
"latitude": lat, "longitude": lon, "current_weather": True,
}, timeout=10)
resp.raise_for_status()
return resp.json()["current_weather"]
def main(argv=None):
parser = argparse.ArgumentParser(description="Current weather for a city.")
parser.add_argument("city", help="the city to look up")
args = parser.parse_args(argv)
try:
located = find_city(args.city)
if located is None:
print(f"weather: no city called {args.city!r}", file=sys.stderr)
return 1
lat, lon, label = located
weather = current_weather(lat, lon)
except requests.RequestException as err:
print(f"weather: network error: {err}", file=sys.stderr)
return 1
print(f"{label}: {weather['temperature']}C, wind {weather['windspeed']} km/h")
return 0
if __name__ == "__main__":
sys.exit(main())This one needs the network or a live secret, so it has no run button. Build it on your own machine.
Stretch goals
- Add a --forecast flag for the next few days.
- Cache results for a few minutes so repeated calls do not hit the API (Lesson 42).
- Package it with a console command so you can type `weather London` (Lesson 50).