Source code for powerline.segments.common.wthr

import json
from collections import namedtuple

from powerline.lib.url import urllib_read, urllib_urlencode
from powerline.lib.threaded import KwThreadedSegment
from powerline.segments import with_docstring


_WeatherKey = namedtuple('Key', 'location_query weather_api_key')


# XXX Warning: module name must not be equal to the segment name as long as this
# segment is imported into powerline.segments.common module.


# Weather condition code descriptions available at
# https://openweathermap.org/weather-conditions
weather_conditions_codes = {
	200: ('stormy',),
	201: ('stormy',),
	202: ('stormy',),
	210: ('stormy',),
	211: ('stormy',),
	212: ('stormy',),
	221: ('stormy',),
	230: ('stormy',),
	231: ('stormy',),
	232: ('stormy',),
	300: ('rainy',),
	301: ('rainy',),
	302: ('rainy',),
	310: ('rainy',),
	311: ('rainy',),
	312: ('rainy',),
	313: ('rainy',),
	314: ('rainy',),
	321: ('rainy',),
	500: ('rainy',),
	501: ('rainy',),
	502: ('rainy',),
	503: ('rainy',),
	504: ('rainy',),
	511: ('snowy',),
	520: ('rainy',),
	521: ('rainy',),
	522: ('rainy',),
	531: ('rainy',),
	600: ('snowy',),
	601: ('snowy',),
	602: ('snowy',),
	611: ('snowy',),
	612: ('snowy',),
	613: ('snowy',),
	615: ('snowy',),
	616: ('snowy',),
	620: ('snowy',),
	621: ('snowy',),
	622: ('snowy',),
	701: ('foggy',),
	711: ('foggy',),
	721: ('foggy',),
	731: ('foggy',),
	741: ('foggy',),
	751: ('foggy',),
	761: ('foggy',),
	762: ('foggy',),
	771: ('foggy',),
	781: ('foggy',),
	800: ('sunny',),
	801: ('cloudy',),
	802: ('cloudy',),
	803: ('cloudy',),
	804: ('cloudy',),
}

weather_conditions_icons = {
    'day':           'DAY',
    'blustery':      'WIND',
    'rainy':         'RAIN',
    'cloudy':        'CLOUDS',
    'snowy':         'SNOW',
    'stormy':        'STORM',
    'foggy':         'FOG',
    'sunny':         'SUN',
    'night':         'NIGHT',
    'windy':         'WINDY',
    'not_available': 'NA',
    'unknown':       'UKN',
}

temp_conversions = {
    'C': lambda temp: temp,
    'F': lambda temp: (temp * 9 / 5) + 32,
    'K': lambda temp: temp + 273.15,
}

# Note: there are also unicode characters for units: ℃, ℉ and  K
temp_units = {
    'C': '°C',
    'F': '°F',
    'K': 'K',
}


[docs]class WeatherSegment(KwThreadedSegment): interval = 600 default_location = None location_urls = {} weather_api_key = "fbc9549d91a5e4b26c15be0dbdac3460" @staticmethod def key(location_query=None, **kwargs): try: weather_api_key = kwargs["weather_api_key"] except KeyError: weather_api_key = WeatherSegment.weather_api_key return _WeatherKey(location_query, weather_api_key) def get_request_url(self, weather_key): try: return self.location_urls[weather_key] except KeyError: query_data = { "appid": weather_key.weather_api_key, "units": "metric" } location_query = weather_key.location_query if location_query is None: location_data = json.loads(urllib_read('https://freegeoip.app/json/')) query_data["lat"] = location_data["latitude"] query_data["lon"] = location_data["longitude"] else: query_data["q"] = location_query self.location_urls[location_query] = url = ( "https://api.openweathermap.org/data/2.5/weather?" + urllib_urlencode(query_data)) return url def compute_state(self, weather_key): url = self.get_request_url(weather_key) raw_response = urllib_read(url) if not raw_response: self.error('Failed to get response') return None response = json.loads(raw_response) try: condition = response['weather'][0] condition_code = int(condition['id']) temp = float(response['main']['temp']) except (KeyError, ValueError): self.exception('OpenWeatherMap returned malformed or unexpected response: {0}', repr(raw_response)) return None try: icon_names = weather_conditions_codes[condition_code] except IndexError: icon_names = ('unknown',) self.error('Unknown condition code: {0}', condition_code) return (temp, icon_names) def render_one(self, weather, icons=None, unit='C', temp_format=None, temp_coldest=-30, temp_hottest=40, **kwargs): if not weather: return None temp, icon_names = weather for icon_name in icon_names: if icons: if icon_name in icons: icon = icons[icon_name] break else: icon = weather_conditions_icons[icon_names[-1]] temp_format = temp_format or ('{temp:.0f}' + temp_units[unit]) converted_temp = temp_conversions[unit](temp) if converted_temp <= temp_coldest: gradient_level = 0 elif converted_temp >= temp_hottest: gradient_level = 100 else: gradient_level = (converted_temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest) groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather'] return [ { 'contents': icon + ' ', 'highlight_groups': groups, 'divider_highlight_group': 'background:divider', }, { 'contents': temp_format.format(temp=converted_temp), 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'divider_highlight_group': 'background:divider', 'gradient_level': gradient_level, }, ]
weather = with_docstring(WeatherSegment(), '''Return weather from OpenWeatherMaps. Uses GeoIP lookup from https://freegeoip.app to automatically determine your current location. This should be changed if you’re in a VPN or if your IP address is registered at another location. Returns a list of colorized icon and temperature segments depending on weather conditions. :param str unit: temperature unit, can be one of ``F``, ``C`` or ``K`` :param str location_query: location query for your current location, e.g. ``oslo, norway`` :param dict icons: dict for overriding default icons, e.g. ``{'heavy_snow' : u'❆'}`` :param str temp_format: format string, receives ``temp`` as an argument. Should also hold unit. :param float temp_coldest: coldest temperature. Any temperature below it will have gradient level equal to zero. :param float temp_hottest: hottest temperature. Any temperature above it will have gradient level equal to 100. Temperatures between ``temp_coldest`` and ``temp_hottest`` receive gradient level that indicates relative position in this interval (``100 * (cur-coldest) / (hottest-coldest)``). Divider highlight group used: ``background:divider``. Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_gradient`` (gradient) or ``weather``. Also uses ``weather_conditions_{condition}`` for all weather conditions supported by OpenWeatherMap. ''')