PageRenderTime 41ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/pywunderground/weather_underground.py

https://gitlab.com/cburki/python_wunderground
Python | 361 lines | 109 code | 74 blank | 178 comment | 8 complexity | c5caa694d4606480b769d6c92714bfb3 MD5 | raw file
  1. #
  2. # Filename : weather_underground.py
  3. # Description :
  4. # Author : Christophe Burki
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License version 3 as
  8. # published by the Free Software Foundation.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; see the file LICENSE. If not, write to the
  17. # Free Software Foundation, Inc., 51 Franklin Street, Fifth
  18. # ;; Floor, Boston, MA 02110-1301, USA.
  19. # ------------------------------------------------------------------------------
  20. from urllib3 import PoolManager
  21. from urllib3.exceptions import ConnectTimeoutError, ReadTimeoutError
  22. from json import loads, dumps
  23. from pywunderground.observation_parser import ObservationParser
  24. from pywunderground.daily_forecast_parser import DailyForecastParser
  25. from pywunderground.hourly_forecast_parser import HourlyForecastParser
  26. from pywunderground.astronomy_parser import AstronomyParser
  27. from pywunderground.exceptions import APICallError, APIResponseError
  28. # ------------------------------------------------------------------------------
  29. class WeatherUnderground:
  30. """
  31. Class providing methods for querying the API.
  32. """
  33. URL = 'http://api.wunderground.com/api/{apikey}/{features}/q/{location}.json'
  34. QUERY_TIMEOUT = 30
  35. FEATURES = ['conditions', 'forecast', 'forecast10day', 'hourly', 'astronomy', 'geolookup']
  36. API_FEATURES = {'conditions': ('observation', 'current_observation'),
  37. 'forecast': ('daily_forecast', 'forecast'),
  38. 'forecast10day': ('daily_forecast', 'forecast'),
  39. 'hourly': ('hourly_forecast', 'hourly_forecast'),
  40. 'astronomy': ('astronomy', 'moon_phase')}
  41. # --------------------------------------------------------------------------
  42. def __init__(self, apiKey, cache):
  43. """
  44. Constructor
  45. :param apiKey: The weather underground API key
  46. :type apiKey: string
  47. :param cache:
  48. :type cache:
  49. """
  50. self._apiKey = apiKey
  51. self._cache = cache
  52. self._api = PoolManager(timeout=WeatherUnderground.QUERY_TIMEOUT)
  53. # --------------------------------------------------------------------------
  54. def __del__(self):
  55. """
  56. Destructor
  57. """
  58. self._api.clear()
  59. # --------------------------------------------------------------------------
  60. def weatherAtPlace(self, place):
  61. """
  62. Query the WU API for the weather currently observed at the given place.
  63. :param place: The location where to observe weather (country,city)
  64. :type place: string
  65. """
  66. data = self._query('conditions/forecast/astronomy', place)
  67. observation = ObservationParser.parse(data)
  68. return observation
  69. # --------------------------------------------------------------------------
  70. def weatherAtCoords(self, latitude, longitude):
  71. """
  72. Query the WU API for the weather currently observed at the given
  73. geographic location.
  74. :param latitude: The location latitude
  75. :type latitude: float
  76. :param longitude: The location longitude
  77. :type longitude: float
  78. """
  79. data = self._query('conditions/forecast/astronomy', '{},{}'.format(latitude, longitude))
  80. observation = ObservationParser.parse(data)
  81. return observation
  82. # --------------------------------------------------------------------------
  83. def weatherAtAirport(self, code):
  84. """
  85. Query the WU API for the weather currently observed at the given
  86. airport code.
  87. :param code: The airport code where to observe weather
  88. :type code: string
  89. """
  90. data = self._query('conditions/forecast/astronomy', code)
  91. observation = ObservationParser.parse(data)
  92. return observation
  93. # --------------------------------------------------------------------------
  94. def weatherAtPWSId(self, id):
  95. """
  96. Query the WU API for the weather currently observed at the given PWS
  97. (Personal Weather Station) ID.
  98. :param id: The PWS identifier where to observe the weather
  99. :type id: string
  100. """
  101. data = self._query('conditions/forecast/astronomy', 'pws:{}'.format(id))
  102. observation = ObservationParser.parse(data)
  103. return observation
  104. # --------------------------------------------------------------------------
  105. def dailyForecastAtPlace(self, place):
  106. """
  107. Query the WU API for the daily weather forecast at the given place.
  108. :param place: The location where to observe weather (country,city)
  109. :type place: string
  110. """
  111. data = self._query('conditions/forecast10day', place)
  112. forecast = DailyForecastParser.parse(data)
  113. return forecast
  114. # --------------------------------------------------------------------------
  115. def dailyForecastAtCoords(self, latitude, longitude):
  116. """
  117. Query the WU API for the daily weather forecast at the given
  118. geographic location.
  119. :param latitude: The location latitude
  120. :type latitude: float
  121. :param longitude: The location longitude
  122. :type longitude: float
  123. """
  124. data = self._query('conditions/forecast10day', '{},{}'.format(latitude, longitude))
  125. forecast = DailyForecastParser.parse(data)
  126. return forecast
  127. # --------------------------------------------------------------------------
  128. def dailyForecastAtAirport(self, code):
  129. """
  130. Query the WU API for the daily weather forecast at the given
  131. airport code.
  132. :param code: The airport code where to observe weather
  133. :type code: string
  134. """
  135. data = self._query('conditions/forecast10day', code)
  136. forecast = DailyForecastParser.parse(data)
  137. return forecast
  138. # --------------------------------------------------------------------------
  139. def dailyForecastAtPWSId(self, id):
  140. """
  141. Query the WU API for the daily weather forecast at the given PWS
  142. (Personal Weather Station) ID.
  143. :param id: The PWS (Personal Weather Station) identifier where to observe the weather
  144. :type id: string
  145. """
  146. data = self._query('conditions/forecast10day', 'pws:{}'.format(id))
  147. forecast = DailyForecastParser.parse(data)
  148. return forecast
  149. # --------------------------------------------------------------------------
  150. def hourlyForecastAtPlace(self, place):
  151. """
  152. Query the WU API for the hourly weather forecast at the given place.
  153. :param place: The location where to observe weather (country/city)
  154. :type place: string
  155. """
  156. data = self._query('conditions/hourly', place)
  157. forecast = HourlyForecastParser.parse(data)
  158. return forecast
  159. # --------------------------------------------------------------------------
  160. def hourlyForecastAtCoords(self, latitude, longitude):
  161. """
  162. Query the WU API for the hourly weather forecast at the given
  163. geographic location.
  164. :param latitude: The location latitude
  165. :type latitude: float
  166. :param longitude: The location longitude
  167. :type longitude: float
  168. """
  169. data = self._query('conditions/hourly', '{},{}'.format(latitude, longitude))
  170. forecast = HourlyForecastParser.parse(data)
  171. return forecast
  172. # --------------------------------------------------------------------------
  173. def hourlyForecastAtAirport(self, code):
  174. """
  175. Query the WU API for the hourly weather forecast at the given
  176. airport code.
  177. :param code: The airport code where to observe weather
  178. :type code: string
  179. """
  180. data = self._query('conditions/hourly', code)
  181. forecast = HourlyForecastParser.parse(data)
  182. return forecast
  183. # --------------------------------------------------------------------------
  184. def hourlyForecastAtPWSId(self, id):
  185. """
  186. Query the WU API for the hourly weather forecast at the given PWS
  187. (Personal Weather Station) ID.
  188. :param id: The PWS (Personal Weather Station) identifier where to observe the weather
  189. :type id: string
  190. """
  191. data = self._query('conditions/hourly', 'pws:{}'.format(id))
  192. forecast = HourlyForecastParser.parse(data)
  193. return forecast
  194. # --------------------------------------------------------------------------
  195. def astronomyAtPlace(self, place):
  196. """
  197. Query the WU API for astronomy at the given place.
  198. :param place: The location where to get the astronomy (country/city)
  199. :type place: string
  200. """
  201. data = self._query('astronomy', place)
  202. astronomy = AstronomyParser.parse(data)
  203. return astronomy
  204. # --------------------------------------------------------------------------
  205. def astronomyAtCoords(self, latitude, longitude):
  206. """
  207. Query the WU API for the astronomy at the given geographic location.
  208. :param latitude: The location latitude
  209. :type latitude: float
  210. :param longitude: The location longitude
  211. :type longitude: float
  212. """
  213. data = self._query('astronomy', '{},{}'.format(latitude, longitude))
  214. astronomy = AstronomyParser.parse(data)
  215. return astronomy
  216. # --------------------------------------------------------------------------
  217. def astronomyAtAirport(self, code):
  218. """
  219. Query the WU API for the astronomy at the given airport code.
  220. :param code: The airport code where to get the astronomy
  221. :type code: string
  222. """
  223. data = self._query('astronomy', code)
  224. astronomy = AstronomyParser.parse(data)
  225. return astronomy
  226. # --------------------------------------------------------------------------
  227. def astronomyAtPWSId(self, id):
  228. """
  229. Query the WU API for the astronomy at the given PWS (Personal Weather
  230. Station) ID.
  231. :param id: The PWS (Personal Weather Station) identifier where to get the astronomy
  232. :type id: string
  233. """
  234. data = self._query('astronomy', 'pws:{}'.format(id))
  235. astronomy = AstronomyParser.parse(data)
  236. return astronomy
  237. # --------------------------------------------------------------------------
  238. def _query(self, features, location):
  239. """
  240. Query the API for the given features and location.
  241. :param features: Features formatted string to query
  242. :type features: string
  243. :param location: The location for which to make the query
  244. :type location: string
  245. """
  246. url = WeatherUnderground.URL.format(apikey=self._apiKey, features=features, location=location)
  247. try:
  248. request = self._api.request('GET', url)
  249. except ConnectTimeoutError as e:
  250. raise APICallError('Timeout error while querying API', e)
  251. except ReadTimeoutError as e:
  252. raise APICallError('Timeout error while querying API', e)
  253. except Exception as e:
  254. raise APICallError('Error while querying API', e)
  255. if request.status != 200:
  256. raise APIResponseError('Error during HTTP request', request.status)
  257. apiData = loads(request.data.decode('utf-8'))
  258. # print("apiData={}".format(apiData))
  259. if 'error' in apiData['response']:
  260. error = apiData['response']['error']
  261. raise APIResponseError('Error from API', error['type'])
  262. if len(apiData['response']['features']) != len(features.split('/')):
  263. raise APIResponseError('Not all features retrieved', dumps(apiData['response']['features']))
  264. # Keep only the data for the requested features.
  265. data = {}
  266. for feature in features.split('/'):
  267. data[WeatherUnderground.API_FEATURES[feature][0]] = apiData[WeatherUnderground.API_FEATURES[feature][1]]
  268. return data
  269. # ------------------------------------------------------------------------------