Skip to main content
Python

How to Build a Python Weather Report by City App: Simple & Detailed Guide

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…

G

gs_admin

Author & Reviewer

Published

May 13, 2025

Read Time

2 min read

article.txt
📰
Python

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.

Build a Python Weather Report by City app
Build a Python Weather Report by City app

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

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 like VS Code for syntax highlighting

Quick & Simple: the wttr.in One-Liner

For an ultra-lightweight solution, you can use wttr.in — a free weather service for the terminal:

python
12345
import requests

city = input("City: ")
res = requests.get(f"http://wttr.in/{city}?format=3")
print(res.text)

How it works: it prompts for a city, sends a GET request to wttr.in with ?format=3 (returns "City: +temp +condition"), and 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

python
1234567891011121314
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']
    # ...

Most weather APIs require latitude & longitude, so we geocode first. Nominatim requires a descriptive User-Agent to identify your application, and you should always validate the JSON to avoid crashes on empty results.

2. Fetching Weather Data from Open-Meteo

python
1234567891011121314151617
weather_url = (
    f"https://api.open-meteo.com/v1/forecast?"
    f"latitude={latitude}&longitude={longitude}&current=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 is free with no API key for basic usage. We request only temperature_2m and weathercode to minimize the payload, and validate that the current key exists before accessing nested fields.

3. Mapping Weather Codes to Descriptions

python
1234567891011
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")

Weather APIs use numeric codes for conditions; mapping makes the output human-friendly. You can extend this dictionary to include icons, humidity, wind speed, and more.

4. Error Handling & User Feedback

  • JSON decode errors: wrap response.json() in a try/except.
  • Network issues: handle 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

python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
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}&current=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 mapping as above) ...
    }
    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, and 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!

G

About the Author: gs_admin

A senior technical contributor specializing in architectural designs, software optimization, database structures, and developer education. Passionate about writing clean code and sharing engineering knowledge.