]> jfr.im git - irc/rizon/acid.git/blame - pyva/pyva/src/main/python/internets/api/weather.py
Split pyva plugin into pyva.core and pyva.pyva
[irc/rizon/acid.git] / pyva / pyva / src / main / python / internets / api / weather.py
CommitLineData
685e346e
A
1# NOTE: You need to have the ZIP code database imported in order for Weather to perform ZIP code lookups
2
3# Code note: In get_conditions/get_forecast, zipcode is used primarily for caching
4# While it would not strictly be necessary for the weather/etc querying to work, this code
5# assumes it is there if a lat/long is given as the 'loctation' input
6
7import datetime as dt
8from feed import get_json, FeedError
9
10
11def c_to_f(temp):
12 """Convert given celcius temperature to farenheit."""
13 return ((9.0 / 5.0) * temp) + 32
14
15
16class Weather(object):
17 def __init__(self, key):
18 self.API_KEY = key
19 self.city_name_cache = {} # cache of names -> city id
20 self.condition_cache = {} # cache of conditions, lasts 10 minutes
21 self.forecast_cache = {} # cache of forecasts, lasts 30 minutes
22 self.last_min_requests = []
23 self.REQ_LIMIT = 99
24
25 def api_request(self, url):
26 """Perform an API request, including respecting sane request limits"""
27 self.last_min_requests = [req for req in self.last_min_requests if (dt.datetime.now() - req).seconds < 60]
28
29 if len(self.last_min_requests) >= self.REQ_LIMIT:
30 raise WeatherException('weather data is temporarily unavailable. Try again later')
31
32 self.last_min_requests.append(dt.datetime.now())
33 try:
34 data = get_json(url)
35 if data['cod'] == '404':
36 raise WeatherException('No cities match your search query')
37 return data
38 except FeedError, e:
39 if getattr(e, 'code', 0) == 512: # openweathermap returns this for no city, apparently. well, used to, that is
40 raise WeatherException('No cities match your search query')
41 else:
42 raise e
43
44 def get_conditions(self, location):
45 """Return weather conditions for given location, using OpenWeatherMap.
46 If location is string, evaluate it as city name.
47 If location is list, evaluate as [latitude, longitude].
48 """
49
50 # id caching
51 location_id = None
52
53 if isinstance(location, str):
54 if location.lower() in self.city_name_cache:
55 location_id = self.city_name_cache[location.lower()]
56
57 # check forecast cache, if we now have a valid ID
58 if location_id is not None:
59 # if we have it in our cache, and it's not expired, use that instead
60 if location_id in self.condition_cache:
61 weather_data = self.condition_cache.get(location_id, {'ts': 1})
62 if ((dt.datetime.now() - weather_data['ts']).seconds / 60) < 10: # 10 minutes
63 return weather_data['conditions']
64
65 # we know that 'xxx' can't be a valid OpenWeatherMap key
66 # and we wanna be nice to these guys, free and open and all
67 if self.API_KEY == 'xxx':
68 raise WeatherException('this key is not valid')
69
70 if location_id is not None:
71 api_data = self.api_request('http://api.openweathermap.org/data/2.5/weather?id={location_id}&APPID={key}&units=metric'.format(key=self.API_KEY, location_id=location_id))
72 elif isinstance(location, str):
73 api_data = self.api_request('http://api.openweathermap.org/data/2.5/weather?q={location}&APPID={key}&units=metric'.format(key=self.API_KEY, location=location))
74 # elif type(location) == list or type(location) == tuple:
75 # api_data = self.api_request('http://api.openweathermap.org/data/2.5/weather?lat={location[0]}&lon={location[1]}&APPID={key}&units=metric'.format(key=self.API_KEY, location=location))
76 else:
77 raise Exception('weather: location type {} not supported'.format(type(location)))
78
79 # set our location cache for next time
80 if isinstance(location, str):
81 self.city_name_cache[location.lower()] = api_data['id']
82
83 # silly hack
84 api_data['weather'][0]['description'] = api_data['weather'][0]['description'].title()
85
86 condition_data = {
87 'id': api_data['id'],
88 'city': api_data['name'] + u', ',
89 'country': api_data['sys']['country'],
90 'description': api_data['weather'][0]['description'].title().replace(' Is ', ' is '),
91 'temp_c': api_data['main']['temp'],
92 'temp_f': c_to_f(api_data['main']['temp']), # convert it manually
93 'pressure': api_data['main']['pressure'],
94 'humidity': api_data['main']['humidity'],
95 'rain': 'No Data Avaliable',
96 }
97
98 # make the country actually consistent
99 if condition_data['country'] in ['United States of America', 'US']:
100 condition_data['country'] = 'USA'
101
102 if not api_data['name']: # this is a country, not a specific city
103 condition_data['city'] = ''
104
105 if 'rain' in api_data: # can be included or not, eg Japan
106 rain_time = api_data['rain'].keys()[0] # since rain time can be '1h', '3h', etc
107 condition_data['rain'] = '{}mm/{}'.format(api_data['rain'][rain_time], rain_time)
108
109 # and set our data, with appropriate timeout
110 self.condition_cache[api_data['id']] = {
111 'ts': dt.datetime.now(),
112 'conditions': condition_data,
113 }
114
115 return condition_data
116
117 def get_forecast(self, location):
118 """Return weather forecast for given location, using OpenWeatherMap.
119 If location is string, evaluate it as city name.
120 If location is list, evaluate as [latitude, longitude].
121 """
122
123 # id caching
124 location_id = None
125
126 if isinstance(location, str):
127 if location.lower() in self.city_name_cache:
128 location_id = self.city_name_cache[location.lower()]
129
130 # check forecast cache, if we now have a valid ID
131 if location_id is not None:
132 # if we have it in our cache, and it's not expired, use that instead
133 if location_id in self.forecast_cache:
134 weather_data = self.forecast_cache.get(location_id, {'ts': 1})
135 if ((dt.datetime.now() - weather_data['ts']).seconds / 60) < 10: # 10 minutes
136 return weather_data['forecast']
137
138 # we know that 'xxx' can't be a valid OpenWeatherMap key
139 # and we wanna be nice to these guys, free and open and all
140 if self.API_KEY == 'xxx':
141 raise WeatherException('this key is not valid')
142
143 if location_id is not None:
144 api_data = self.api_request('http://api.openweathermap.org/data/2.5/forecast?id={location_id}&APPID={key}&units=metric'.format(key=self.API_KEY, location_id=location_id))
145 elif isinstance(location, str):
146 api_data = self.api_request('http://api.openweathermap.org/data/2.5/forecast?q={location}&APPID={key}&units=metric'.format(key=self.API_KEY, location=location))
147 else:
148 raise Exception('get_conditions: location type {} not supported'.format(type(location)))
149
150 # set our location cache for next time
151 if isinstance(location, str):
152 self.city_name_cache[location.lower()] = api_data['city']['id']
153
154 forecast_data = {
155 'id': api_data['city']['id'],
156 'city': api_data['city']['name'] + u', ',
157 'country': api_data['city']['country'],
158 'days': [],
159 }
160
161 # make the country actually consistent
162 if forecast_data['country'] in ['United States of America', 'US']:
163 forecast_data['country'] = 'USA'
164
165 if not api_data['city']['name']: # this is a country, not a specific city
166 forecast_data['city'] = ''
167
168 # assemble and insert specific day data
169 today = dt.datetime.now()
170 i = 0
171
172 for day in api_data['list'][:4]:
173 day_data = {
174 'name': (today + dt.timedelta(days=i)).strftime('%A'),
175 'description': day['weather'][0]['description'].title().replace(' Is ', ' is '),
176 'min_c': int(day['main']['temp_min']),
177 'min_f': int(c_to_f(day['main']['temp_min'])),
178 'max_c': int(day['main']['temp_max']),
179 'max_f': int(c_to_f(day['main']['temp_max'])),
180 }
181
182 forecast_data['days'].append(day_data)
183 i += 1
184
185 # and set our data, with appropriate timeout
186 self.forecast_cache[api_data['city']['id']] = {
187 'ts': dt.datetime.now(),
188 'forecast': forecast_data,
189 }
190
191 return forecast_data
192
193
194class WeatherException(Exception):
195 def __init__(self, msg):
196 self.msg = msg
197
198 def __str__(self):
199 return str(self.msg)