1 The Problem
We want a command-line tool that takes a city name, asks a weather API over the internet, and shows the temperature and conditions. It teaches the single most important real-world skill beyond the basics: calling a web API — sending a request and parsing the JSON response.
2 How to Think About It
Think about the request-response cycle, before any code:
3 The Build — explained part by part
Here is the complete weather tool. Each part is explained below. (It uses urllib, which is built into Python — no install needed.)
import json
import urllib.request
import urllib.parse
def get_weather(city):
# Open-Meteo's free geocoding + forecast (no API key needed).
geo_url = "https://geocoding-api.open-meteo.com/v1/search?" + \
urllib.parse.urlencode({"name": city, "count": 1})
with urllib.request.urlopen(geo_url) as response:
geo = json.load(response)
if not geo.get("results"):
return None
place = geo["results"][0]
lat, lon = place["latitude"], place["longitude"]
forecast_url = "https://api.open-meteo.com/v1/forecast?" + \
urllib.parse.urlencode({"latitude": lat, "longitude": lon,
"current_weather": "true"})
with urllib.request.urlopen(forecast_url) as response:
data = json.load(response)
return data["current_weather"]
if __name__ == "__main__":
city = input("City: ")
weather = get_weather(city)
if weather:
print(f"Temperature: {weather['temperature']}C")
print(f"Wind: {weather['windspeed']} km/h")
else:
print("City not found.")
requests library is nicer, but urllib needs no install, so we start here.)urllib.parse.urlencode({...}) — safely turn our parameters (the city name) into the
?name=London&count=1 part of a URL, escaping spaces and special characters.urllib.request.urlopen(url) — send the HTTP request and get the response. json.load(response) parses the JSON reply into Python dictionaries and lists.
two requests — first we look up the city’s latitude/longitude (geocoding), then we fetch the forecast for those coordinates. Chaining API calls like this is very common.
data["current_weather"] — reach into the parsed JSON to pull out the part we want, then read
temperature and windspeed from it.
urllib.parse.urlencode to escape parameters safely.if not geo.get("results") and handle the missing case.4 Test & Prove Each Part
Network calls are unpredictable, so we test the parsing logic against a saved sample response — not the live API.
def parse_current(data):
"""Extract the current weather block from a forecast response."""
return data.get("current_weather")
def parse_place(geo):
"""Extract lat/lon from a geocoding response, or None."""
if not geo.get("results"):
return None
place = geo["results"][0]
return place["latitude"], place["longitude"]
def test_parse_temperature():
"""Temperature is read from the sample response."""
sample = {"current_weather": {"temperature": 18.5, "windspeed": 10}}
assert parse_current(sample)["temperature"] == 18.5
def test_missing_city():
"""An empty geocoding result returns None."""
assert parse_place({"results": []}) is None
def test_coordinates():
"""Lat and lon are read from geo data."""
geo = {"results": [{"latitude": 51.5, "longitude": -0.1}]}
assert parse_place(geo) == (51.5, -0.1)
Run with pytest -v. We never call the real API in tests — networks are slow and unreliable. Instead we test parsing against saved sample data. This is exactly how professionals test API code.
5 The Interface
What it expects
City: LondonWhat it returns
Temperature: 14.2C
Wind: 11 km/h6 Run It & Automate It
python3 weather.pyType a city to see its live weather. Needs an internet connection. Uses the free Open-Meteo API (no key required).
Jenkins runs the tests automatically — each line explained below.
City: London
Temperature: 14.2C
Wind: 11 km/hURLError or a timeout.KeyError: 'current_weather'.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Use
requests. Install the friendlierrequestslibrary and simplify the code. (Teaches: third-party packages.) - Forecast. Show the next few days, not just now. (Teaches: reading more of the JSON.)
- Cache results. Save recent lookups to avoid repeat calls. (Teaches: caching.)