File size: 7,509 Bytes
b30f068
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
"""
OpenWeatherMap API Client
Fetches current weather data for any location
"""

from src.api_clients.base_client import BaseAPIClient
from src.config.credentials import CredentialsManager
from typing import Dict, Any, Optional
import requests


class WeatherClient(BaseAPIClient):
    """
    OpenWeatherMap API Client
    
    Provides current weather data including:
    - Temperature
    - Humidity
    - Wind speed
    - Weather description
    - Coordinates
    
    Usage:
        client = WeatherClient()
        weather = client.get_weather(city="London")
        print(f"Temperature: {weather['temperature']}°C")
    """
    
    def __init__(self, api_key: Optional[str] = None):
        """
        Initialize weather client
        
        Args:
            api_key: OpenWeatherMap API key (optional, loads from .env if not provided)
        """
        super().__init__()
        
        # Get API key
        if api_key is None:
            creds = CredentialsManager()
            api_key = creds.get_api_key("openweather")
        
        self.api_key = api_key
        self.base_url = "https://api.openweathermap.org/data/2.5"
    
    def _validate_params(self, **kwargs) -> bool:
        """
        Validate request parameters
        
        Args:
            **kwargs: Parameters to validate
        
        Returns:
            True if valid
        
        Raises:
            ValueError: If parameters are invalid
        """
        if "city" not in kwargs and ("lat" not in kwargs or "lon" not in kwargs):
            raise ValueError(
                "Either 'city' or both 'lat' and 'lon' must be provided"
            )
        return True
    
    def get_weather(
        self,
        city: Optional[str] = None,
        lat: Optional[float] = None,
        lon: Optional[float] = None,
        units: str = "metric",
        country_code: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Get current weather for a location
        
        Args:
            city: City name (e.g., "London", "New York")
            lat: Latitude (alternative to city)
            lon: Longitude (alternative to city)
            units: Temperature units ("metric" for Celsius, "imperial" for Fahrenheit)
            country_code: ISO 3166 country code (e.g., "US", "GB")
        
        Returns:
            Dict with weather information:
            {
                "success": True,
                "city": "London",
                "country": "GB",
                "temperature": 15.5,
                "feels_like": 14.2,
                "humidity": 72,
                "pressure": 1013,
                "wind_speed": 5.2,
                "wind_direction": 250,
                "description": "partly cloudy",
                "icon": "☁️",
                "lat": 51.5074,
                "lon": -0.1278,
                "timestamp": 1699632000
            }
        
        Raises:
            ValueError: If parameters are invalid
            requests.exceptions.RequestException: If API request fails
        """
        # Validate parameters
        self._validate_params(city=city, lat=lat, lon=lon)
        
        # Build query parameters
        params = {
            "appid": self.api_key,
            "units": units
        }
        
        # Add location parameters
        if city:
            query = f"{city},{country_code}" if country_code else city
            params["q"] = query
        else:
            params["lat"] = lat
            params["lon"] = lon
        
        # Make API request
        try:
            endpoint = f"{self.base_url}/weather"
            data = self.get(endpoint, params=params)
            
            # Format response
            return self._format_response(data, units)
            
        except requests.exceptions.RequestException as e:
            return {
                "success": False,
                "error": str(e)
            }
    
    def _format_response(self, data: Dict, units: str) -> Dict[str, Any]:
        """
        Format OpenWeatherMap API response
        
        Args:
            data: Raw API response
            units: Temperature units
        
        Returns:
            Formatted weather data
        """
        # Determine temperature unit symbol
        temp_unit = "°C" if units == "metric" else "°F"
        
        # Get weather icon emoji
        weather_id = data["weather"][0]["id"]
        icon = self._get_weather_icon(weather_id)
        
        return {
            "success": True,
            "city": data["name"],
            "country": data["sys"].get("country", ""),
            "temperature": round(data["main"]["temp"], 1),
            "feels_like": round(data["main"]["feels_like"], 1),
            "temp_min": round(data["main"]["temp_min"], 1),
            "temp_max": round(data["main"]["temp_max"], 1),
            "humidity": data["main"]["humidity"],
            "pressure": data["main"]["pressure"],
            "wind_speed": round(data["wind"]["speed"], 1),
            "wind_direction": data["wind"].get("deg", 0),
            "description": data["weather"][0]["description"],
            "icon": icon,
            "icon_code": data["weather"][0]["icon"],
            "lat": data["coord"]["lat"],
            "lon": data["coord"]["lon"],
            "timestamp": data["dt"],
            "timezone": data.get("timezone", 0),
            "temp_unit": temp_unit,
            "units": units
        }
    
    def _get_weather_icon(self, weather_id: int) -> str:
        """
        Get emoji icon for weather condition
        
        Args:
            weather_id: OpenWeatherMap weather condition ID
        
        Returns:
            Emoji string
        """
        # Weather ID ranges
        if 200 <= weather_id < 300:
            return "⛈️"  # Thunderstorm
        elif 300 <= weather_id < 400:
            return "🌦️"  # Drizzle
        elif 500 <= weather_id < 600:
            return "🌧️"  # Rain
        elif 600 <= weather_id < 700:
            return "❄️"  # Snow
        elif 700 <= weather_id < 800:
            return "🌫️"  # Atmosphere (fog, mist, etc.)
        elif weather_id == 800:
            return "☀️"  # Clear
        elif 801 <= weather_id < 900:
            return "☁️"  # Clouds
        else:
            return "🌤️"  # Default


# Test function
if __name__ == "__main__":
    print("Testing Weather Client...")
    print("-" * 50)
    
    try:
        client = WeatherClient()
        
        # Test with a city
        print("\n📍 Testing with city: London")
        weather = client.get_weather(city="London")
        
        if weather.get("success"):
            print(f"✅ Success!")
            print(f"🌍 Location: {weather['city']}, {weather['country']}")
            print(f"🌡️  Temperature: {weather['temperature']}{weather['temp_unit']}")
            print(f"🤔 Feels like: {weather['feels_like']}{weather['temp_unit']}")
            print(f"💧 Humidity: {weather['humidity']}%")
            print(f"💨 Wind: {weather['wind_speed']} m/s")
            print(f"{weather['icon']} {weather['description'].title()}")
        else:
            print(f"❌ Error: {weather.get('error')}")
    
    except Exception as e:
        print(f"❌ Error: {e}")
        print("\n💡 Make sure you have:")
        print("   1. Created .env file with OPENWEATHER_API_KEY")
        print("   2. Installed dependencies: python-dotenv, requests")