# vim:fileencoding=utf-8:noet from __future__ import (unicode_literals, division, absolute_import, print_function) import json from powerline.lib.url import urllib_read, urllib_urlencode from powerline.lib.threaded import KwThreadedSegment from powerline.segments import with_docstring # 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 # http://developer.yahoo.com/weather/#codes weather_conditions_codes = ( ('tornado', 'stormy'), # 0 ('tropical_storm', 'stormy'), # 1 ('hurricane', 'stormy'), # 2 ('severe_thunderstorms', 'stormy'), # 3 ('thunderstorms', 'stormy'), # 4 ('mixed_rain_and_snow', 'rainy' ), # 5 ('mixed_rain_and_sleet', 'rainy' ), # 6 ('mixed_snow_and_sleet', 'snowy' ), # 7 ('freezing_drizzle', 'rainy' ), # 8 ('drizzle', 'rainy' ), # 9 ('freezing_rain', 'rainy' ), # 10 ('showers', 'rainy' ), # 11 ('showers', 'rainy' ), # 12 ('snow_flurries', 'snowy' ), # 13 ('light_snow_showers', 'snowy' ), # 14 ('blowing_snow', 'snowy' ), # 15 ('snow', 'snowy' ), # 16 ('hail', 'snowy' ), # 17 ('sleet', 'snowy' ), # 18 ('dust', 'foggy' ), # 19 ('fog', 'foggy' ), # 20 ('haze', 'foggy' ), # 21 ('smoky', 'foggy' ), # 22 ('blustery', 'windy' ), # 23 ('windy', ), # 24 ('cold', 'day' ), # 25 ('clouds', 'cloudy'), # 26 ('mostly_cloudy_night', 'cloudy'), # 27 ('mostly_cloudy_day', 'cloudy'), # 28 ('partly_cloudy_night', 'cloudy'), # 29 ('partly_cloudy_day', 'cloudy'), # 30 ('clear_night', 'night' ), # 31 ('sun', 'sunny' ), # 32 ('fair_night', 'night' ), # 33 ('fair_day', 'day' ), # 34 ('mixed_rain_and_hail', 'rainy' ), # 35 ('hot', 'sunny' ), # 36 ('isolated_thunderstorms', 'stormy'), # 37 ('scattered_thunderstorms', 'stormy'), # 38 ('scattered_thunderstorms', 'stormy'), # 39 ('scattered_showers', 'rainy' ), # 40 ('heavy_snow', 'snowy' ), # 41 ('scattered_snow_showers', 'snowy' ), # 42 ('heavy_snow', 'snowy' ), # 43 ('partly_cloudy', 'cloudy'), # 44 ('thundershowers', 'rainy' ), # 45 ('snow_showers', 'snowy' ), # 46 ('isolated_thundershowers', 'rainy' ), # 47 ) # ('day', (25, 34)), # ('rainy', (5, 6, 8, 9, 10, 11, 12, 35, 40, 45, 47)), # ('cloudy', (26, 27, 28, 29, 30, 44)), # ('snowy', (7, 13, 14, 15, 16, 17, 18, 41, 42, 43, 46)), # ('stormy', (0, 1, 2, 3, 4, 37, 38, 39)), # ('foggy', (19, 20, 21, 22, 23)), # ('sunny', (32, 36)), # ('night', (31, 33))): 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', } class WeatherSegment(KwThreadedSegment): interval = 600 default_location = None location_urls = {} @staticmethod def key(location_query=None, **kwargs): return location_query def get_request_url(self, location_query): try: return self.location_urls[location_query] except KeyError: if location_query is None: location_data = json.loads(urllib_read('http://geoip.nekudo.com/api/')) location = ','.join(( location_data['city'], location_data['country']['name'], location_data['country']['code'] )) self.info('Location returned by nekudo is {0}', location) else: location = location_query query_data = { 'q': 'use "https://raw.githubusercontent.com/yql/yql-tables/master/weather/weather.bylocation.xml" as we;' 'select * from weather.forecast where woeid in' ' (select woeid from geo.places(1) where text="{0}") and u="c"'.format(location).encode('utf-8'), 'format': 'json', } self.location_urls[location_query] = url = ( 'http://query.yahooapis.com/v1/public/yql?' + urllib_urlencode(query_data)) return url def compute_state(self, location_query): url = self.get_request_url(location_query) 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['query']['results']['channel']['item']['condition'] condition_code = int(condition['code']) temp = float(condition['temp']) except (KeyError, ValueError): self.exception('Yahoo returned malformed or unexpected response: {0}', repr(raw_response)) return None try: icon_names = weather_conditions_codes[condition_code] except IndexError: if condition_code == 3200: icon_names = ('not_available',) self.warn('Weather is not available for location {0}', self.location) else: 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 temp <= temp_coldest: gradient_level = 0 elif temp >= temp_hottest: gradient_level = 100 else: gradient_level = (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 Yahoo! Weather. Uses GeoIP lookup from http://geoip.nekudo.com 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 Yahoo. ''')