Skip to content

Weather App GUI with API Integration

Abstract

Create a comprehensive desktop weather application that integrates with the OpenWeatherMap API to provide real-time weather data, 5-day forecasts, and weather history. This project demonstrates advanced GUI development, API integration, threading for responsive UI, and data persistence with JSON file handling.

Prerequisites

  • Python 3.7 or above
  • Text Editor or IDE
  • Solid understanding of Python syntax and OOP concepts
  • Knowledge of Tkinter for GUI development
  • Familiarity with API requests and JSON handling
  • Understanding of threading and asynchronous programming
  • Basic knowledge of weather data and meteorology concepts

Getting Started

Create a new project

  1. Create a new project folder and name it weatherAppGUIweatherAppGUI.
  2. Create a new file and name it weatherappgui.pyweatherappgui.py.
  3. Sign up for a free API key at OpenWeatherMap.
  4. Install required dependencies: pip install requestspip install requests
  5. Open the project folder in your favorite text editor or IDE.
  6. Copy the code below and paste it into your weatherappgui.pyweatherappgui.py file.

Write the code

  1. Add the following code to your weatherappgui.pyweatherappgui.py file.
⚙️ Weather App GUI with API Integration
Weather App GUI with API Integration
# Weather App with GUI (Tkinter)
 
import tkinter as tk
from tkinter import ttk, messagebox
import requests
import json
from datetime import datetime, timedelta
from typing import Dict, Optional, List
import threading
 
class WeatherApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Weather App")
        self.root.geometry("800x600")
        self.root.configure(bg='#2c3e50')
        
        # API key (you would get this from OpenWeatherMap)
        self.api_key = "your_api_key_here"  # Replace with actual API key
        self.base_url = "http://api.openweathermap.org/data/2.5"
        
        # Store weather data
        self.current_weather = None
        self.forecast_data = None
        
        self.setup_ui()
        
    def setup_ui(self):
        """Setup the user interface"""
        # Title
        title_label = tk.Label(
            self.root, 
            text="Weather App", 
            font=("Arial", 24, "bold"),
            bg='#2c3e50', 
            fg='#ecf0f1'
        )
        title_label.pack(pady=20)
        
        # Search frame
        search_frame = tk.Frame(self.root, bg='#2c3e50')
        search_frame.pack(pady=10)
        
        tk.Label(
            search_frame, 
            text="Enter City:", 
            font=("Arial", 12),
            bg='#2c3e50', 
            fg='#ecf0f1'
        ).pack(side=tk.LEFT, padx=5)
        
        self.city_entry = tk.Entry(
            search_frame, 
            font=("Arial", 12), 
            width=20
        )
        self.city_entry.pack(side=tk.LEFT, padx=5)
        self.city_entry.bind('<Return>', lambda event: self.get_weather())
        
        self.search_button = tk.Button(
            search_frame, 
            text="Get Weather", 
            command=self.get_weather,
            font=("Arial", 10, "bold"),
            bg='#3498db',
            fg='white',
            padx=10
        )
        self.search_button.pack(side=tk.LEFT, padx=5)
        
        # Current weather frame
        self.current_frame = tk.LabelFrame(
            self.root, 
            text="Current Weather", 
            font=("Arial", 14, "bold"),
            bg='#34495e',
            fg='#ecf0f1',
            padx=20,
            pady=15
        )
        self.current_frame.pack(pady=20, padx=20, fill='x')
        
        # Weather info labels
        self.weather_labels = {}
        self.create_weather_labels()
        
        # Forecast frame
        self.forecast_frame = tk.LabelFrame(
            self.root, 
            text="5-Day Forecast", 
            font=("Arial", 14, "bold"),
            bg='#34495e',
            fg='#ecf0f1',
            padx=20,
            pady=15
        )
        self.forecast_frame.pack(pady=10, padx=20, fill='both', expand=True)
        
        # Buttons frame
        buttons_frame = tk.Frame(self.root, bg='#2c3e50')
        buttons_frame.pack(pady=10)
        
        self.refresh_button = tk.Button(
            buttons_frame, 
            text="Refresh", 
            command=self.refresh_weather,
            font=("Arial", 10),
            bg='#27ae60',
            fg='white',
            padx=15
        )
        self.refresh_button.pack(side=tk.LEFT, padx=5)
        
        self.save_button = tk.Button(
            buttons_frame, 
            text="Save Data", 
            command=self.save_weather_data,
            font=("Arial", 10),
            bg='#e67e22',
            fg='white',
            padx=15
        )
        self.save_button.pack(side=tk.LEFT, padx=5)
        
        # Status bar
        self.status_var = tk.StringVar()
        self.status_var.set("Ready")
        status_bar = tk.Label(
            self.root, 
            textvariable=self.status_var, 
            relief=tk.SUNKEN, 
            anchor=tk.W,
            bg='#2c3e50',
            fg='#ecf0f1'
        )
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
    def create_weather_labels(self):
        """Create labels for weather information"""
        # City and country
        self.weather_labels['city'] = tk.Label(
            self.current_frame, 
            text="City: -", 
            font=("Arial", 14, "bold"),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['city'].grid(row=0, column=0, columnspan=2, pady=5, sticky='w')
        
        # Temperature
        self.weather_labels['temp'] = tk.Label(
            self.current_frame, 
            text="Temperature: -", 
            font=("Arial", 24, "bold"),
            bg='#34495e',
            fg='#e74c3c'
        )
        self.weather_labels['temp'].grid(row=1, column=0, columnspan=2, pady=10)
        
        # Weather description
        self.weather_labels['desc'] = tk.Label(
            self.current_frame, 
            text="Description: -", 
            font=("Arial", 12),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['desc'].grid(row=2, column=0, columnspan=2, pady=5)
        
        # Feels like
        self.weather_labels['feels_like'] = tk.Label(
            self.current_frame, 
            text="Feels like: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['feels_like'].grid(row=3, column=0, pady=5, sticky='w')
        
        # Humidity
        self.weather_labels['humidity'] = tk.Label(
            self.current_frame, 
            text="Humidity: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['humidity'].grid(row=3, column=1, pady=5, sticky='w')
        
        # Pressure
        self.weather_labels['pressure'] = tk.Label(
            self.current_frame, 
            text="Pressure: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['pressure'].grid(row=4, column=0, pady=5, sticky='w')
        
        # Wind speed
        self.weather_labels['wind'] = tk.Label(
            self.current_frame, 
            text="Wind: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['wind'].grid(row=4, column=1, pady=5, sticky='w')
        
        # Visibility
        self.weather_labels['visibility'] = tk.Label(
            self.current_frame, 
            text="Visibility: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['visibility'].grid(row=5, column=0, pady=5, sticky='w')
        
        # UV Index (if available)
        self.weather_labels['uv'] = tk.Label(
            self.current_frame, 
            text="UV Index: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['uv'].grid(row=5, column=1, pady=5, sticky='w')
        
    def get_weather(self):
        """Get weather data for the specified city"""
        city = self.city_entry.get().strip()
        if not city:
            messagebox.showwarning("Warning", "Please enter a city name")
            return
        
        # Show loading status
        self.status_var.set("Loading weather data...")
        self.search_button.config(state='disabled')
        
        # Use threading to prevent UI freezing
        thread = threading.Thread(target=self._fetch_weather_data, args=(city,))
        thread.daemon = True
        thread.start()
        
    def _fetch_weather_data(self, city: str):
        """Fetch weather data in a separate thread"""
        try:
            # Get current weather
            current_url = f"{self.base_url}/weather?q={city}&appid={self.api_key}&units=metric"
            
            # For demo purposes, we'll use mock data
            # In real implementation, you would use: response = requests.get(current_url)
            mock_current_data = self.get_mock_current_weather(city)
            
            # Get forecast
            # forecast_url = f"{self.base_url}/forecast?q={city}&appid={self.api_key}&units=metric"
            mock_forecast_data = self.get_mock_forecast_data(city)
            
            # Update UI in main thread
            self.root.after(0, self._update_weather_display, mock_current_data, mock_forecast_data)
            
        except Exception as e:
            self.root.after(0, self._show_error, f"Error fetching weather data: {str(e)}")
        
    def get_mock_current_weather(self, city: str) -> Dict:
        """Generate mock current weather data for demo"""
        import random
        
        temps = [15, 18, 22, 25, 28, 30, 12, 8, 5, 2]
        conditions = ["Clear", "Cloudy", "Rainy", "Sunny", "Partly Cloudy", "Overcast"]
        
        return {
            "name": city.title(),
            "sys": {"country": "XX"},
            "main": {
                "temp": random.choice(temps),
                "feels_like": random.choice(temps) + random.randint(-3, 3),
                "humidity": random.randint(30, 80),
                "pressure": random.randint(1000, 1030)
            },
            "weather": [{
                "main": random.choice(conditions),
                "description": random.choice(conditions).lower()
            }],
            "wind": {
                "speed": random.randint(5, 25)
            },
            "visibility": random.randint(5000, 10000),
            "dt": int(datetime.now().timestamp())
        }
    
    def get_mock_forecast_data(self, city: str) -> Dict:
        """Generate mock forecast data for demo"""
        import random
        
        forecast_list = []
        for i in range(40):  # 5 days * 8 times per day (3-hour intervals)
            date = datetime.now() + timedelta(hours=i*3)
            temp = random.randint(10, 30)
            
            forecast_list.append({
                "dt": int(date.timestamp()),
                "dt_txt": date.strftime("%Y-%m-%d %H:%M:%S"),
                "main": {
                    "temp": temp,
                    "temp_min": temp - 2,
                    "temp_max": temp + 2
                },
                "weather": [{
                    "main": random.choice(["Clear", "Cloudy", "Rain"]),
                    "description": "demo weather"
                }]
            })
        
        return {"list": forecast_list}
    
    def _update_weather_display(self, current_data: Dict, forecast_data: Dict):
        """Update the weather display with fetched data"""
        try:
            self.current_weather = current_data
            self.forecast_data = forecast_data
            
            # Update current weather
            city_country = f"{current_data['name']}, {current_data['sys']['country']}"
            self.weather_labels['city'].config(text=f"City: {city_country}")
            
            temp = round(current_data['main']['temp'])
            self.weather_labels['temp'].config(text=f"{temp}°C")
            
            desc = current_data['weather'][0]['description'].title()
            self.weather_labels['desc'].config(text=f"Description: {desc}")
            
            feels_like = round(current_data['main']['feels_like'])
            self.weather_labels['feels_like'].config(text=f"Feels like: {feels_like}°C")
            
            humidity = current_data['main']['humidity']
            self.weather_labels['humidity'].config(text=f"Humidity: {humidity}%")
            
            pressure = current_data['main']['pressure']
            self.weather_labels['pressure'].config(text=f"Pressure: {pressure} hPa")
            
            wind_speed = current_data['wind']['speed']
            self.weather_labels['wind'].config(text=f"Wind: {wind_speed} m/s")
            
            visibility = current_data.get('visibility', 0) / 1000
            self.weather_labels['visibility'].config(text=f"Visibility: {visibility:.1f} km")
            
            # Update forecast
            self.update_forecast_display(forecast_data)
            
            # Update status
            self.status_var.set(f"Last updated: {datetime.now().strftime('%H:%M:%S')}")
            
        except Exception as e:
            self._show_error(f"Error updating display: {str(e)}")
        
        finally:
            self.search_button.config(state='normal')
    
    def update_forecast_display(self, forecast_data: Dict):
        """Update the forecast display"""
        # Clear existing forecast widgets
        for widget in self.forecast_frame.winfo_children():
            widget.destroy()
        
        # Group forecast by day
        daily_forecasts = {}
        for item in forecast_data['list'][:40]:  # 5 days
            date = datetime.fromtimestamp(item['dt']).date()
            if date not in daily_forecasts:
                daily_forecasts[date] = []
            daily_forecasts[date].append(item)
        
        # Display daily forecasts
        for i, (date, forecasts) in enumerate(list(daily_forecasts.items())[:5]):
            # Calculate daily min/max temperatures
            temps = [f['main']['temp'] for f in forecasts]
            min_temp = round(min(temps))
            max_temp = round(max(temps))
            
            # Get most common weather condition
            conditions = [f['weather'][0]['main'] for f in forecasts]
            most_common = max(set(conditions), key=conditions.count)
            
            # Create forecast frame
            day_frame = tk.Frame(self.forecast_frame, bg='#34495e')
            day_frame.grid(row=0, column=i, padx=5, pady=5, sticky='nsew')
            
            # Configure grid weights
            self.forecast_frame.grid_columnconfigure(i, weight=1)
            
            # Day label
            day_name = date.strftime('%A') if date == datetime.now().date() else date.strftime('%a')
            day_label = tk.Label(
                day_frame, 
                text=day_name,
                font=("Arial", 10, "bold"),
                bg='#34495e',
                fg='#ecf0f1'
            )
            day_label.pack(pady=2)
            
            # Date label
            date_label = tk.Label(
                day_frame, 
                text=date.strftime('%m/%d'),
                font=("Arial", 9),
                bg='#34495e',
                fg='#bdc3c7'
            )
            date_label.pack()
            
            # Weather condition
            condition_label = tk.Label(
                day_frame, 
                text=most_common,
                font=("Arial", 9),
                bg='#34495e',
                fg='#ecf0f1'
            )
            condition_label.pack(pady=2)
            
            # Temperature range
            temp_label = tk.Label(
                day_frame, 
                text=f"{max_temp}°/{min_temp}°",
                font=("Arial", 10, "bold"),
                bg='#34495e',
                fg='#e74c3c'
            )
            temp_label.pack()
    
    def _show_error(self, message: str):
        """Show error message"""
        messagebox.showerror("Error", message)
        self.status_var.set("Error occurred")
        self.search_button.config(state='normal')
    
    def refresh_weather(self):
        """Refresh current weather data"""
        city = self.city_entry.get().strip()
        if city:
            self.get_weather()
        else:
            messagebox.showwarning("Warning", "Please enter a city name first")
    
    def save_weather_data(self):
        """Save current weather data to file"""
        if not self.current_weather:
            messagebox.showwarning("Warning", "No weather data to save")
            return
        
        try:
            filename = f"weather_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
            
            data_to_save = {
                "current_weather": self.current_weather,
                "forecast": self.forecast_data,
                "saved_at": datetime.now().isoformat()
            }
            
            with open(filename, 'w') as f:
                json.dump(data_to_save, f, indent=2)
            
            messagebox.showinfo("Success", f"Weather data saved to {filename}")
            self.status_var.set(f"Data saved to {filename}")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save data: {str(e)}")
 
def main():
    """Main function to run the weather app"""
    root = tk.Tk()
    app = WeatherApp(root)
    
    # Center the window
    root.update_idletasks()
    width = root.winfo_width()
    height = root.winfo_height()
    x = (root.winfo_screenwidth() // 2) - (width // 2)
    y = (root.winfo_screenheight() // 2) - (height // 2)
    root.geometry(f'{width}x{height}+{x}+{y}')
    
    # Show instructions for API key
    messagebox.showinfo(
        "API Key Required", 
        "To use real weather data, please:\n"
        "1. Sign up at https://openweathermap.org/api\n"
        "2. Get your free API key\n"
        "3. Replace 'your_api_key_here' in the code\n\n"
        "For now, the app will show demo data."
    )
    
    root.mainloop()
 
if __name__ == "__main__":
    main()
 
Weather App GUI with API Integration
# Weather App with GUI (Tkinter)
 
import tkinter as tk
from tkinter import ttk, messagebox
import requests
import json
from datetime import datetime, timedelta
from typing import Dict, Optional, List
import threading
 
class WeatherApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Weather App")
        self.root.geometry("800x600")
        self.root.configure(bg='#2c3e50')
        
        # API key (you would get this from OpenWeatherMap)
        self.api_key = "your_api_key_here"  # Replace with actual API key
        self.base_url = "http://api.openweathermap.org/data/2.5"
        
        # Store weather data
        self.current_weather = None
        self.forecast_data = None
        
        self.setup_ui()
        
    def setup_ui(self):
        """Setup the user interface"""
        # Title
        title_label = tk.Label(
            self.root, 
            text="Weather App", 
            font=("Arial", 24, "bold"),
            bg='#2c3e50', 
            fg='#ecf0f1'
        )
        title_label.pack(pady=20)
        
        # Search frame
        search_frame = tk.Frame(self.root, bg='#2c3e50')
        search_frame.pack(pady=10)
        
        tk.Label(
            search_frame, 
            text="Enter City:", 
            font=("Arial", 12),
            bg='#2c3e50', 
            fg='#ecf0f1'
        ).pack(side=tk.LEFT, padx=5)
        
        self.city_entry = tk.Entry(
            search_frame, 
            font=("Arial", 12), 
            width=20
        )
        self.city_entry.pack(side=tk.LEFT, padx=5)
        self.city_entry.bind('<Return>', lambda event: self.get_weather())
        
        self.search_button = tk.Button(
            search_frame, 
            text="Get Weather", 
            command=self.get_weather,
            font=("Arial", 10, "bold"),
            bg='#3498db',
            fg='white',
            padx=10
        )
        self.search_button.pack(side=tk.LEFT, padx=5)
        
        # Current weather frame
        self.current_frame = tk.LabelFrame(
            self.root, 
            text="Current Weather", 
            font=("Arial", 14, "bold"),
            bg='#34495e',
            fg='#ecf0f1',
            padx=20,
            pady=15
        )
        self.current_frame.pack(pady=20, padx=20, fill='x')
        
        # Weather info labels
        self.weather_labels = {}
        self.create_weather_labels()
        
        # Forecast frame
        self.forecast_frame = tk.LabelFrame(
            self.root, 
            text="5-Day Forecast", 
            font=("Arial", 14, "bold"),
            bg='#34495e',
            fg='#ecf0f1',
            padx=20,
            pady=15
        )
        self.forecast_frame.pack(pady=10, padx=20, fill='both', expand=True)
        
        # Buttons frame
        buttons_frame = tk.Frame(self.root, bg='#2c3e50')
        buttons_frame.pack(pady=10)
        
        self.refresh_button = tk.Button(
            buttons_frame, 
            text="Refresh", 
            command=self.refresh_weather,
            font=("Arial", 10),
            bg='#27ae60',
            fg='white',
            padx=15
        )
        self.refresh_button.pack(side=tk.LEFT, padx=5)
        
        self.save_button = tk.Button(
            buttons_frame, 
            text="Save Data", 
            command=self.save_weather_data,
            font=("Arial", 10),
            bg='#e67e22',
            fg='white',
            padx=15
        )
        self.save_button.pack(side=tk.LEFT, padx=5)
        
        # Status bar
        self.status_var = tk.StringVar()
        self.status_var.set("Ready")
        status_bar = tk.Label(
            self.root, 
            textvariable=self.status_var, 
            relief=tk.SUNKEN, 
            anchor=tk.W,
            bg='#2c3e50',
            fg='#ecf0f1'
        )
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
    def create_weather_labels(self):
        """Create labels for weather information"""
        # City and country
        self.weather_labels['city'] = tk.Label(
            self.current_frame, 
            text="City: -", 
            font=("Arial", 14, "bold"),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['city'].grid(row=0, column=0, columnspan=2, pady=5, sticky='w')
        
        # Temperature
        self.weather_labels['temp'] = tk.Label(
            self.current_frame, 
            text="Temperature: -", 
            font=("Arial", 24, "bold"),
            bg='#34495e',
            fg='#e74c3c'
        )
        self.weather_labels['temp'].grid(row=1, column=0, columnspan=2, pady=10)
        
        # Weather description
        self.weather_labels['desc'] = tk.Label(
            self.current_frame, 
            text="Description: -", 
            font=("Arial", 12),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['desc'].grid(row=2, column=0, columnspan=2, pady=5)
        
        # Feels like
        self.weather_labels['feels_like'] = tk.Label(
            self.current_frame, 
            text="Feels like: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['feels_like'].grid(row=3, column=0, pady=5, sticky='w')
        
        # Humidity
        self.weather_labels['humidity'] = tk.Label(
            self.current_frame, 
            text="Humidity: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['humidity'].grid(row=3, column=1, pady=5, sticky='w')
        
        # Pressure
        self.weather_labels['pressure'] = tk.Label(
            self.current_frame, 
            text="Pressure: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['pressure'].grid(row=4, column=0, pady=5, sticky='w')
        
        # Wind speed
        self.weather_labels['wind'] = tk.Label(
            self.current_frame, 
            text="Wind: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['wind'].grid(row=4, column=1, pady=5, sticky='w')
        
        # Visibility
        self.weather_labels['visibility'] = tk.Label(
            self.current_frame, 
            text="Visibility: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['visibility'].grid(row=5, column=0, pady=5, sticky='w')
        
        # UV Index (if available)
        self.weather_labels['uv'] = tk.Label(
            self.current_frame, 
            text="UV Index: -", 
            font=("Arial", 11),
            bg='#34495e',
            fg='#ecf0f1'
        )
        self.weather_labels['uv'].grid(row=5, column=1, pady=5, sticky='w')
        
    def get_weather(self):
        """Get weather data for the specified city"""
        city = self.city_entry.get().strip()
        if not city:
            messagebox.showwarning("Warning", "Please enter a city name")
            return
        
        # Show loading status
        self.status_var.set("Loading weather data...")
        self.search_button.config(state='disabled')
        
        # Use threading to prevent UI freezing
        thread = threading.Thread(target=self._fetch_weather_data, args=(city,))
        thread.daemon = True
        thread.start()
        
    def _fetch_weather_data(self, city: str):
        """Fetch weather data in a separate thread"""
        try:
            # Get current weather
            current_url = f"{self.base_url}/weather?q={city}&appid={self.api_key}&units=metric"
            
            # For demo purposes, we'll use mock data
            # In real implementation, you would use: response = requests.get(current_url)
            mock_current_data = self.get_mock_current_weather(city)
            
            # Get forecast
            # forecast_url = f"{self.base_url}/forecast?q={city}&appid={self.api_key}&units=metric"
            mock_forecast_data = self.get_mock_forecast_data(city)
            
            # Update UI in main thread
            self.root.after(0, self._update_weather_display, mock_current_data, mock_forecast_data)
            
        except Exception as e:
            self.root.after(0, self._show_error, f"Error fetching weather data: {str(e)}")
        
    def get_mock_current_weather(self, city: str) -> Dict:
        """Generate mock current weather data for demo"""
        import random
        
        temps = [15, 18, 22, 25, 28, 30, 12, 8, 5, 2]
        conditions = ["Clear", "Cloudy", "Rainy", "Sunny", "Partly Cloudy", "Overcast"]
        
        return {
            "name": city.title(),
            "sys": {"country": "XX"},
            "main": {
                "temp": random.choice(temps),
                "feels_like": random.choice(temps) + random.randint(-3, 3),
                "humidity": random.randint(30, 80),
                "pressure": random.randint(1000, 1030)
            },
            "weather": [{
                "main": random.choice(conditions),
                "description": random.choice(conditions).lower()
            }],
            "wind": {
                "speed": random.randint(5, 25)
            },
            "visibility": random.randint(5000, 10000),
            "dt": int(datetime.now().timestamp())
        }
    
    def get_mock_forecast_data(self, city: str) -> Dict:
        """Generate mock forecast data for demo"""
        import random
        
        forecast_list = []
        for i in range(40):  # 5 days * 8 times per day (3-hour intervals)
            date = datetime.now() + timedelta(hours=i*3)
            temp = random.randint(10, 30)
            
            forecast_list.append({
                "dt": int(date.timestamp()),
                "dt_txt": date.strftime("%Y-%m-%d %H:%M:%S"),
                "main": {
                    "temp": temp,
                    "temp_min": temp - 2,
                    "temp_max": temp + 2
                },
                "weather": [{
                    "main": random.choice(["Clear", "Cloudy", "Rain"]),
                    "description": "demo weather"
                }]
            })
        
        return {"list": forecast_list}
    
    def _update_weather_display(self, current_data: Dict, forecast_data: Dict):
        """Update the weather display with fetched data"""
        try:
            self.current_weather = current_data
            self.forecast_data = forecast_data
            
            # Update current weather
            city_country = f"{current_data['name']}, {current_data['sys']['country']}"
            self.weather_labels['city'].config(text=f"City: {city_country}")
            
            temp = round(current_data['main']['temp'])
            self.weather_labels['temp'].config(text=f"{temp}°C")
            
            desc = current_data['weather'][0]['description'].title()
            self.weather_labels['desc'].config(text=f"Description: {desc}")
            
            feels_like = round(current_data['main']['feels_like'])
            self.weather_labels['feels_like'].config(text=f"Feels like: {feels_like}°C")
            
            humidity = current_data['main']['humidity']
            self.weather_labels['humidity'].config(text=f"Humidity: {humidity}%")
            
            pressure = current_data['main']['pressure']
            self.weather_labels['pressure'].config(text=f"Pressure: {pressure} hPa")
            
            wind_speed = current_data['wind']['speed']
            self.weather_labels['wind'].config(text=f"Wind: {wind_speed} m/s")
            
            visibility = current_data.get('visibility', 0) / 1000
            self.weather_labels['visibility'].config(text=f"Visibility: {visibility:.1f} km")
            
            # Update forecast
            self.update_forecast_display(forecast_data)
            
            # Update status
            self.status_var.set(f"Last updated: {datetime.now().strftime('%H:%M:%S')}")
            
        except Exception as e:
            self._show_error(f"Error updating display: {str(e)}")
        
        finally:
            self.search_button.config(state='normal')
    
    def update_forecast_display(self, forecast_data: Dict):
        """Update the forecast display"""
        # Clear existing forecast widgets
        for widget in self.forecast_frame.winfo_children():
            widget.destroy()
        
        # Group forecast by day
        daily_forecasts = {}
        for item in forecast_data['list'][:40]:  # 5 days
            date = datetime.fromtimestamp(item['dt']).date()
            if date not in daily_forecasts:
                daily_forecasts[date] = []
            daily_forecasts[date].append(item)
        
        # Display daily forecasts
        for i, (date, forecasts) in enumerate(list(daily_forecasts.items())[:5]):
            # Calculate daily min/max temperatures
            temps = [f['main']['temp'] for f in forecasts]
            min_temp = round(min(temps))
            max_temp = round(max(temps))
            
            # Get most common weather condition
            conditions = [f['weather'][0]['main'] for f in forecasts]
            most_common = max(set(conditions), key=conditions.count)
            
            # Create forecast frame
            day_frame = tk.Frame(self.forecast_frame, bg='#34495e')
            day_frame.grid(row=0, column=i, padx=5, pady=5, sticky='nsew')
            
            # Configure grid weights
            self.forecast_frame.grid_columnconfigure(i, weight=1)
            
            # Day label
            day_name = date.strftime('%A') if date == datetime.now().date() else date.strftime('%a')
            day_label = tk.Label(
                day_frame, 
                text=day_name,
                font=("Arial", 10, "bold"),
                bg='#34495e',
                fg='#ecf0f1'
            )
            day_label.pack(pady=2)
            
            # Date label
            date_label = tk.Label(
                day_frame, 
                text=date.strftime('%m/%d'),
                font=("Arial", 9),
                bg='#34495e',
                fg='#bdc3c7'
            )
            date_label.pack()
            
            # Weather condition
            condition_label = tk.Label(
                day_frame, 
                text=most_common,
                font=("Arial", 9),
                bg='#34495e',
                fg='#ecf0f1'
            )
            condition_label.pack(pady=2)
            
            # Temperature range
            temp_label = tk.Label(
                day_frame, 
                text=f"{max_temp}°/{min_temp}°",
                font=("Arial", 10, "bold"),
                bg='#34495e',
                fg='#e74c3c'
            )
            temp_label.pack()
    
    def _show_error(self, message: str):
        """Show error message"""
        messagebox.showerror("Error", message)
        self.status_var.set("Error occurred")
        self.search_button.config(state='normal')
    
    def refresh_weather(self):
        """Refresh current weather data"""
        city = self.city_entry.get().strip()
        if city:
            self.get_weather()
        else:
            messagebox.showwarning("Warning", "Please enter a city name first")
    
    def save_weather_data(self):
        """Save current weather data to file"""
        if not self.current_weather:
            messagebox.showwarning("Warning", "No weather data to save")
            return
        
        try:
            filename = f"weather_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
            
            data_to_save = {
                "current_weather": self.current_weather,
                "forecast": self.forecast_data,
                "saved_at": datetime.now().isoformat()
            }
            
            with open(filename, 'w') as f:
                json.dump(data_to_save, f, indent=2)
            
            messagebox.showinfo("Success", f"Weather data saved to {filename}")
            self.status_var.set(f"Data saved to {filename}")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save data: {str(e)}")
 
def main():
    """Main function to run the weather app"""
    root = tk.Tk()
    app = WeatherApp(root)
    
    # Center the window
    root.update_idletasks()
    width = root.winfo_width()
    height = root.winfo_height()
    x = (root.winfo_screenwidth() // 2) - (width // 2)
    y = (root.winfo_screenheight() // 2) - (height // 2)
    root.geometry(f'{width}x{height}+{x}+{y}')
    
    # Show instructions for API key
    messagebox.showinfo(
        "API Key Required", 
        "To use real weather data, please:\n"
        "1. Sign up at https://openweathermap.org/api\n"
        "2. Get your free API key\n"
        "3. Replace 'your_api_key_here' in the code\n\n"
        "For now, the app will show demo data."
    )
    
    root.mainloop()
 
if __name__ == "__main__":
    main()
 
  1. Replace your_api_key_hereyour_api_key_here with your actual OpenWeatherMap API key.
  2. Save the file.
  3. Run the following command to run the application.
command
C:\Users\username\Documents\weatherAppGUI> python weatherappgui.py
Weather App
Enter City: New York
[Click "Get Weather"]
Current Weather in New York, US
Temperature: 22°C
Description: Clear Sky
Humidity: 65%, Wind: 8 m/s
5-Day Forecast displayed with daily temperatures
command
C:\Users\username\Documents\weatherAppGUI> python weatherappgui.py
Weather App
Enter City: New York
[Click "Get Weather"]
Current Weather in New York, US
Temperature: 22°C
Description: Clear Sky
Humidity: 65%, Wind: 8 m/s
5-Day Forecast displayed with daily temperatures

Explanation

  1. The import tkinter as tkimport tkinter as tk statement imports the Tkinter library for creating the GUI interface.
  2. The import requestsimport requests imports the requests library for making HTTP API calls to OpenWeatherMap.
  3. The class WeatherApp:class WeatherApp: defines the main application class that manages the GUI and weather data.
  4. The setup_ui()setup_ui() method creates all the GUI components including entry fields, buttons, and display labels.
  5. The get_weather()get_weather() method handles user input and initiates the weather data fetching process.
  6. The threading implementation prevents the UI from freezing during API calls.
  7. The _fetch_weather_data()_fetch_weather_data() method makes API requests to get current weather and forecast data.
  8. The weather display updates show current conditions, temperature, humidity, wind speed, and pressure.
  9. The forecast display shows a 5-day weather prediction with daily temperature ranges.
  10. The save_weather_data()save_weather_data() method allows users to save weather information to JSON files.
  11. Error handling manages network issues, invalid city names, and API response errors.
  12. The status bar provides real-time feedback about the application’s current state.

Next Steps

Congratulations! You have successfully created a Weather App GUI in Python. Experiment with the code and see if you can modify the application. Here are a few suggestions:

  • Add weather maps and radar imagery
  • Implement location-based weather using GPS coordinates
  • Create weather alerts and notifications
  • Add multiple city comparison features
  • Implement weather history charts and graphs
  • Add weather widgets for desktop integration
  • Create different color themes based on weather conditions
  • Add voice announcements for weather updates
  • Implement weather-based activity recommendations

Conclusion

In this project, you learned how to create a Weather App GUI in Python using Tkinter and API integration. You also learned about threading, data persistence, real-time data fetching, and creating responsive user interfaces. You can find the source code on GitHub

1. WeatherApp Class Structure

weatherappgui.py
class WeatherApp:
    def __init__(self):
        self.root = tk.Tk()
        self.api_key = "YOUR_API_KEY_HERE"
        self.base_url = "http://api.openweathermap.org/data/2.5/"
        self.weather_data = {}
        self.favorites = []
weatherappgui.py
class WeatherApp:
    def __init__(self):
        self.root = tk.Tk()
        self.api_key = "YOUR_API_KEY_HERE"
        self.base_url = "http://api.openweathermap.org/data/2.5/"
        self.weather_data = {}
        self.favorites = []

The main class manages:

  • GUI Components: All Tkinter widgets and layouts
  • API Communication: HTTP requests to OpenWeatherMap
  • Data Management: Weather data storage and retrieval
  • User Preferences: Favorite cities and settings

2. API Integration

weatherappgui.py
def get_weather_data(self, city, units="metric"):
    """Fetch current weather data from API"""
    try:
        url = f"{self.base_url}weather"
        params = {
            "q": city,
            "appid": self.api_key,
            "units": units
        }
        response = requests.get(url, params=params, timeout=10)
        return response.json()
    except requests.RequestException as e:
        self.show_error(f"API Error: {e}")
        return None
weatherappgui.py
def get_weather_data(self, city, units="metric"):
    """Fetch current weather data from API"""
    try:
        url = f"{self.base_url}weather"
        params = {
            "q": city,
            "appid": self.api_key,
            "units": units
        }
        response = requests.get(url, params=params, timeout=10)
        return response.json()
    except requests.RequestException as e:
        self.show_error(f"API Error: {e}")
        return None

3. Threading for Responsive UI

weatherappgui.py
def fetch_weather_threaded(self, city):
    """Fetch weather data in separate thread"""
    def worker():
        self.show_loading(True)
        data = self.get_weather_data(city)
        self.root.after(0, lambda: self.display_weather(data))
        self.show_loading(False)
    
    thread = threading.Thread(target=worker, daemon=True)
    thread.start()
weatherappgui.py
def fetch_weather_threaded(self, city):
    """Fetch weather data in separate thread"""
    def worker():
        self.show_loading(True)
        data = self.get_weather_data(city)
        self.root.after(0, lambda: self.display_weather(data))
        self.show_loading(False)
    
    thread = threading.Thread(target=worker, daemon=True)
    thread.start()

4. Data Persistence

weatherappgui.py
def save_weather_data(self, city, data):
    """Save weather data locally"""
    filename = f"weather_data_{city.lower()}.json"
    try:
        with open(filename, 'w') as f:
            json.dump(data, f, indent=2)
    except IOError as e:
        self.show_error(f"Save Error: {e}")
weatherappgui.py
def save_weather_data(self, city, data):
    """Save weather data locally"""
    filename = f"weather_data_{city.lower()}.json"
    try:
        with open(filename, 'w') as f:
            json.dump(data, f, indent=2)
    except IOError as e:
        self.show_error(f"Save Error: {e}")

Usage Examples

Basic Usage

weatherappgui.py
# Run the application
app = WeatherApp()
app.run()
weatherappgui.py
# Run the application
app = WeatherApp()
app.run()

API Configuration

weatherappgui.py
# Set your API key
app = WeatherApp()
app.set_api_key("your_openweathermap_api_key")
app.run()
weatherappgui.py
# Set your API key
app = WeatherApp()
app.set_api_key("your_openweathermap_api_key")
app.run()

Custom Settings

weatherappgui.py
# Configure default settings
app = WeatherApp()
app.set_default_units("imperial")  # Fahrenheit, mph
app.set_default_city("New York")
app.run()
weatherappgui.py
# Configure default settings
app = WeatherApp()
app.set_default_units("imperial")  # Fahrenheit, mph
app.set_default_city("New York")
app.run()

Running the Application

Command Line

python weatherappgui.py
python weatherappgui.py

Expected Output

Weather App Starting...
API Key: Configured ✓
GUI: Initialized ✓
Ready for use!
Weather App Starting...
API Key: Configured ✓
GUI: Initialized ✓
Ready for use!

User Interface Guide

Main Window Components

1. Search Section

  • City Input: Text field for entering city names
  • Search Button: Fetch weather for entered city
  • Units Toggle: Switch between Celsius/Fahrenheit

2. Current Weather Display

Current Weather in New York
Temperature: 22°C (Feels like 25°C)
Condition: Partly Cloudy
Humidity: 65%
Wind: 15 km/h NE
Pressure: 1013 hPa
Visibility: 10 km
UV Index: 6 (High)
Current Weather in New York
Temperature: 22°C (Feels like 25°C)
Condition: Partly Cloudy
Humidity: 65%
Wind: 15 km/h NE
Pressure: 1013 hPa
Visibility: 10 km
UV Index: 6 (High)

3. Forecast Section

  • 5-Day Forecast: Daily weather predictions
  • Hourly Forecast: 24-hour detailed forecast
  • Weather Charts: Temperature and precipitation graphs

4. Additional Features

  • Favorites List: Quick access to saved cities
  • Weather History: Previously fetched data
  • Settings Panel: Customize app preferences

Advanced Features

1. Weather Alerts

weatherappgui.py
def check_weather_alerts(self, weather_data):
    """Check for severe weather conditions"""
    alerts = []
    
    # Temperature alerts
    temp = weather_data['main']['temp']
    if temp > 35:  # Celsius
        alerts.append("⚠️ Extreme Heat Warning")
    elif temp < -10:
        alerts.append("❄️ Extreme Cold Warning")
    
    # Wind alerts
    wind_speed = weather_data['wind']['speed']
    if wind_speed > 20:  # m/s
        alerts.append("💨 High Wind Warning")
    
    return alerts
weatherappgui.py
def check_weather_alerts(self, weather_data):
    """Check for severe weather conditions"""
    alerts = []
    
    # Temperature alerts
    temp = weather_data['main']['temp']
    if temp > 35:  # Celsius
        alerts.append("⚠️ Extreme Heat Warning")
    elif temp < -10:
        alerts.append("❄️ Extreme Cold Warning")
    
    # Wind alerts
    wind_speed = weather_data['wind']['speed']
    if wind_speed > 20:  # m/s
        alerts.append("💨 High Wind Warning")
    
    return alerts

2. Data Visualization

weatherappgui.py
def create_temperature_chart(self, forecast_data):
    """Create temperature trend chart"""
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    
    # Extract temperature data
    temps = [item['main']['temp'] for item in forecast_data]
    times = [item['dt_txt'] for item in forecast_data]
    
    # Create chart
    fig, ax = plt.subplots(figsize=(10, 4))
    ax.plot(times, temps, marker='o')
    ax.set_title('Temperature Forecast')
    ax.set_ylabel('Temperature (°C)')
    
    # Embed in Tkinter
    canvas = FigureCanvasTkAgg(fig, self.chart_frame)
    canvas.draw()
    canvas.get_tk_widget().pack()
weatherappgui.py
def create_temperature_chart(self, forecast_data):
    """Create temperature trend chart"""
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    
    # Extract temperature data
    temps = [item['main']['temp'] for item in forecast_data]
    times = [item['dt_txt'] for item in forecast_data]
    
    # Create chart
    fig, ax = plt.subplots(figsize=(10, 4))
    ax.plot(times, temps, marker='o')
    ax.set_title('Temperature Forecast')
    ax.set_ylabel('Temperature (°C)')
    
    # Embed in Tkinter
    canvas = FigureCanvasTkAgg(fig, self.chart_frame)
    canvas.draw()
    canvas.get_tk_widget().pack()

3. Favorites Management

weatherappgui.py
def add_to_favorites(self, city):
    """Add city to favorites list"""
    if city not in self.favorites:
        self.favorites.append(city)
        self.save_favorites()
        self.update_favorites_list()
 
def save_favorites(self):
    """Persist favorites to file"""
    with open('favorites.json', 'w') as f:
        json.dump(self.favorites, f)
weatherappgui.py
def add_to_favorites(self, city):
    """Add city to favorites list"""
    if city not in self.favorites:
        self.favorites.append(city)
        self.save_favorites()
        self.update_favorites_list()
 
def save_favorites(self):
    """Persist favorites to file"""
    with open('favorites.json', 'w') as f:
        json.dump(self.favorites, f)

Configuration Options

Settings File (config.json)

{
    "api_key": "your_api_key_here",
    "default_city": "London",
    "default_units": "metric",
    "update_interval": 300,
    "theme": "light",
    "show_forecast": true,
    "auto_refresh": true
}
{
    "api_key": "your_api_key_here",
    "default_city": "London",
    "default_units": "metric",
    "update_interval": 300,
    "theme": "light",
    "show_forecast": true,
    "auto_refresh": true
}

Customization Examples

weatherappgui.py
# Theme customization
def apply_dark_theme(self):
    """Apply dark theme to UI"""
    self.root.configure(bg='#2b2b2b')
    self.main_frame.configure(bg='#2b2b2b')
    # Update all widget colors
 
# Unit conversion
def convert_temperature(self, temp, from_unit, to_unit):
    """Convert temperature between units"""
    if from_unit == "celsius" and to_unit == "fahrenheit":
        return (temp * 9/5) + 32
    elif from_unit == "fahrenheit" and to_unit == "celsius":
        return (temp - 32) * 5/9
    return temp
weatherappgui.py
# Theme customization
def apply_dark_theme(self):
    """Apply dark theme to UI"""
    self.root.configure(bg='#2b2b2b')
    self.main_frame.configure(bg='#2b2b2b')
    # Update all widget colors
 
# Unit conversion
def convert_temperature(self, temp, from_unit, to_unit):
    """Convert temperature between units"""
    if from_unit == "celsius" and to_unit == "fahrenheit":
        return (temp * 9/5) + 32
    elif from_unit == "fahrenheit" and to_unit == "celsius":
        return (temp - 32) * 5/9
    return temp

Sample Weather Data

Current Weather Response

{
    "weather": [
        {
            "id": 801,
            "main": "Clouds",
            "description": "few clouds",
            "icon": "02d"
        }
    ],
    "main": {
        "temp": 22.5,
        "feels_like": 24.8,
        "temp_min": 20.1,
        "temp_max": 25.3,
        "pressure": 1013,
        "humidity": 65
    },
    "wind": {
        "speed": 4.2,
        "deg": 180
    },
    "dt": 1693737600,
    "name": "London"
}
{
    "weather": [
        {
            "id": 801,
            "main": "Clouds",
            "description": "few clouds",
            "icon": "02d"
        }
    ],
    "main": {
        "temp": 22.5,
        "feels_like": 24.8,
        "temp_min": 20.1,
        "temp_max": 25.3,
        "pressure": 1013,
        "humidity": 65
    },
    "wind": {
        "speed": 4.2,
        "deg": 180
    },
    "dt": 1693737600,
    "name": "London"
}

Forecast Response Structure

{
    "list": [
        {
            "dt": 1693737600,
            "main": {
                "temp": 22.5,
                "humidity": 65
            },
            "weather": [
                {
                    "main": "Clouds",
                    "description": "few clouds"
                }
            ],
            "dt_txt": "2025-09-02 12:00:00"
        }
    ]
}
{
    "list": [
        {
            "dt": 1693737600,
            "main": {
                "temp": 22.5,
                "humidity": 65
            },
            "weather": [
                {
                    "main": "Clouds",
                    "description": "few clouds"
                }
            ],
            "dt_txt": "2025-09-02 12:00:00"
        }
    ]
}

Error Handling

Common Issues and Solutions

1. API Key Issues

weatherappgui.py
def validate_api_key(self):
    """Validate API key before making requests"""
    test_url = f"{self.base_url}weather?q=London&appid={self.api_key}"
    try:
        response = requests.get(test_url, timeout=5)
        if response.status_code == 401:
            raise ValueError("Invalid API key")
        return True
    except requests.RequestException:
        return False
weatherappgui.py
def validate_api_key(self):
    """Validate API key before making requests"""
    test_url = f"{self.base_url}weather?q=London&appid={self.api_key}"
    try:
        response = requests.get(test_url, timeout=5)
        if response.status_code == 401:
            raise ValueError("Invalid API key")
        return True
    except requests.RequestException:
        return False

2. Network Connectivity

weatherappgui.py
def check_internet_connection(self):
    """Check if internet connection is available"""
    try:
        requests.get('https://www.google.com', timeout=3)
        return True
    except requests.RequestException:
        return False
weatherappgui.py
def check_internet_connection(self):
    """Check if internet connection is available"""
    try:
        requests.get('https://www.google.com', timeout=3)
        return True
    except requests.RequestException:
        return False

3. Invalid City Names

weatherappgui.py
def validate_city_name(self, city):
    """Validate city name format"""
    if not city or len(city.strip()) < 2:
        raise ValueError("City name too short")
    
    # Check for valid characters
    import re
    if not re.match(r'^[a-zA-Z\s\-\.]+$', city):
        raise ValueError("Invalid characters in city name")
    
    return city.strip().title()
weatherappgui.py
def validate_city_name(self, city):
    """Validate city name format"""
    if not city or len(city.strip()) < 2:
        raise ValueError("City name too short")
    
    # Check for valid characters
    import re
    if not re.match(r'^[a-zA-Z\s\-\.]+$', city):
        raise ValueError("Invalid characters in city name")
    
    return city.strip().title()

Data Export Features

Export to CSV

weatherappgui.py
def export_weather_data(self, filename="weather_export.csv"):
    """Export weather data to CSV"""
    import csv
    
    with open(filename, 'w', newline='') as csvfile:
        fieldnames = ['city', 'date', 'temperature', 'humidity', 'description']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        writer.writeheader()
        for record in self.weather_history:
            writer.writerow(record)
weatherappgui.py
def export_weather_data(self, filename="weather_export.csv"):
    """Export weather data to CSV"""
    import csv
    
    with open(filename, 'w', newline='') as csvfile:
        fieldnames = ['city', 'date', 'temperature', 'humidity', 'description']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        writer.writeheader()
        for record in self.weather_history:
            writer.writerow(record)

Weather Analytics

weatherappgui.py
def analyze_weather_trends(self, city):
    """Analyze weather trends for a city"""
    data = self.load_weather_history(city)
    
    if not data:
        return None
    
    temps = [record['temperature'] for record in data]
    
    analysis = {
        'average_temp': sum(temps) / len(temps),
        'max_temp': max(temps),
        'min_temp': min(temps),
        'temp_range': max(temps) - min(temps)
    }
    
    return analysis
weatherappgui.py
def analyze_weather_trends(self, city):
    """Analyze weather trends for a city"""
    data = self.load_weather_history(city)
    
    if not data:
        return None
    
    temps = [record['temperature'] for record in data]
    
    analysis = {
        'average_temp': sum(temps) / len(temps),
        'max_temp': max(temps),
        'min_temp': min(temps),
        'temp_range': max(temps) - min(temps)
    }
    
    return analysis

Troubleshooting

Common Problems

1. API Rate Limiting

weatherappgui.py
# Solution: Implement rate limiting
import time
 
def make_api_request_with_rate_limit(self, url, params):
    """Make API request with rate limiting"""
    if hasattr(self, 'last_request_time'):
        time_since_last = time.time() - self.last_request_time
        if time_since_last < 1:  # 1 second minimum between requests
            time.sleep(1 - time_since_last)
    
    self.last_request_time = time.time()
    return requests.get(url, params=params)
weatherappgui.py
# Solution: Implement rate limiting
import time
 
def make_api_request_with_rate_limit(self, url, params):
    """Make API request with rate limiting"""
    if hasattr(self, 'last_request_time'):
        time_since_last = time.time() - self.last_request_time
        if time_since_last < 1:  # 1 second minimum between requests
            time.sleep(1 - time_since_last)
    
    self.last_request_time = time.time()
    return requests.get(url, params=params)

2. GUI Freezing

weatherappgui.py
# Solution: Use threading for all API calls
def update_weather_async(self, city):
    """Update weather data asynchronously"""
    def fetch_and_update():
        data = self.get_weather_data(city)
        # Use root.after to update GUI from main thread
        self.root.after(0, lambda: self.update_display(data))
    
    threading.Thread(target=fetch_and_update, daemon=True).start()
weatherappgui.py
# Solution: Use threading for all API calls
def update_weather_async(self, city):
    """Update weather data asynchronously"""
    def fetch_and_update():
        data = self.get_weather_data(city)
        # Use root.after to update GUI from main thread
        self.root.after(0, lambda: self.update_display(data))
    
    threading.Thread(target=fetch_and_update, daemon=True).start()

3. Memory Usage

weatherappgui.py
# Solution: Limit data storage
def manage_data_storage(self):
    """Manage memory usage by limiting stored data"""
    max_records = 1000
    if len(self.weather_history) > max_records:
        # Keep only recent records
        self.weather_history = self.weather_history[-max_records:]
weatherappgui.py
# Solution: Limit data storage
def manage_data_storage(self):
    """Manage memory usage by limiting stored data"""
    max_records = 1000
    if len(self.weather_history) > max_records:
        # Keep only recent records
        self.weather_history = self.weather_history[-max_records:]

Extensions and Improvements

1. Weather Maps Integration

weatherappgui.py
def show_weather_map(self, city):
    """Display weather map for city"""
    # Integrate with map services
    map_url = f"https://tile.openweathermap.org/map/temp_new/5/{lat}/{lon}.png"
    # Display map in new window
weatherappgui.py
def show_weather_map(self, city):
    """Display weather map for city"""
    # Integrate with map services
    map_url = f"https://tile.openweathermap.org/map/temp_new/5/{lat}/{lon}.png"
    # Display map in new window

2. Push Notifications

weatherappgui.py
def setup_weather_notifications(self):
    """Setup desktop notifications for weather alerts"""
    import plyer
    
    def notify_weather_alert(message):
        plyer.notification.notify(
            title="Weather Alert",
            message=message,
            timeout=10
        )
weatherappgui.py
def setup_weather_notifications(self):
    """Setup desktop notifications for weather alerts"""
    import plyer
    
    def notify_weather_alert(message):
        plyer.notification.notify(
            title="Weather Alert",
            message=message,
            timeout=10
        )

3. Multiple Language Support

weatherappgui.py
def set_language(self, lang_code):
    """Set application language"""
    self.language = lang_code
    self.load_translations()
    self.update_ui_text()
weatherappgui.py
def set_language(self, lang_code):
    """Set application language"""
    self.language = lang_code
    self.load_translations()
    self.update_ui_text()

Performance Optimization

1. Caching Strategy

weatherappgui.py
import time
 
def get_weather_with_cache(self, city):
    """Get weather data with caching"""
    cache_key = f"weather_{city}"
    cache_duration = 300  # 5 minutes
    
    if cache_key in self.cache:
        cached_data, timestamp = self.cache[cache_key]
        if time.time() - timestamp < cache_duration:
            return cached_data
    
    # Fetch fresh data
    data = self.get_weather_data(city)
    self.cache[cache_key] = (data, time.time())
    return data
weatherappgui.py
import time
 
def get_weather_with_cache(self, city):
    """Get weather data with caching"""
    cache_key = f"weather_{city}"
    cache_duration = 300  # 5 minutes
    
    if cache_key in self.cache:
        cached_data, timestamp = self.cache[cache_key]
        if time.time() - timestamp < cache_duration:
            return cached_data
    
    # Fetch fresh data
    data = self.get_weather_data(city)
    self.cache[cache_key] = (data, time.time())
    return data

2. Background Updates

weatherappgui.py
def start_background_updates(self):
    """Start background weather updates"""
    def update_worker():
        while self.running:
            for city in self.favorites:
                self.update_weather_cache(city)
            time.sleep(600)  # Update every 10 minutes
    
    self.update_thread = threading.Thread(target=update_worker, daemon=True)
    self.update_thread.start()
weatherappgui.py
def start_background_updates(self):
    """Start background weather updates"""
    def update_worker():
        while self.running:
            for city in self.favorites:
                self.update_weather_cache(city)
            time.sleep(600)  # Update every 10 minutes
    
    self.update_thread = threading.Thread(target=update_worker, daemon=True)
    self.update_thread.start()

Next Steps

After completing this weather app, consider:

  1. Mobile Development: Create mobile versions with Kivy or BeeWare
  2. Web Version: Build web interface with Flask or Django
  3. Machine Learning: Add weather prediction models
  4. IoT Integration: Connect with weather sensors
  5. Advanced Visualizations: Use Plotly for interactive charts

Resources

Conclusion

This weather application demonstrates professional GUI development with Python. It showcases important concepts like API integration, threading, data persistence, and user interface design. The modular structure makes it easy to extend with additional features and customizations.

The application provides a solid foundation for building more complex desktop applications and demonstrates best practices for handling external APIs and creating responsive user interfaces. 🌤️🐍

Was this page helpful?

Let us know how we did