Build a Python Weather Report by City App
Learn how to create a Python-based Weather Report by City app—first with a one-liner using wttr.in, then with a full-featured solution using OpenStreetMap’s Nominatim geocoding and the Open-Meteo API. Step-by-step code breakdown, error handling, and best practices included.
Introduction
Whether you’re building a CLI tool for quick forecasts or a full-blown weather dashboard, fetching and displaying weather by city is a classic Python project. In this post, you’ll first see a minimal one-liner solution using wttr.in, then dive into a robust implementation leveraging:
Nominatim (OpenStreetMap’s geocoding service)
Open-Meteo API for current weather data
Python’s
requests
library for HTTP calls
By the end, you’ll understand each section of the code and best practices for error handling, user-agent headers, and mapping weather codes to human-friendly descriptions.
Prerequisites
Make sure you have:
Python 3.7+ installed
The requests library:
pip install requests
A basic understanding of HTTP requests and JSON parsing
(Optional) An IDE or text editor like VS Code for syntax highlighting
Quick & Simple: wttr.in One-Liner
For an ultra-lightweight solution, you can use wttr.in—a free weather service for the terminal:
import requests
city = input(“City: “)
res = requests.get(f”http://wttr.in/{city}?format=3″)
print(res.text)
How it works:
Prompts the user for a city name.
Sends a GET request to wttr.in with
?format=3
(returns “City: +temp +condition”).Prints the concise result.
Pros: No API key required; one-liner.
Cons: Limited formatting, no fine-grained control, depends on an external service’s uptime.
The Detailed Approach
For production-grade projects, you’ll often need more control, reliability, and clarity. Let’s explore a two-step version:
1. Geocoding with Nominatim
def get_weather(city):
geocode_url = f”https://nominatim.openstreetmap.org/search?city={city}&format=json”
headers = {
‘User-Agent’: ‘WeatherApp (contact@example.com)’
}
response = requests.get(geocode_url, headers=headers)
location_data = response.json()
# Validate the response
if not location_data:
print(f”Error: City ‘{city}’ not found.”)
return
latitude = location_data[0][‘lat’]
longitude = location_data[0][‘lon’]
…
Why geocode? Most weather APIs require latitude & longitude.
User-Agent header: Nominatim requires a descriptive User-Agent to identify your application (OSM Usage Policy).
JSON parsing & validation: Always check for empty lists or malformed JSON to avoid crashes.
2. Fetching Weather Data from Open-Meteo
weather_url = (
f”https://api.open-meteo.com/v1/forecast?”
f”latitude={latitude}&longitude={longitude}¤t=temperature_2m,weathercode”
)
weather_response = requests.get(weather_url)
weather_data = weather_response.json()
if ‘current’ in weather_data:
current = weather_data[‘current’]
temperature = current[‘temperature_2m’]
code = current[‘weathercode’]
description = get_weather_description(code)
print(f”Weather in {city.title()}:”)
print(f”Temperature: {temperature}°C”)
print(f”Condition: {description}”)
else:
print(f”Error: Weather data unavailable for ‘{city}’.”)
Open-Meteo API: Free, no API key required for basic usage.
Current weather: We request only
temperature_2m
andweathercode
to minimize payload.Data validation: Ensure the
current
key exists before accessing nested fields.
3. Mapping Weather Codes to Descriptions
def get_weather_description(code):
weather_descriptions = {
0: “Clear sky”,
1: “Mainly clear”,
2: “Partly cloudy”,
3: “Overcast”,
45: “Fog”,
# … include all codes up to 99 …
99: “Thunderstorm with heavy hail”
}
return weather_descriptions.get(code, “Unknown condition”)
Why map codes? Weather APIs use numeric codes for conditions; mapping makes output human-friendly.
Extensibility: You can extend this dictionary to include icons, humidity, wind speed, etc.
4. Error Handling & User Feedback
JSON decode errors: Wrap
response.json()
in a try/except to catch invalid JSON.Network issues: Consider handling
requests.exceptions.RequestException
for timeouts or connection errors.Invalid input: Prompt the user to re-enter if the city name yields no geocoding result.
Putting It All Together
import requests
def get_weather(city):
# Step 1: Geocode city → lat/lon
geocode_url = f”https://nominatim.openstreetmap.org/search?city={city}&format=json”
headers = {‘User-Agent’: ‘WeatherApp (contact@example.com)’}
try:
location_data = requests.get(geocode_url, headers=headers).json()
except requests.exceptions.JSONDecodeError:
print(“Error: Could not parse location data.”)
return
if not location_data:
print(f”Error: City ‘{city}’ not found.”)
return
latitude, longitude = location_data[0][‘lat’], location_data[0][‘lon’]
# Step 2: Fetch current weather
weather_url = (
f”https://api.open-meteo.com/v1/forecast?”
f”latitude={latitude}&longitude={longitude}¤t=temperature_2m,weathercode”
)
weather_data = requests.get(weather_url).json()
if ‘current’ not in weather_data:
print(f”Error: No weather data for ‘{city}’.”)
return
current = weather_data[‘current’]
temp = current[‘temperature_2m’]
code = current[‘weathercode’]
desc = get_weather_description(code)
# Step 3: Display
print(f”\nWeather in {city.title()}:”)
print(f”🌡️ Temperature: {temp}°C”)
print(f”☁️ Condition: {desc}”)
def get_weather_description(code):
weather_descriptions = {
# … (same as previous mapping) …
}
return weather_descriptions.get(code, “Unknown condition”)
if __name__ == “__main__”:
city = input(“Enter city name: “)
get_weather(city)
Conclusion
You now have two approaches to fetch weather by city in Python:
A one-line wttr.in solution for quick CLI checks.
A detailed implementation using Nominatim geocoding and the Open-Meteo API for production-grade apps.
Experiment by extending the mapping dictionary, adding error logging, or integrating with a GUI/web framework like Flask or Tkinter. Happy coding!
💬 Got questions? Drop a comment or reach out!