Update Weather Underground weather backend to reflect API change (#816)

* Update Weather Underground weather backend to reflect API change

Also change weather backends to use f-strings instead of
percent-replacement in log messages, now that i3pystatus is Python 3.6+.

* Address pycodestyle failure

* Remove outdated config items
This commit is contained in:
Erik Johnson 2021-04-29 21:48:26 -05:00 committed by GitHub
parent cf3123b415
commit 3bc23608fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 63 deletions

View File

@ -112,15 +112,9 @@ class NBA(ScoresBackend):
'exists primarily for troubleshooting purposes.'),
('live_url', 'URL string to launch NBA Game Tracker. This value '
'should not need to be changed.'),
('scoreboard_url', 'Link to the NBA.com scoreboard page. Like '
'**live_url**, this value should not need to be '
'changed.'),
('api_url', 'Alternate URL string from which to retrieve score data. '
'Like, **live_url**, this value should not need to be '
'changed.'),
('standings_url', 'Alternate URL string from which to retrieve team '
'standings. Like **live_url**, this value should '
'not need to be changed.'),
)
required = ()

View File

@ -24,30 +24,26 @@ class WeatherBackend(SettingsBase):
@require(internet)
def api_request(self, url, headers=None):
self.logger.debug('Making API request to %s', url)
self.logger.debug(f'Making API request to {url}')
try:
response_json = self.http_request(url, headers=headers).strip()
if not response_json:
self.logger.debug('JSON response from %s was blank', url)
self.logger.debug(f'JSON response from {url} was blank')
return {}
try:
response = json.loads(response_json)
except json.decoder.JSONDecodeError as exc:
self.logger.error('Error loading JSON: %s', exc)
self.logger.debug('JSON text that failed to load: %s',
response_json)
self.logger.error(f'Error loading JSON: {exc}')
self.logger.debug(f'JSON text that failed to load: {response_json}')
return {}
self.logger.log(5, 'API response: %s', response)
self.logger.log(5, f'API response: {response}')
error = self.check_response(response)
if error:
self.logger.error('Error in JSON response: %s', error)
self.logger.error(f'Error in JSON response: {error}')
return {}
return response
except Exception as exc:
self.logger.error(
'Failed to make API request to %s. Exception follows:', url,
exc_info=True
)
self.logger.exception(f'Failed to make API request to {url}')
return {}
def check_response(self, response):
@ -194,9 +190,9 @@ class Weather(IntervalModule):
on_leftclick = ['check_weather']
def launch_web(self):
if self.backend.forecast_url and self.backend.forecast_url != 'N/A':
self.logger.debug('Launching %s in browser', self.backend.forecast_url)
user_open(self.backend.forecast_url)
if self.backend.conditions_url and self.backend.conditions_url != 'N/A':
self.logger.debug(f'Launching {self.backend.conditions_url} in browser')
user_open(self.backend.conditions_url)
def init(self):
if self.online_interval is None:
@ -296,7 +292,7 @@ class Weather(IntervalModule):
else self.color_icons[condition]
def refresh_display(self):
self.logger.debug('Weather data: %s', self.backend.data)
self.logger.debug(f'Weather data: {self.backend.data}')
self.backend.data['icon'], condition_color = \
self.get_color_data(self.backend.data['condition'])
color = condition_color if self.colorize else self.color

View File

@ -21,7 +21,7 @@ class WeathercomHTMLParser(HTMLParser):
super(WeathercomHTMLParser, self).__init__()
def get_weather_data(self, url):
self.logger.debug('Making request to %s to retrieve weather data', url)
self.logger.debug(f'Making request to {url} to retrieve weather data')
self.weather_data = None
req = Request(url, headers={'User-Agent': self.user_agent})
with urlopen(req) as content:
@ -40,12 +40,12 @@ class WeathercomHTMLParser(HTMLParser):
)
def load_json(self, json_input):
self.logger.debug('Loading the following data as JSON: %s', json_input)
self.logger.debug(f'Loading the following data as JSON: {json_input}')
try:
return json.loads(json_input)
except json.decoder.JSONDecodeError as exc:
self.logger.debug('Error loading JSON: %s', exc)
self.logger.debug('String that failed to load: %s', json_input)
self.logger.debug(f'Error loading JSON: {exc}')
self.logger.debug(f'String that failed to load: {json_input}')
return None
def handle_data(self, content):
@ -84,7 +84,7 @@ class WeathercomHTMLParser(HTMLParser):
if weather_data is None:
self.logger.debug(
'Failed to locate weather data in the '
'following data: %s', json_data
f'following data: {json_data}'
)
else:
self.weather_data = weather_data
@ -144,7 +144,7 @@ class Weathercom(WeatherBackend):
url_template = 'https://weather.com/{locale}/weather/today/l/{location_code}'
# This will be set in the init based on the passed location code
forecast_url = None
conditions_url = None
def init(self):
if self.location_code is not None:
@ -156,7 +156,7 @@ class Weathercom(WeatherBackend):
# causes weather.com to return the default, which is imperial.
self.locale = 'en-CA' if self.units == 'metric' else ''
self.forecast_url = self.url_template.format(**vars(self))
self.conditions_url = self.url_template.format(**vars(self))
self.parser = WeathercomHTMLParser(self.logger)
@require(internet)
@ -178,7 +178,7 @@ class Weathercom(WeatherBackend):
self.data['update_error'] = ''
try:
self.parser.get_weather_data(self.forecast_url)
self.parser.get_weather_data(self.conditions_url)
if self.parser.weather_data is None:
self.logger.error(
'Failed to read weather data from page. Run module with '
@ -187,7 +187,7 @@ class Weathercom(WeatherBackend):
self.data['update_error'] = self.update_error
return
self.logger.debug('Parsed weather data: %s', self.parser.weather_data)
self.logger.debug(f'Parsed weather data: {self.parser.weather_data}')
try:
observed = self.parser.weather_data['getSunV3CurrentObservationsUrlConfig']
# Observation data stored under a sub-key containing the
@ -228,7 +228,7 @@ class Weathercom(WeatherBackend):
except KeyError:
self.logger.warning(
'Failed to get city name from API response, falling back '
'to location code \'%s\'', self.location_code
f'to location code {self.location_code}'
)
self.city_name = self.location_code
@ -295,8 +295,5 @@ class Weathercom(WeatherBackend):
self.data['uv_index'] = str(observed.get('uvIndex', ''))
except Exception:
# Don't let an uncaught exception kill the update thread
self.logger.error(
'Uncaught error occurred while checking weather. '
'Exception follows:', exc_info=True
)
self.logger.exception('Uncaught error occurred while checking weather')
self.data['update_error'] = self.update_error

View File

@ -73,18 +73,15 @@ class Wunderground(WeatherBackend):
units = 'metric'
update_error = '!'
url_template = 'https://www.wunderground.com/dashboard/pws/{location_code}'
# Will be set in the init func
conditions_url = None
# This will be set in the init based on the passed location code
forecast_url = None
summary_url = 'https://api.weather.com/v2/pws/dailysummary/1day?apiKey={api_key}&stationId={location_code}&format=json&units={units_type}'
forecast_url = 'https://api.weather.com/v3/wx/forecast/daily/7day?apiKey={api_key}&geocode={lat:.2f}%2C{lon:.2f}&language=en-US&units={units_type}&format=json'
observation_url = 'https://api.weather.com/v2/pws/observations/current?apiKey={api_key}&stationId={location_code}&format=json&units={units_type}'
overview_url = 'https://api.weather.com/v3/aggcommon/v3alertsHeadlines;v3-wx-observations-current;v3-location-point?apiKey={api_key}&geocodes={lat:.2f}%2C{lon:.2f}&language=en-US&units=e&format=json'
overview_url = 'https://api.weather.com/v3/aggcommon/v3alertsHeadlines;v3-wx-observations-current;v3-location-point?apiKey={api_key}&geocodes={lat:.2f}%2C{lon:.2f}&language=en-US&units={units_type}&format=json'
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',
'Referer': 'https://www.wunderground.com/dashboard/pws/{location_code}',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
@ -92,7 +89,7 @@ class Wunderground(WeatherBackend):
def init(self):
self.units_type = 'm' if self.units == 'metric' else 'e'
self.forecast_url = self.url_template.format(**vars(self))
self.headers['Referer'] = self.conditions_url = f'https://www.wunderground.com/weather/{self.location_code}'
@require(internet)
def get_api_key(self):
@ -110,7 +107,7 @@ class Wunderground(WeatherBackend):
},
)
except Exception as exc:
self.logger.exception('Failed to load %s', url)
self.logger.exception(f'Failed to load {url}')
else:
try:
return re.search(r'apiKey=([0-9a-f]+)', page_source).group(1)
@ -138,16 +135,6 @@ class Wunderground(WeatherBackend):
self.data['update_error'] = ''
try:
try:
summary = self.api_request(self.summary_url.format(**vars(self)))['summaries'][0]
except (IndexError, KeyError):
self.logger.error(
'Failed to retrieve summary data from API response. '
'Run module with debug logging to get more information.'
)
self.data['update_error'] = self.update_error
return
try:
observation = self.api_request(self.observation_url.format(**vars(self)))['observations'][0]
except (IndexError, KeyError):
@ -161,6 +148,8 @@ class Wunderground(WeatherBackend):
self.lat = observation['lat']
self.lon = observation['lon']
forecast = self.api_request(self.forecast_url.format(**vars(self)))
try:
overview = self.api_request(self.overview_url.format(**vars(self)))[0]
except IndexError:
@ -191,13 +180,27 @@ class Wunderground(WeatherBackend):
def _find(path, data, default=''):
ptr = data
try:
for item in path.split(':'):
if item == 'units':
item = self.units
for item in path.split(':'):
if item == 'units':
item = self.units
# self.logger.debug(f'item = {item}')
try:
ptr = ptr[item]
except (KeyError, IndexError, TypeError):
return default
except (KeyError, TypeError):
try:
# Try list index
int_item = int(item)
except (TypeError, ValueError):
return default
else:
if len(item) == len(str(int_item)):
try:
ptr = ptr[int_item]
continue
except IndexError:
return default
else:
return default
return str(ptr)
pressure_tendency = _find(
@ -209,8 +212,8 @@ class Wunderground(WeatherBackend):
self.data['condition'] = _find('v3-wx-observations-current:wxPhraseMedium', overview)
self.data['observation_time'] = observation_time
self.data['current_temp'] = _find('units:temp', observation, '0')
self.data['low_temp'] = _find('units:tempLow', summary)
self.data['high_temp'] = _find('units:tempHigh', summary)
self.data['low_temp'] = _find('calendarDayTemperatureMin:0', forecast)
self.data['high_temp'] = _find('calendarDayTemperatureMax:0', forecast)
self.data['temp_unit'] = temp_unit
self.data['feelslike'] = _find('units:heatIndex', observation)
self.data['dewpoint'] = _find('units:dewpt', observation)