Skip to content

Simple Weather Forecast App

Abstract

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"]data["weather"][0]["description"]) without crashing.
  • Why you check status_codestatus_code and handle the unhappy paths first.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • The requestsrequests library: pip install requestspip install requests.
  • A free OpenWeatherMap API key from openweathermap.org/api.
  • Tkinter (bundled with Python).

Getting Started

Get an API key

  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.

Create the project

  1. Create a folder named weather-appweather-app.
  2. Inside it, create simple_weather_forecast_app.pysimple_weather_forecast_app.py.
  3. Install the dependency: pip install requestspip install requests.

Write the code

simple_weather_forecast_app.pySource
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()
 
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()
 

Run it

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.
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.

Step-by-Step Explanation

1. The endpoint and key

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

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

2. Building the request

simple_weather_forecast_app.py
params = {"q": city, "appid": API_KEY, "units": "metric"}
response = requests.get(BASE_URL, params=params)
data = response.json()
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 paramsparams dict lets requestsrequests build the query string for you (?q=London&appid=...&units=metric?q=London&appid=...&units=metric) and handles URL-encoding spaces and special characters. units="metric"units="metric" gives Celsius; "imperial""imperial" gives Fahrenheit.

3. Check status before parsing

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."))
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 404404 with a helpful messagemessage field — surface it instead of crashing on a missing key.

4. Reading nested JSON

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

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

Protect Your API Key

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
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"
command
# PowerShell
$env:OWM_API_KEY = "your_key_here"

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

Add a 5-Day Forecast

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"]]
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.

Show Weather Icons

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
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

Add a °C / °F Toggle

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

Common Mistakes

ProblemCauseFix
401 Unauthorized401 UnauthorizedKey missing, wrong, or not yet activeCheck the key; wait a few minutes after signup
KeyError: 'main'KeyError: 'main'Parsed JSON before checking statusGuard with if status_code == 200if status_code == 200 first
App freezes on slow networkNo timeouttimeoutAdd timeout=10timeout=10; fetch on a background thread
Spaces in city name break URLManual string concatenationUse the params=params= dict — it encodes for you
Wrong temperature scaleForgot unitsunitsSet units="metric"units="metric" or "imperial""imperial"
Rate-limit errorsPolling too oftenCache responses for a few minutes

Variations to Try

  1. Geolocation — detect the user’s city via their IP and load it on launch.
  2. Hourly chart — plot the next 24 hours with matplotlibmatplotlib.
  3. Favorites — save a list of cities and refresh them all at once.
  4. Severe-weather alerts — use the One Call API’s alertsalerts 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).

Real-World Applications

  • 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.

Educational Value

  • REST APIs — keys, query parameters, JSON responses.
  • Defensive parsing — status checks and .get().get() with defaults.
  • Secret management — environment variables over hard-coding.
  • Caching & rate limits — being a good API citizen.

Next Steps

  • 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.

Conclusion

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.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did