Python  

Build a Weather App in Python Using the OpenWeatherMap API

Fetching live weather data is a practical real-world task, and building a Weather App helps you learn API integration, JSON handling, error management, and optionally web development. In this project, we will build:

  • A command-line (CLI) Weather App in Python
  • An optional Flask-based web interface to display weather for a city
  • Support for units (metric/imperial), error handling, and basic caching

Prerequisites

  • Python 3.7+ installed
  • Basic knowledge of Python (functions, modules, I/O)
  • Internet access
  • requests library (install using pip install requests)
  • Optional: Flask (install using pip install flask)

Step 1. Get an API Key from OpenWeatherMap

  1. Register (free) at openweathermap.org
  2. Navigate to the API section and generate an API key (called appid)
  3. Note: Free tier allows reasonable usage for learning; be mindful of rate limits

Store your API key securely (environment variable recommended).

Example for Linux/macOS:

export OWM_API_KEY="your_api_key_here"

Example for Windows (PowerShell):

$env:OWM_API_KEY="your_api_key_here"

Step 2. CLI Weather App (weather_cli.py)

import os
import requests
from datetime import datetime

API_KEY = os.getenv("OWM_API_KEY")
BASE_URL = "https://api.openweathermap.org/data/2.5/weather"

def get_weather(city: str, units: str = "metric"):
    if not API_KEY:
        raise RuntimeError("OpenWeatherMap API key not found. Set OWM_API_KEY environment variable.")

    params = {
        "q": city,
        "appid": API_KEY,
    }
    if units in ("metric", "imperial"):
        params["units"] = units

    try:
        response = requests.get(BASE_URL, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()
    except requests.RequestException as e:
        print(f"Network or request error: {e}")
        return None

    if data.get("cod") != 200:
        print(f"Error from API: {data.get('message', 'Unknown error')}")
        return None

    return data

def format_and_print(data, units="metric"):
    name = data["name"]
    country = data["sys"]["country"]
    weather_desc = data["weather"][0]["description"].title()
    temp = data["main"]["temp"]
    feels_like = data["main"]["feels_like"]
    humidity = data["main"]["humidity"]
    wind_speed = data["wind"]["speed"]
    timestamp = datetime.fromtimestamp(data["dt"]).strftime("%Y-%m-%d %H:%M:%S")

    unit_symbol = "°C" if units == "metric" else "°F" if units == "imperial" else "K"
    speed_unit = "m/s" if units in ("metric", "") else "mph"

    print(f"\nWeather in {name}, {country} at {timestamp}")
    print("-" * 40)
    print(f"Condition     : {weather_desc}")
    print(f"Temperature   : {temp}{unit_symbol}")
    print(f"Feels Like    : {feels_like}{unit_symbol}")
    print(f"Humidity      : {humidity}%")
    print(f"Wind Speed    : {wind_speed} {speed_unit}")
    print("-" * 40)

def main():
    print("=== Python Weather CLI ===")
    city = input("Enter city name: ").strip()
    if not city:
        print("City name cannot be empty.")
        return

    unit_choice = input("Units - (1) Metric (°C), (2) Imperial (°F), (3) Kelvin: ").strip()
    if unit_choice == "1":
        units = "metric"
    elif unit_choice == "2":
        units = "imperial"
    else:
        units = None

    data = get_weather(city, units=units if units else "")
    if data:
        format_and_print(data, units=units if units else "")

if __name__ == "__main__":
    main()

Run with

python weather_cli.py

Step 3. Optional Web Version Using Flask (weather_web.py)

import os
from flask import Flask, request, render_template_string
import requests
from datetime import datetime

app = Flask(__name__)
API_KEY = os.getenv("OWM_API_KEY")
BASE_URL = "https://api.openweathermap.org/data/2.5/weather"

TEMPLATE = """
<!doctype html>
<title>Weather App</title>
<h2>Weather in {{ name }}, {{ country }}</h2>
<p><strong>Condition:</strong> {{ desc }}</p>
<p><strong>Temperature:</strong> {{ temp }} {{ unit }}</p>
<p><strong>Feels Like:</strong> {{ feels_like }} {{ unit }}</p>
<p><strong>Humidity:</strong> {{ humidity }}%</p>
<p><strong>Wind Speed:</strong> {{ wind }} {{ speed_unit }}</p>
<p><em>As of {{ timestamp }}</em></p>
<form method="get">
  <input name="city" placeholder="City name" required value="{{ name }}">
  <select name="units">
    <option value="metric" {% if units=='metric' %}selected{% endif %}>Metric (°C)</option>
    <option value="imperial" {% if units=='imperial' %}selected{% endif %}>Imperial (°F)</option>
    <option value="" {% if units=='' %}selected{% endif %}>Kelvin</option>
  </select>
  <button type="submit">Get Weather</button>
</form>
{% if error %}
  <p style="color:red;">{{ error }}</p>
{% endif %}
"""

def fetch_weather(city, units="metric"):
    if not API_KEY:
        return None, "API key is missing."

    params = {"q": city, "appid": API_KEY}
    if units in ("metric", "imperial"):
        params["units"] = units

    try:
        r = requests.get(BASE_URL, params=params, timeout=10)
        r.raise_for_status()
        data = r.json()
        if data.get("cod") != 200:
            return None, data.get("message", "API error")
        return data, None
    except requests.RequestException as e:
        return None, str(e)

@app.route("/", methods=["GET"])
def index():
    city = request.args.get("city", "London")
    units = request.args.get("units", "metric")
    data, error = fetch_weather(city, units)
    if data:
        name = data["name"]
        country = data["sys"]["country"]
        desc = data["weather"][0]["description"].title()
        temp = data["main"]["temp"]
        feels_like = data["main"]["feels_like"]
        humidity = data["main"]["humidity"]
        wind = data["wind"]["speed"]
        timestamp = datetime.fromtimestamp(data["dt"]).strftime("%Y-%m-%d %H:%M:%S")
        unit_symbol = "°C" if units == "metric" else "°F" if units == "imperial" else "K"
        speed_unit = "m/s" if units in ("metric", "") else "mph"
        return render_template_string(
            TEMPLATE,
            name=name,
            country=country,
            desc=desc,
            temp=temp,
            feels_like=feels_like,
            humidity=humidity,
            wind=wind,
            timestamp=timestamp,
            unit=unit_symbol,
            speed_unit=speed_unit,
            units=units,
            error=None,
        )
    else:
        return render_template_string(
            TEMPLATE,
            name=city,
            country="",
            desc="",
            temp="",
            feels_like="",
            humidity="",
            wind="",
            timestamp="",
            unit="",
            speed_unit="",
            units=units,
            error=error,
        )

if __name__ == "__main__":
    app.run(debug=True)

Run with

export OWM_API_KEY=your_api_key_here
python weather_web.py

Then visit http://127.0.0.1:5000 in the browser

Enhancements & Next Steps

  • Geolocation auto-detection using IP-to-location services
  • Caching responses to avoid hitting rate limits
  • Forecast extension using OpenWeather One Call API or 5-day/3-hour forecast
  • GUI version with Tkinter or PyQt
  • Deploy the web app on Render, Vercel, or VPS
  • Add icons/visuals based on weather condition codes

Security & Best Practices

  • Never hardcode the API key
  • Handle API errors and timeouts
  • Respect API rate limits

Conclusion

This weather application demonstrates how to integrate external REST APIs in Python, parse JSON responses, handle errors, and optionally build a lightweight web UI. It's a great stepping stone toward more complex data-driven apps.

Output

Output screenshot