]>
jfr.im git - irc/rizon/acid.git/blob - pyva/src/main/python/internets/api/weather.py
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
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
)
109 # and set our data, with appropriate timeout
110 self
.condition_cache
[api_data
['id']] = {
111 'ts': dt
.datetime
.now(),
112 'conditions': condition_data
,
115 return condition_data
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].
126 if isinstance(location
, str):
127 if location
.lower() in self
.city_name_cache
:
128 location_id
= self
.city_name_cache
[location
.lower()]
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']
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')
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
))
148 raise Exception('get_conditions: location type {} not supported'.format(type(location
)))
150 # set our location cache for next time
151 if isinstance(location
, str):
152 self
.city_name_cache
[location
.lower()] = api_data
['city']['id']
155 'id': api_data
['city']['id'],
156 'city': api_data
['city']['name'] + u
', ',
157 'country': api_data
['city']['country'],
161 # make the country actually consistent
162 if forecast_data
['country'] in ['United States of America', 'US']:
163 forecast_data
['country'] = 'USA'
165 if not api_data
['city']['name']: # this is a country, not a specific city
166 forecast_data
['city'] = ''
168 # assemble and insert specific day data
169 today
= dt
.datetime
.now()
172 for day
in api_data
['list'][:4]:
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'])),
182 forecast_data
['days'].append(day_data
)
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
,
194 class WeatherException(Exception):
195 def __init__(self
, msg
):