]>
jfr.im git - irc/rizon/acid.git/blob - pyva/pyva/src/main/python/internets/api/weather.py
15b37dc6fdfc5f894ecdf6e7392677befcf853fd
1 # NOTE: You need to have the ZIP code database imported in order for Weather to perform ZIP code lookups
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
8 from feed
import get_json
, FeedError
12 """Convert given celcius temperature to farenheit."""
13 return ((9.0 / 5.0) * temp
) + 32
16 class Weather(object):
17 def __init__(self
, 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
= []
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]
29 if len(self
.last_min_requests
) >= self
.REQ_LIMIT
:
30 raise WeatherException('weather data is temporarily unavailable. Try again later')
32 self
.last_min_requests
.append(dt
.datetime
.now())
35 if data
['cod'] == '404':
36 raise WeatherException('No cities match your search query')
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')
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].
53 if isinstance(location
, str):
54 if location
.lower() in self
.city_name_cache
:
55 location_id
= self
.city_name_cache
[location
.lower()]
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']
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')
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))
77 raise Exception('weather: location type {} not supported'.format(type(location
)))
79 # set our location cache for next time
80 if isinstance(location
, str):
81 self
.city_name_cache
[location
.lower()] = api_data
['id']
84 api_data
['weather'][0]['description'] = api_data
['weather'][0]['description'].title()
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',
98 # make the country actually consistent
99 if condition_data
['country'] in ['United States of America', 'US']:
100 condition_data
['country'] = 'USA'
102 if not api_data
['name']: # this is a country, not a specific city
103 condition_data
['city'] = ''
105 if 'rain' in api_data
: # can be included or not, eg Japan
107 rain_time
= api_data
['rain'].keys()[0] # since rain time can be '1h', '3h', etc
108 condition_data
['rain'] = '{}mm/{}'.format(api_data
['rain'][rain_time
], rain_time
)
110 condition_data
['rain'] = 'Not Raining'
112 # and set our data, with appropriate timeout
113 self
.condition_cache
[api_data
['id']] = {
114 'ts': dt
.datetime
.now(),
115 'conditions': condition_data
,
118 return condition_data
120 def get_forecast(self
, location
):
121 """Return weather forecast for given location, using OpenWeatherMap.
122 If location is string, evaluate it as city name.
123 If location is list, evaluate as [latitude, longitude].
129 if isinstance(location
, str):
130 if location
.lower() in self
.city_name_cache
:
131 location_id
= self
.city_name_cache
[location
.lower()]
133 # check forecast cache, if we now have a valid ID
134 if location_id
is not None:
135 # if we have it in our cache, and it's not expired, use that instead
136 if location_id
in self
.forecast_cache
:
137 weather_data
= self
.forecast_cache
.get(location_id
, {'ts': 1}
)
138 if ((dt
.datetime
.now() - weather_data
['ts']).seconds
/ 60) < 10: # 10 minutes
139 return weather_data
['forecast']
141 # we know that 'xxx' can't be a valid OpenWeatherMap key
142 # and we wanna be nice to these guys, free and open and all
143 if self
.API_KEY
== 'xxx':
144 raise WeatherException('this key is not valid')
146 if location_id
is not None:
147 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
))
148 elif isinstance(location
, str):
149 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
))
151 raise Exception('get_conditions: location type {} not supported'.format(type(location
)))
153 # set our location cache for next time
154 if isinstance(location
, str):
155 self
.city_name_cache
[location
.lower()] = api_data
['city']['id']
158 'id': api_data
['city']['id'],
159 'city': api_data
['city']['name'] + u
', ',
160 'country': api_data
['city']['country'],
164 # make the country actually consistent
165 if forecast_data
['country'] in ['United States of America', 'US']:
166 forecast_data
['country'] = 'USA'
168 if not api_data
['city']['name']: # this is a country, not a specific city
169 forecast_data
['city'] = ''
171 # assemble and insert specific day data
172 today
= dt
.datetime
.now()
175 for day
in api_data
['list'][:4]:
177 'name': (today
+ dt
.timedelta(days
=i
)).strftime('%A'),
178 'description': day
['weather'][0]['description'].title().replace(' Is ', ' is '),
179 'min_c': int(day
['main']['temp_min']),
180 'min_f': int(c_to_f(day
['main']['temp_min'])),
181 'max_c': int(day
['main']['temp_max']),
182 'max_f': int(c_to_f(day
['main']['temp_max'])),
185 forecast_data
['days'].append(day_data
)
188 # and set our data, with appropriate timeout
189 self
.forecast_cache
[api_data
['city']['id']] = {
190 'ts': dt
.datetime
.now(),
191 'forecast': forecast_data
,
197 class WeatherException(Exception):
198 def __init__(self
, msg
):