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:
parent
cf3123b415
commit
3bc23608fd
@ -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 = ()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user