Update weather.com weather backend to reflect API changes (#783)

This commit is contained in:
Erik Johnson 2020-06-12 10:31:28 -05:00 committed by GitHub
parent e0234c1223
commit dad5eb0c5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -67,52 +67,26 @@ class WeathercomHTMLParser(HTMLParser):
# Look for feed information embedded as a javascript variable
begin = content.find('window.__data')
if begin != -1:
weather_data = None
self.logger.debug('Located window.__data')
# Look for end of JSON dict and end of javascript statement
end = content.find('};', begin)
if end == -1:
self.logger.debug('Failed to locate end of javascript statement')
else:
# Strip the "window.__data=" from the beginning
json_data = self.load_json(
content[begin:end + 1].split('=', 1)[1].lstrip()
# Strip the "window.__data=" from the beginning and load json
json_data = self.load_json(
content[begin:].split('=', 1)[1].lstrip()
)
if json_data is not None:
try:
weather_data = json_data['dal']
except KeyError:
pass
if weather_data is None:
self.logger.debug(
'Failed to locate weather data in the '
'following data: %s', json_data
)
if json_data is not None:
def _find_weather_data(data):
'''
Helper designed to minimize impact of potential
structural changes to this data.
'''
if isinstance(data, dict):
if 'Observation' in data and 'DailyForecast' in data:
return data
else:
for key in data:
ret = _find_weather_data(data[key])
if ret is not None:
return ret
return None
weather_data = _find_weather_data(json_data)
if weather_data is None:
self.logger.debug(
'Failed to locate weather data in the '
'following data structure: %s', json_data
)
else:
self.weather_data = weather_data
return
for line in content.splitlines():
line = line.strip().rstrip(';')
if line.startswith('var adaptorParams'):
# Strip off the "var adaptorParams = " from the beginning,
# and the javascript semicolon from the end. This will give
# us JSON that we can load.
weather_data = self.load_json(line.split('=', 1)[1].lstrip())
if weather_data is not None:
self.weather_data = weather_data
return
else:
self.weather_data = weather_data
return
class Weathercom(WeatherBackend):
@ -216,15 +190,15 @@ class Weathercom(WeatherBackend):
return
try:
observed = self.parser.weather_data['Observation']
observed = self.parser.weather_data['getSunV3CurrentObservationsUrlConfig']
# Observation data stored under a sub-key containing the
# lat/long coordinates. For example:
# lat/long coordinates, locale info, etc. For example:
#
# geocode:41.77,-88.35:language:en-US:units:e
#
# Since this is the only key under "Observation", we can just
# use next(iter(observed)) to get it.
observed = observed[next(iter(observed))]['data']['vt1observation']
observed = observed[next(iter(observed))]['data']
except KeyError:
self.logger.error(
'Failed to retrieve current conditions from API response. '
@ -234,11 +208,10 @@ class Weathercom(WeatherBackend):
return
try:
forecast = self.parser.weather_data['DailyForecast']
forecast = self.parser.weather_data['getSunV3DailyForecastUrlConfig']
# Same as above, use next(iter(forecast)) to drill down to the
# correct nested dict level.
forecast = forecast[next(iter(forecast))]
forecast = forecast['data']['vt1dailyForecast'][0]
forecast = forecast[next(iter(forecast))]['data']
except (IndexError, KeyError):
self.logger.error(
'Failed to retrieve forecast data from API response. '
@ -248,11 +221,11 @@ class Weathercom(WeatherBackend):
return
try:
self.city_name = self.parser.weather_data['Location']
location = self.parser.weather_data['getSunV3LocationPointUrlConfig']
# Again, same technique as above used to get down to the
# correct nested dict level.
self.city_name = self.city_name[next(iter(self.city_name))]
self.city_name = self.city_name['data']['location']['displayName']
location = location[next(iter(location))]
self.city_name = location['data']['location']['displayName']
except KeyError:
self.logger.warning(
'Failed to get city name from API response, falling back '
@ -260,19 +233,15 @@ class Weathercom(WeatherBackend):
)
self.city_name = self.location_code
# Cut off the timezone from the end of the string (it's after the last
# space, hence the use of rpartition). International timezones (or ones
# outside the system locale) don't seem to be handled well by
# datetime.datetime.strptime().
try:
observation_time_str = str(observed.get('observationTime', ''))
observation_time_str = str(observed.get('validTimeLocal', ''))
observation_time = datetime.strptime(observation_time_str,
'%Y-%d-%yT%H:%M:%S%z')
except (ValueError, AttributeError):
observation_time = datetime.fromtimestamp(0)
try:
pressure_trend_str = observed.get('barometerTrend', '').lower()
pressure_trend_str = observed.get('pressureTendencyTrend', '').lower()
except AttributeError:
pressure_trend_str = ''
@ -283,18 +252,14 @@ class Weathercom(WeatherBackend):
else:
pressure_trend = ''
self.logger.critical('forecast = %s', forecast)
try:
high_temp = forecast.get('day', {}).get('temperature', '')
high_temp = forecast.get('temperatureMax', [])[0] or ''
except (AttributeError, IndexError):
high_temp = ''
else:
if high_temp is None:
# In the mid-afternoon, the high temp disappears from the
# forecast, so just set high_temp to an empty string.
high_temp = ''
try:
low_temp = forecast.get('night', {}).get('temperature', '')
low_temp = forecast.get('temperatureMin', [])[0]
except (AttributeError, IndexError):
low_temp = ''
@ -310,25 +275,25 @@ class Weathercom(WeatherBackend):
visibility_unit = 'km'
self.data['city'] = self.city_name
self.data['condition'] = str(observed.get('phrase', ''))
self.data['condition'] = str(observed.get('wxPhraseMedium', ''))
self.data['observation_time'] = observation_time
self.data['current_temp'] = str(observed.get('temperature', ''))
self.data['low_temp'] = str(low_temp)
self.data['high_temp'] = str(high_temp)
self.data['temp_unit'] = temp_unit
self.data['feelslike'] = str(observed.get('feelsLike', ''))
self.data['dewpoint'] = str(observed.get('dewPoint', ''))
self.data['feelslike'] = str(observed.get('temperatureFeelsLike', ''))
self.data['dewpoint'] = str(observed.get('temperatureDewPoint', ''))
self.data['wind_speed'] = str(observed.get('windSpeed', ''))
self.data['wind_unit'] = wind_unit
self.data['wind_direction'] = str(observed.get('windDirCompass', ''))
self.data['wind_direction'] = str(observed.get('windDirectionCardinal', ''))
# Gust can be None, using "or" to ensure empty string in this case
self.data['wind_gust'] = str(observed.get('gust', '') or '')
self.data['pressure'] = str(observed.get('altimeter', ''))
self.data['wind_gust'] = str(observed.get('windGust', '') or '')
self.data['pressure'] = str(observed.get('pressureAltimeter', ''))
self.data['pressure_unit'] = pressure_unit
self.data['pressure_trend'] = pressure_trend
self.data['visibility'] = str(observed.get('visibility', ''))
self.data['visibility_unit'] = visibility_unit
self.data['humidity'] = str(observed.get('humidity', ''))
self.data['humidity'] = str(observed.get('relativeHumidity', ''))
self.data['uv_index'] = str(observed.get('uvIndex', ''))
except Exception:
# Don't let an uncaught exception kill the update thread