Complete Project 2: Weather App with API

Building a Weather App is like creating your own personal weather station. Instead of building sensors, you connect to existing weather services (APIs) and display the data in a user-friendly way.
The Weather App is a complete, interactive console application that demonstrates how to integrate an external API (simulated), handle JSON data, manage user preferences, and provide a polished user experience. It includes current weather, 3‑day forecasts, temperature unit conversion, recent searches, favorite cities, and weather alerts.

**Core Features:**
- **Current Weather** – Shows temperature (Celsius/Fahrenheit), feels‑like temperature, condition (with emoji), humidity, wind speed, UV index, and last update time.
- **3‑Day Forecast** – Displays max/min temperatures, condition, and special notes (rain expected, hot day, freezing temperatures).
- **Temperature Unit Toggle** – Switch between metric (°C) and imperial (°F); all temperatures convert automatically.
- **Recent Searches** – Stores the last 5 cities searched; allows quick re‑viewing.
- **Favorite Cities** – Add/remove cities to a personal list; view all favourites' current weather in one screen.
- **Weather Alerts** – Simulated alerts (e.g., heavy rain, strong winds) with severity indicators.
- **Error Handling** – Invalid city names, empty input, and menu choices are handled gracefully.

**How It Works (Simulated API):**

The app uses a `weather_data` dictionary containing hard‑coded weather information for New York, London, and Tokyo. This simulates the JSON response you would get from a real weather API like OpenWeatherMap. In a real application, you would replace the `simulate_api_call` method with actual `requests.get()` calls to a live API using an API key.

**Class Structure:**

The `WeatherApp` class encapsulates all functionality. Key methods:
- `__init__` – Initialises units (metric), recent searches, favourites, and the simulated data.
- `simulate_api_call(city)` – Mimics network delay (`time.sleep(1)`), checks if city exists, updates recent searches, and returns the weather data.
- `convert_temperature(temp_c)` – Converts Celsius to Fahrenheit if units are imperial.
- `display_current_weather(city, weather)` – Formats and prints current conditions, including UV warnings and personalised advice.
- `display_forecast(city, weather)` – Loops through the 3‑day forecast, computes weekday names, and adds contextual notes.
- `manage_favorites()` – Sub‑menu to add, remove, or view favourite cities.
- `show_weather_alerts()` – Displays a static list of simulated alerts with severity emojis.
- `run()` – Main menu loop, handling user input (1‑7) and exceptions.

**Temperature Conversion:**

All internal temperatures are stored in Celsius (`temp_c`, `feelslike_c`). The `convert_temperature` method uses the `self.units` flag to return either the original Celsius or the Fahrenheit equivalent (`temp_c * 9/5 + 32`). The unit symbol displayed is updated via `get_temperature_unit()`.

**User Experience Enhancements:**

- Emojis for weather conditions (☀️, ⛅, 🌧️, etc.) make the output more engaging.
- UV index warnings: ≥6 gives moderate protection advice, ≥8 gives high UV alert.
- Weather advice based on condition and temperature (e.g., umbrella for rain, sunscreen for sunny).
- Recent searches can be selected by number to view details without retyping.
- Favourites list allows bulk weather check.

**Error Handling:**

- Empty city names are rejected.
- Non‑existent cities print a friendly message and list available cities.
- Menu choices outside 1‑7 are caught and reprompted.
- KeyboardInterrupt (Ctrl+C) exits gracefully.
- General exceptions are caught and printed without crashing.

**Why This Project Is Important:**

- **API Integration** – Even though it uses simulated data, the structure is identical to real API consumption (fetching data, parsing JSON, handling errors).
- **Data Transformation** – Converting units, formatting dates, adding emojis, generating advice.
- **State Management** – Recent searches and favorites persist during the session (can be extended to file storage).
- **Menu‑Driven Architecture** – A common pattern for utility applications.
- **Extensibility** – You can easily replace the simulated data with live API calls (e.g., using `requests` and a free API key from OpenWeatherMap).

**How to Run:**
Save the code as `weather.py` and run `python weather.py`. No external libraries are required (everything uses standard modules: `json`, `datetime`, `sys`, `time`).

**Potential Enhancements (for practice):**
- Replace simulated data with real API calls (requires `requests` library and an API key).
- Store favorites and recent searches in a JSON file for persistence across sessions.
- Add a 7‑day forecast.
- Include air quality index or precipitation probability.
- Implement geolocation to automatically detect the user's city.
- Add a simple caching mechanism to avoid repeated API calls for the same city.

**Real‑World Skills:**
This project mirrors the architecture of many real‑world applications: fetch data from an external service, transform it for display, manage user preferences, and provide an intuitive interface. It is an excellent portfolio piece for aspiring Python developers interested in APIs and data visualisation.
# WEATHER APP WITH API
import json
import datetime
import sys

print("WEATHER APPLICATION")
print("=" * 50)

class WeatherApp:
    """A weather application using simulated API data"""
    
    def __init__(self):
        self.units = "metric"  # Celsius by default
        self.recent_searches = []
        self.favorite_cities = []
        self.api_key = "demo_key"  # In real app, store securely
        
        # Simulated API data for demonstration
        self.weather_data = {
            "New York": {
                "current": {
                    "temp_c": 15,
                    "temp_f": 59,
                    "condition": "Partly Cloudy",
                    "humidity": 65,
                    "wind_kph": 12,
                    "feelslike_c": 14,
                    "uv": 5,
                    "last_updated": "2024-03-15 14:30"
                },
                "forecast": [
                    {"date": "2024-03-16", "max_temp_c": 16, "min_temp_c": 8, "condition": "Sunny"},
                    {"date": "2024-03-17", "max_temp_c": 14, "min_temp_c": 7, "condition": "Rainy"},
                    {"date": "2024-03-18", "max_temp_c": 12, "min_temp_c": 5, "condition": "Cloudy"}
                ]
            },
            "London": {
                "current": {
                    "temp_c": 10,
                    "temp_f": 50,
                    "condition": "Rainy",
                    "humidity": 85,
                    "wind_kph": 18,
                    "feelslike_c": 8,
                    "uv": 2,
                    "last_updated": "2024-03-15 14:45"
                },
                "forecast": [
                    {"date": "2024-03-16", "max_temp_c": 11, "min_temp_c": 6, "condition": "Cloudy"},
                    {"date": "2024-03-17", "max_temp_c": 12, "min_temp_c": 7, "condition": "Partly Cloudy"},
                    {"date": "2024-03-18", "max_temp_c": 10, "min_temp_c": 5, "condition": "Rainy"}
                ]
            },
            "Tokyo": {
                "current": {
                    "temp_c": 18,
                    "temp_f": 64,
                    "condition": "Sunny",
                    "humidity": 55,
                    "wind_kph": 8,
                    "feelslike_c": 19,
                    "uv": 7,
                    "last_updated": "2024-03-15 15:00"
                },
                "forecast": [
                    {"date": "2024-03-16", "max_temp_c": 19, "min_temp_c": 12, "condition": "Sunny"},
                    {"date": "2024-03-17", "max_temp_c": 17, "min_temp_c": 11, "condition": "Cloudy"},
                    {"date": "2024-03-18", "max_temp_c": 16, "min_temp_c": 10, "condition": "Rainy"}
                ]
            }
        }
    
    def display_menu(self):
        """Display main menu"""
        print("\n" + "☀️" * 20)
        print("WEATHER APP MENU")
        print("☀️" * 20)
        print("1. Check Current Weather")
        print("2. Check Weather Forecast")
        print("3. Toggle Temperature Units (C/F)")
        print("4. Recent Searches")
        print("5. Manage Favorite Cities")
        print("6. Weather Alerts")
        print("7. Exit")
        print("-" * 40)
        print(f"Current units: {'°C' if self.units == 'metric' else '°F'}")
    
    def get_city_input(self):
        """Get city name from user"""
        city = input("\nEnter city name: ").strip().title()
        if not city:
            print("City name cannot be empty!")
            return None
        return city
    
    def simulate_api_call(self, city):
        """Simulate API call to get weather data"""
        # Simulate network delay
        import time
        print(f"Fetching weather data for {city}...")
        time.sleep(1)  # Simulate API delay
        
        if city in self.weather_data:
            # Add to recent searches
            if city not in self.recent_searches:
                self.recent_searches.append(city)
                if len(self.recent_searches) > 5:
                    self.recent_searches.pop(0)
            return self.weather_data[city]
        else:
            # Simulate city not found
            print(f"City '{city}' not found in our database.")
            print("Available cities: New York, London, Tokyo")
            return None
    
    def convert_temperature(self, temp_c):
        """Convert temperature based on selected units"""
        if self.units == "imperial":
            return temp_c * 9/5 + 32
        return temp_c
    
    def get_temperature_unit(self):
        """Get temperature unit symbol"""
        return "°F" if self.units == "imperial" else "°C"
    
    def display_current_weather(self, city, weather):
        """Display current weather information"""
        current = weather["current"]
        
        print("\n" + "=" * 50)
        print(f"🌤  CURRENT WEATHER IN {city.upper()}")
        print("=" * 50)
        
        # Temperature
        temp = self.convert_temperature(current["temp_c"])
        feels_like = self.convert_temperature(current["feelslike_c"])
        unit = self.get_temperature_unit()
        
        print(f"Temperature: {temp:.1f}{unit}")
        print(f"Feels like: {feels_like:.1f}{unit}")
        
        # Weather condition with emoji
        condition = current["condition"]
        condition_emoji = self.get_weather_emoji(condition)
        print(f"Condition: {condition_emoji} {condition}")
        
        # Other details
        print(f"Humidity: {current['humidity']}%")
        print(f"Wind Speed: {current['wind_kph']} km/h")
        print(f"UV Index: {current['uv']}")
        
        # UV index warning
        uv = current["uv"]
        if uv >= 8:
            print("⚠  High UV! Wear sunscreen!")
        elif uv >= 6:
            print("⚠  Moderate UV protection needed")
        
        print(f"Last Updated: {current['last_updated']}")
        print("=" * 50)
        
        # Weather advice
        self.give_weather_advice(condition, temp)
    
    def get_weather_emoji(self, condition):
        """Get emoji for weather condition"""
        emoji_map = {
            "Sunny": "☀️",
            "Partly Cloudy": "⛅",
            "Cloudy": "☁️",
            "Rainy": "🌧️",
            "Snowy": "❄️",
            "Stormy": "⛈️",
            "Windy": "💨",
            "Foggy": "🌫️"
        }
        return emoji_map.get(condition, "🌡️")
    
    def give_weather_advice(self, condition, temperature):
        """Give advice based on weather conditions"""
        print("\n💡 WEATHER ADVICE:")
        
        if "Rain" in condition:
            print("  • Don't forget your umbrella! ☔")
            print("  • Drive carefully on wet roads")
        elif temperature > 30:
            print("  • Stay hydrated! 💧")
            print("  • Avoid outdoor activities during peak heat")
        elif temperature < 5:
            print("  • Bundle up! 🧣🧤")
            print("  • Watch for icy conditions")
        elif "Sunny" in condition:
            print("  • Perfect day for outdoor activities! 🌳")
            print("  • Don't forget sunscreen! ☀️")
        elif "Windy" in condition:
            print("  • Secure loose outdoor items")
            print("  • Be cautious while driving")
        else:
            print("  • Enjoy your day! 😊")
    
    def display_forecast(self, city, weather):
        """Display weather forecast"""
        forecast = weather["forecast"]
        
        print("\n" + "=" * 50)
        print(f"📅 3-DAY FORECAST FOR {city.upper()}")
        print("=" * 50)
        
        for day in forecast:
            date_obj = datetime.datetime.strptime(day["date"], "%Y-%m-%d")
            weekday = date_obj.strftime("%a")  # Monday, Tuesday, etc
            
            max_temp = self.convert_temperature(day["max_temp_c"])
            min_temp = self.convert_temperature(day["min_temp_c"])
            unit = self.get_temperature_unit()
            
            emoji = self.get_weather_emoji(day["condition"])
            
            print(f"\n{weekday} ({day['date']}):")
            print(f"  {emoji} {day['condition']}")
            print(f"  High: {max_temp:.1f}{unit}, Low: {min_temp:.1f}{unit}")
            
            # Special notes
            if "Rain" in day["condition"]:
                print("  ☔ Rain expected")
            elif day["max_temp_c"] > 25:
                print("  🔥 Hot day ahead")
            elif day["min_temp_c"] < 0:
                print("  ❄️ Freezing temperatures overnight")
        
        print("=" * 50)
    
    def toggle_units(self):
        """Toggle between Celsius and Fahrenheit"""
        if self.units == "metric":
            self.units = "imperial"
            print("\n✅ Switched to Fahrenheit (°F)")
        else:
            self.units = "metric"
            print("\n✅ Switched to Celsius (°C)")
    
    def show_recent_searches(self):
        """Show recently searched cities"""
        if not self.recent_searches:
            print("\nNo recent searches.")
            return
        
        print("\n" + "=" * 40)
        print("RECENT SEARCHES")
        print("=" * 40)
        
        for i, city in enumerate(self.recent_searches, 1):
            print(f"{i}. {city}")
            
            # Quick weather summary
            if city in self.weather_data:
                weather = self.weather_data[city]["current"]
                temp = self.convert_temperature(weather["temp_c"])
                unit = self.get_temperature_unit()
                print(f"   {self.get_weather_emoji(weather['condition'])} {temp:.1f}{unit} - {weather['condition']}")
        
        print("=" * 40)
        
        # Option to select from recent
        try:
            choice = input("\nEnter number to view details (or press Enter to skip): ")
            if choice.strip():
                idx = int(choice) - 1
                if 0 <= idx < len(self.recent_searches):
                    city = self.recent_searches[idx]
                    weather = self.simulate_api_call(city)
                    if weather:
                        self.display_current_weather(city, weather)
        except (ValueError, IndexError):
            print("Invalid selection.")
    
    def manage_favorites(self):
        """Manage favorite cities"""
        print("\n" + "=" * 40)
        print("FAVORITE CITIES")
        print("=" * 40)
        
        if not self.favorite_cities:
            print("No favorite cities yet.")
        else:
            print("Your favorite cities:")
            for i, city in enumerate(self.favorite_cities, 1):
                print(f"{i}. {city}")
        
        print("\nOptions:")
        print("1. Add favorite city")
        print("2. Remove favorite city")
        print("3. View weather for favorites")
        print("4. Back to main menu")
        
        choice = input("\nEnter choice: ").strip()
        
        if choice == "1":
            city = self.get_city_input()
            if city and city not in self.favorite_cities:
                self.favorite_cities.append(city)
                print(f"✅ Added {city} to favorites!")
        elif choice == "2":
            if self.favorite_cities:
                for i, city in enumerate(self.favorite_cities, 1):
                    print(f"{i}. {city}")
                try:
                    idx = int(input("Enter number to remove: ")) - 1
                    if 0 <= idx < len(self.favorite_cities):
                        removed = self.favorite_cities.pop(idx)
                        print(f"✅ Removed {removed} from favorites.")
                except (ValueError, IndexError):
                    print("Invalid selection.")
            else:
                print("No favorites to remove.")
        elif choice == "3":
            if not self.favorite_cities:
                print("No favorite cities to display.")
            else:
                print("\nWeather for your favorite cities:")
                print("-" * 40)
                for city in self.favorite_cities:
                    if city in self.weather_data:
                        weather = self.weather_data[city]
                        temp = self.convert_temperature(weather["current"]["temp_c"])
                        unit = self.get_temperature_unit()
                        condition = weather["current"]["condition"]
                        emoji = self.get_weather_emoji(condition)
                        print(f"{city}: {emoji} {temp:.1f}{unit} - {condition}")
                    else:
                        print(f"{city}: Data not available")
        elif choice == "4":
            return
        else:
            print("Invalid choice.")
    
    def show_weather_alerts(self):
        """Show weather alerts"""
        print("\n" + "=" * 50)
        print("⚠  WEATHER ALERTS")
        print("=" * 50)
        
        # Simulated alerts
        alerts = [
            {"city": "London", "type": "Rain", "severity": "Moderate", "message": "Heavy rain expected tonight"},
            {"city": "New York", "type": "Wind", "severity": "High", "message": "Strong winds expected tomorrow"}
        ]
        
        if alerts:
            for alert in alerts:
                severity_emoji = "🟡" if alert["severity"] == "Moderate" else "🔴"
                print(f"\n{severity_emoji} {alert['city']} - {alert['type']} Alert ({alert['severity']})")
                print(f"   {alert['message']}")
                print(f"   Issued: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}")
        else:
            print("\n✅ No active weather alerts in your area.")
        
        print("\n" + "=" * 50)
    
    def check_current_weather(self):
        """Check current weather for a city"""
        city = self.get_city_input()
        if not city:
            return
        
        weather = self.simulate_api_call(city)
        if weather:
            self.display_current_weather(city, weather)
    
    def check_weather_forecast(self):
        """Check weather forecast for a city"""
        city = self.get_city_input()
        if not city:
            return
        
        weather = self.simulate_api_call(city)
        if weather:
            self.display_forecast(city, weather)
    
    def run(self):
        """Run the weather application"""
        print("\n🌤️  Welcome to the Weather App!")
        print("Get accurate weather information for any city.")
        
        while True:
            self.display_menu()
            
            try:
                choice = input("\nEnter your choice (1-7): ").strip()
                
                if choice == "1":
                    self.check_current_weather()
                elif choice == "2":
                    self.check_weather_forecast()
                elif choice == "3":
                    self.toggle_units()
                elif choice == "4":
                    self.show_recent_searches()
                elif choice == "5":
                    self.manage_favorites()
                elif choice == "6":
                    self.show_weather_alerts()
                elif choice == "7":
                    print("\n👋 Thank you for using Weather App!")
                    print("Stay safe and have a great day! 🌈")
                    break
                else:
                    print("\n❌ Invalid choice. Please enter 1-7.")
            except KeyboardInterrupt:
                print("\n\n👋 Goodbye!")
                break
            except Exception as e:
                print(f"\n❌ An error occurred: {e}")

# Run the application
if __name__ == "__main__":
    app = WeatherApp()
    app.run()

→ Run this code interactively