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
- Register (free) at openweathermap.org
- Navigate to the API section and generate an API key (called appid)
- 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]()