Skip to content

Simple Weather Forecast App

Consuming a real web API is a rite of passage, and weather is the perfect first API: free, well-documented, and instantly gratifying. In this tutorial you build a Tkinter app that queries the OpenWeatherMap API for a city’s current conditions and displays temperature, “feels like”, humidity, and a description. Then you grow it into something polished — a 5-day forecast, weather icons, °C/°F toggle, geolocation, response caching, and the kind of error handling that separates a demo from a tool.

You will leave understanding:

  • How to register for and use an API key safely.
  • The request → JSON → parse → display pipeline behind every API client.
  • How to read nested JSON (data["weather"][0]["description"]) without crashing.
  • Why you check status_code and handle the unhappy paths first.
  • Python 3.6 or above.
  • A text editor or IDE.
  • The requests library: pip install requests.
  • A free OpenWeatherMap API key from openweathermap.org/api.
  • Tkinter (bundled with Python).
  1. Sign up at openweathermap.org (free tier is plenty).
  2. Copy your API key from the dashboard.
  3. New keys can take a few minutes to activate — don’t panic if the first call 401s.
  1. Create a folder named weather-app.
  2. Inside it, create simple_weather_forecast_app.py.
  3. Install the dependency: pip install requests.
simple_weather_forecast_app.py pch.viewSource
simple_weather_forecast_app.py
"""
Simple Weather Forecast App

A Python application that fetches and displays weather forecast data. Features include:
- Fetching weather data from an API.
- Displaying the forecast in a user-friendly format.
"""

import requests
from tkinter import Tk, Label, Entry, Button, messagebox

API_KEY = "your_openweathermap_api_key"
BASE_URL = "http://api.openweathermap.org/data/2.5/weather"


class WeatherForecastApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Simple Weather Forecast App")

        Label(root, text="Enter City Name:").grid(row=0, column=0, padx=10, pady=10)
        self.city_entry = Entry(root, width=30)
        self.city_entry.grid(row=0, column=1, padx=10, pady=10)

        Button(root, text="Get Weather", command=self.get_weather).grid(row=1, column=0, columnspan=2, pady=10)

        self.result_label = Label(root, text="", wraplength=400, justify="left")
        self.result_label.grid(row=2, column=0, columnspan=2, padx=10, pady=10)

    def get_weather(self):
        """Fetch weather data for the entered city."""
        city = self.city_entry.get()
        if not city:
            messagebox.showerror("Error", "Please enter a city name.")
            return

        params = {"q": city, "appid": API_KEY, "units": "metric"}
        try:
            response = requests.get(BASE_URL, params=params)
            data = response.json()

            if response.status_code == 200:
                weather = data["weather"][0]["description"].capitalize()
                temp = data["main"]["temp"]
                feels_like = data["main"]["feels_like"]
                humidity = data["main"]["humidity"]

                result = (
                    f"Weather in {city}:\n"
                    f"Condition: {weather}\n"
                    f"Temperature: {temp}°C\n"
                    f"Feels Like: {feels_like}°C\n"
                    f"Humidity: {humidity}%"
                )
                self.result_label.config(text=result)
            else:
                messagebox.showerror("Error", data.get("message", "Failed to fetch weather data."))
        except Exception as e:
            messagebox.showerror("Error", f"An error occurred: {e}")


def main():
    root = Tk()
    app = WeatherForecastApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()
command
C:\Users\Your Name\weather-app> python simple_weather_forecast_app.py
# Enter a city name, click "Get Weather".
# Make sure you replaced API_KEY with your real key first.

Read from the top: this is what runs when you execute the file, and which function calls which. It is generated from the code, so it cannot drift from it.

diagram Diagram mermaid
simple_weather_forecast_app.py
API_KEY = "your_openweathermap_api_key"
BASE_URL = "http://api.openweathermap.org/data/2.5/weather"

BASE_URL is the “current weather” endpoint. The key authenticates you and counts against your rate limit.

simple_weather_forecast_app.py
params = {"q": city, "appid": API_KEY, "units": "metric"}
response = requests.get(BASE_URL, params=params)
data = response.json()

Passing a params dict lets requests build the query string for you (?q=London&appid=...&units=metric) and handles URL-encoding spaces and special characters. units="metric" gives Celsius; "imperial" gives Fahrenheit.

simple_weather_forecast_app.py
if response.status_code == 200:
    weather = data["weather"][0]["description"].capitalize()
    temp = data["main"]["temp"]
    ...
else:
    messagebox.showerror("Error", data.get("message", "Failed to fetch weather data."))

This is the most important habit in API work: handle the failure path first. A typo’d city returns 404 with a helpful message field — surface it instead of crashing on a missing key.

simple_weather_forecast_app.py
data["weather"][0]["description"]   # list → first item → field
data["main"]["temp"]                # nested dict

OpenWeatherMap nests weather inside a list (a place can have several conditions) and groups numbers under main. Knowing the shape of the JSON is half the battle — print data once and study it.

Never hard-code secrets. Use an environment variable:

config.py
import os
API_KEY = os.environ["OWM_API_KEY"]   # set OWM_API_KEY in your shell, not in code
command
# PowerShell
$env:OWM_API_KEY = "your_key_here"

This keeps the key out of screenshots, git history, and shared code.

The forecast endpoint returns data in 3-hour steps:

forecast.py
FORECAST_URL = "http://api.openweathermap.org/data/2.5/forecast"
 
def five_day(city):
    r = requests.get(FORECAST_URL,
                     params={"q": city, "appid": API_KEY, "units": "metric"},
                     timeout=10)
    r.raise_for_status()
    # Pick the midday (12:00) reading for each day
    return [item for item in r.json()["list"] if "12:00:00" in item["dt_txt"]]

Loop the result into five labels or a small table.

Each condition carries an icon code:

icons.py
from tkinter import PhotoImage
import requests, io
 
icon_code = data["weather"][0]["icon"]                       # e.g. "10d"
url = f"http://openweathermap.org/img/wn/{icon_code}@2x.png"
img_bytes = requests.get(url, timeout=10).content
# Save then load, or use Pillow's ImageTk for in-memory display

Store the raw value once, convert for display:

units.py
def to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32
 
label.config(text=f"{temp}°C" if self.metric else f"{to_fahrenheit(temp):.1f}°F")
ProblemCauseFix
401 UnauthorizedKey missing, wrong, or not yet activeCheck the key; wait a few minutes after signup
KeyError: 'main'Parsed JSON before checking statusGuard with if status_code == 200 first
App freezes on slow networkNo timeoutAdd timeout=10; fetch on a background thread
Spaces in city name break URLManual string concatenationUse the params= dict — it encodes for you
Wrong temperature scaleForgot unitsSet units="metric" or "imperial"
Rate-limit errorsPolling too oftenCache responses for a few minutes
  1. Geolocation — detect the user’s city via their IP and load it on launch.
  2. Hourly chart — plot the next 24 hours with matplotlib.
  3. Favorites — save a list of cities and refresh them all at once.
  4. Severe-weather alerts — use the One Call API’s alerts field.
  5. Background by condition — change the window color for sunny/rainy/snow.
  6. Caching layer — store responses with a timestamp; skip the API within 10 minutes.
  7. Voice output — read the forecast aloud (see Weather App with Voice Commands).
  • Travel apps — destination weather at a glance.
  • Agriculture & logistics — planning around conditions.
  • Dashboards — a weather widget on a home-automation panel.
  • Event planning — outdoor-event go/no-go decisions.
  • REST APIs — keys, query parameters, JSON responses.
  • Defensive parsing — status checks and .get() with defaults.
  • Secret management — environment variables over hard-coding.
  • Caching & rate limits — being a good API citizen.
  • Move the key into an environment variable.
  • Add a 5-day forecast and weather icons.
  • Implement a °C/°F toggle and response caching.
  • Detect the user’s city with geolocation.

You built a weather app that turns a city name into live conditions, then layered on a forecast, icons, unit toggles, and caching — all on top of the same request → parse → display loop you’ll reuse for every API you ever touch. The skill transfers directly: swap the endpoint and you have a stock ticker, a currency converter, or a news reader. Full source on GitHub. Explore more API projects on Python Central Hub.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading