Merge pull request #278 from aceinnolab/feature/#275
python 3.11 & code quality improvements
This commit is contained in:
		| @@ -1,2 +1,3 @@ | ||||
| from .functions import * | ||||
| from .inkycal_exceptions import * | ||||
| from .openweathermap_wrapper import OpenWeatherMap | ||||
| @@ -6,8 +6,10 @@ Inkycal custom-functions for ease-of-use | ||||
| Copyright by aceinnolab | ||||
| """ | ||||
| import logging | ||||
| import traceback | ||||
|  | ||||
| from PIL import Image, ImageDraw, ImageFont, ImageColor | ||||
| from urllib.request import urlopen | ||||
| import requests | ||||
| import os | ||||
| import time | ||||
|  | ||||
| @@ -98,11 +100,13 @@ def auto_fontsize(font, max_height): | ||||
|       Returns: | ||||
|           A PIL font object with modified height. | ||||
|       """ | ||||
|  | ||||
|     fontsize = font.getsize('hg')[1] | ||||
|     while font.getsize('hg')[1] <= (max_height * 0.80): | ||||
|     text_bbox = font.getbbox("hg") | ||||
|     text_height = text_bbox[3] - text_bbox[1] | ||||
|     fontsize = text_height | ||||
|     while text_height <= (max_height * 0.80): | ||||
|         fontsize += 1 | ||||
|         font = ImageFont.truetype(font.path, fontsize) | ||||
|         text_height = text_bbox[3] - text_bbox[1] | ||||
|     return font | ||||
|  | ||||
|  | ||||
| @@ -154,21 +158,34 @@ def write(image, xy, box_size, text, font=None, **kwargs): | ||||
|     if autofit or (fill_width != 1.0) or (fill_height != 0.8): | ||||
|         size = 8 | ||||
|         font = ImageFont.truetype(font.path, size) | ||||
|         text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] | ||||
|         text_bbox = font.getbbox(text) | ||||
|         text_width = text_bbox[2] - text_bbox[0] | ||||
|         text_bbox_height = font.getbbox("hg") | ||||
|         text_height = text_bbox_height[3] - text_bbox_height[1] | ||||
|  | ||||
|         while (text_width < int(box_width * fill_width) and | ||||
|                text_height < int(box_height * fill_height)): | ||||
|             size += 1 | ||||
|             font = ImageFont.truetype(font.path, size) | ||||
|             text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] | ||||
|             text_bbox = font.getbbox(text) | ||||
|             text_width = text_bbox[2] - text_bbox[0] | ||||
|             text_bbox_height = font.getbbox("hg") | ||||
|             text_height = text_bbox_height[3] - text_bbox_height[1] | ||||
|  | ||||
|     text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] | ||||
|     text_bbox = font.getbbox(text) | ||||
|     text_width = text_bbox[2] - text_bbox[0] | ||||
|     text_bbox_height = font.getbbox("hg") | ||||
|     text_height = text_bbox_height[3] - text_bbox_height[1] | ||||
|  | ||||
|     # Truncate text if text is too long so it can fit inside the box | ||||
|     if (text_width, text_height) > (box_width, box_height): | ||||
|         logs.debug(('truncating {}'.format(text))) | ||||
|         while (text_width, text_height) > (box_width, box_height): | ||||
|             text = text[0:-1] | ||||
|             text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] | ||||
|             text_bbox = font.getbbox(text) | ||||
|             text_width = text_bbox[2] - text_bbox[0] | ||||
|             text_bbox_height = font.getbbox("hg") | ||||
|             text_height = text_bbox_height[3] - text_bbox_height[1] | ||||
|         logs.debug(text) | ||||
|  | ||||
|     # Align text to desired position | ||||
| @@ -215,14 +232,17 @@ def text_wrap(text, font=None, max_width=None): | ||||
|       A list containing chunked strings of the full text. | ||||
|     """ | ||||
|     lines = [] | ||||
|     if font.getsize(text)[0] < max_width: | ||||
|  | ||||
|     text_width = font.getlength(text) | ||||
|  | ||||
|     if text_width < max_width: | ||||
|         lines.append(text) | ||||
|     else: | ||||
|         words = text.split(' ') | ||||
|         i = 0 | ||||
|         while i < len(words): | ||||
|             line = '' | ||||
|             while i < len(words) and font.getsize(line + words[i])[0] <= max_width: | ||||
|             while i < len(words) and font.getlength(line + words[i]) <= max_width: | ||||
|                 line = line + words[i] + " " | ||||
|                 i += 1 | ||||
|             if not line: | ||||
| @@ -249,9 +269,10 @@ def internet_available(): | ||||
|     """ | ||||
|  | ||||
|     try: | ||||
|         urlopen('https://google.com', timeout=5) | ||||
|         requests.get('https://google.com', timeout=5) | ||||
|         return True | ||||
|     except: | ||||
|         print(f"Network could not be reached: {traceback.print_exc()}") | ||||
|         return False | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										43
									
								
								inkycal/custom/openweathermap_wrapper.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								inkycal/custom/openweathermap_wrapper.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import logging | ||||
| from enum import Enum | ||||
|  | ||||
| import requests | ||||
| import json | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| class WEATHER_OPTIONS(Enum): | ||||
|     CURRENT_WEATHER = "weather" | ||||
|  | ||||
| class FORECAST_INTERVAL(Enum): | ||||
|     THREE_HOURS = "3h" | ||||
|     FIVE_DAYS = "5d" | ||||
|  | ||||
|  | ||||
|  | ||||
| class OpenWeatherMap: | ||||
|     def __init__(self, api_key:str, city_id:int, units:str) -> None: | ||||
|         self.api_key = api_key | ||||
|         self.city_id = city_id | ||||
|         assert (units  in ["metric", "imperial"] ) | ||||
|         self.units = units | ||||
|         self._api_version = "2.5" | ||||
|         self._base_url = f"https://api.openweathermap.org/data/{self._api_version}" | ||||
|  | ||||
|  | ||||
|     def get_current_weather(self) -> dict: | ||||
|         current_weather_url = f"{self._base_url}/weather?id={self.city_id}&appid={self.api_key}&units={self.units}" | ||||
|         response = requests.get(current_weather_url) | ||||
|         if not response.ok: | ||||
|             raise AssertionError(f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}") | ||||
|         data = json.loads(response.text) | ||||
|         return data | ||||
|  | ||||
|     def get_weather_forecast(self) -> dict: | ||||
|         forecast_url = f"{self._base_url}/forecast?id={self.city_id}&appid={self.api_key}&units={self.units}" | ||||
|         response = requests.get(forecast_url) | ||||
|         if not response.ok: | ||||
|             raise AssertionError(f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}") | ||||
|         data = json.loads(response.text)["list"] | ||||
|         return data | ||||
|  | ||||
| @@ -98,7 +98,9 @@ class Agenda(inkycal_module): | ||||
|  | ||||
|         # Calculate the max number of lines that can fit on the image | ||||
|         line_spacing = 1 | ||||
|         line_height = int(self.font.getsize('hg')[1]) + line_spacing | ||||
|  | ||||
|         text_bbox_height = self.font.getbbox("hg") | ||||
|         line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing | ||||
|         line_width = im_width | ||||
|         max_lines = im_height // line_height | ||||
|         logger.debug(f'max lines: {max_lines}') | ||||
| @@ -133,8 +135,8 @@ class Agenda(inkycal_module): | ||||
|         # parser.show_events() | ||||
|  | ||||
|         # Set the width for date, time and event titles | ||||
|         date_width = int(max([self.font.getsize( | ||||
|             dates['begin'].format(self.date_format, locale=self.language))[0] | ||||
|         date_width = int(max([self.font.getlength( | ||||
|             dates['begin'].format(self.date_format, locale=self.language)) | ||||
|                               for dates in agenda_events]) * 1.2) | ||||
|         logger.debug(f'date_width: {date_width}') | ||||
|  | ||||
| @@ -147,8 +149,9 @@ class Agenda(inkycal_module): | ||||
|             logger.info('Managed to parse events from urls') | ||||
|  | ||||
|             # Find out how much space the event times take | ||||
|             time_width = int(max([self.font.getsize( | ||||
|                 events['begin'].format(self.time_format, locale=self.language))[0] | ||||
|  | ||||
|             time_width = int(max([self.font.getlength( | ||||
|                 events['begin'].format(self.time_format, locale=self.language)) | ||||
|                                   for events in upcoming_events]) * 1.2) | ||||
|             logger.debug(f'time_width: {time_width}') | ||||
|  | ||||
|   | ||||
| @@ -110,7 +110,8 @@ class Calendar(inkycal_module): | ||||
|  | ||||
|         # Allocate space for month-names, weekdays etc. | ||||
|         month_name_height = int(im_height * 0.10) | ||||
|         weekdays_height = int(self.font.getsize('hg')[1] * 1.25) | ||||
|         text_bbox_height = self.font.getbbox("hg") | ||||
|         weekdays_height = int((text_bbox_height[3] - text_bbox_height[1])* 1.25) | ||||
|         logger.debug(f"month_name_height: {month_name_height}") | ||||
|         logger.debug(f"weekdays_height: {weekdays_height}") | ||||
|  | ||||
| @@ -182,15 +183,15 @@ class Calendar(inkycal_module): | ||||
|         ] | ||||
|         logger.debug(f'weekday names: {weekday_names}') | ||||
|  | ||||
|         for idx, weekday in enumerate(weekday_pos): | ||||
|         for index, weekday in enumerate(weekday_pos): | ||||
|             write( | ||||
|                 im_black, | ||||
|                 weekday, | ||||
|                 (icon_width, weekdays_height), | ||||
|                 weekday_names[idx], | ||||
|                 weekday_names[index], | ||||
|                 font=self.font, | ||||
|                 autofit=True, | ||||
|                 fill_height=1.0, | ||||
|                 fill_height=0.9, | ||||
|             ) | ||||
|  | ||||
|         # Create a calendar template and flatten (remove nestings) | ||||
| @@ -207,6 +208,10 @@ class Calendar(inkycal_module): | ||||
|         # remove zeros from calendar since they are not required | ||||
|         calendar_flat = [num for num in calendar_flat if num != 0] | ||||
|  | ||||
|         # ensure all numbers have the same size | ||||
|         fontsize_numbers = int(min(icon_width, icon_height) * 0.5) | ||||
|         number_font = ImageFont.truetype(self.font.path, fontsize_numbers) | ||||
|  | ||||
|         # Add the numbers on the correct positions | ||||
|         for number in calendar_flat: | ||||
|             if number != int(now.day): | ||||
| @@ -215,9 +220,7 @@ class Calendar(inkycal_module): | ||||
|                     grid[number], | ||||
|                     (icon_width, icon_height), | ||||
|                     str(number), | ||||
|                     font=self.num_font, | ||||
|                     fill_height=0.5, | ||||
|                     fill_width=0.5, | ||||
|                     font=number_font, | ||||
|                 ) | ||||
|  | ||||
|         # Draw a red/black circle with the current day of month in white | ||||
| @@ -262,10 +265,10 @@ class Calendar(inkycal_module): | ||||
|             from inkycal.modules.ical_parser import iCalendar | ||||
|  | ||||
|             # find out how many lines can fit at max in the event section | ||||
|             line_spacing = 0 | ||||
|             max_event_lines = events_height // ( | ||||
|                 self.font.getsize('hg')[1] + line_spacing | ||||
|             ) | ||||
|             line_spacing = 2 | ||||
|             text_bbox_height = self.font.getbbox("hg") | ||||
|             line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing | ||||
|             max_event_lines = events_height // (line_height + line_spacing) | ||||
|  | ||||
|             # generate list of coordinates for each line | ||||
|             events_offset = im_height - events_height | ||||
| @@ -329,31 +332,18 @@ class Calendar(inkycal_module): | ||||
|                 # Find out how much space (width) the date format requires | ||||
|                 lang = self.language | ||||
|  | ||||
|                 date_width = int( | ||||
|                     max( | ||||
|                         ( | ||||
|                             self.font.getsize( | ||||
|                                 events['begin'].format(self.date_format, locale=lang) | ||||
|                             )[0] | ||||
|                             for events in upcoming_events | ||||
|                         ) | ||||
|                     ) | ||||
|                     * 1.1 | ||||
|                 date_width = int(max(( | ||||
|                     self.font.getlength(events['begin'].format(self.date_format, locale=lang)) | ||||
|                     for events in upcoming_events))* 1.1 | ||||
|                 ) | ||||
|  | ||||
|                 time_width = int( | ||||
|                     max( | ||||
|                         ( | ||||
|                             self.font.getsize( | ||||
|                                 events['begin'].format(self.time_format, locale=lang) | ||||
|                             )[0] | ||||
|                             for events in upcoming_events | ||||
|                         ) | ||||
|                     ) | ||||
|                     * 1.1 | ||||
|                 time_width = int(max(( | ||||
|                     self.font.getlength(events['begin'].format(self.time_format, locale=lang)) | ||||
|                     for events in upcoming_events))* 1.1 | ||||
|                 ) | ||||
|  | ||||
|                 line_height = self.font.getsize('hg')[1] + line_spacing | ||||
|                 text_bbox_height = self.font.getbbox("hg") | ||||
|                 line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing | ||||
|  | ||||
|                 event_width_s = im_width - date_width - time_width | ||||
|                 event_width_l = im_width - date_width | ||||
| @@ -411,12 +401,13 @@ class Calendar(inkycal_module): | ||||
|                             cursor += 1 | ||||
|             else: | ||||
|                 symbol = '- ' | ||||
|                 while self.font.getsize(symbol)[0] < im_width * 0.9: | ||||
|  | ||||
|                 while self.font.getlength(symbol) < im_width * 0.9: | ||||
|                     symbol += ' -' | ||||
|                 write( | ||||
|                     im_black, | ||||
|                     event_lines[0], | ||||
|                     (im_width, self.font.getsize(symbol)[1]), | ||||
|                     (im_width, line_height), | ||||
|                     symbol, | ||||
|                     font=self.font, | ||||
|                 ) | ||||
|   | ||||
| @@ -91,9 +91,11 @@ class Feeds(inkycal_module): | ||||
|  | ||||
|         # Set some parameters for formatting feeds | ||||
|         line_spacing = 1 | ||||
|         line_height = self.font.getsize('hg')[1] + line_spacing | ||||
|  | ||||
|         line_width = im_width | ||||
|         max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) | ||||
|         text_bbox_height = self.font.getbbox("hg") | ||||
|         line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing | ||||
|         max_lines = (im_height // (line_height + line_spacing)) | ||||
|  | ||||
|         # Calculate padding from top so the lines look centralised | ||||
|         spacing_top = int(im_height % line_height / 2) | ||||
|   | ||||
| @@ -54,10 +54,11 @@ class Jokes(inkycal_module): | ||||
|             raise NetworkNotReachableError | ||||
|  | ||||
|         # Set some parameters for formatting feeds | ||||
|         line_spacing = 1 | ||||
|         line_height = self.font.getsize('hg')[1] + line_spacing | ||||
|         line_spacing = 5 | ||||
|         text_bbox = self.font.getbbox("hg") | ||||
|         line_height = text_bbox[3] - text_bbox[1] + line_spacing | ||||
|         line_width = im_width | ||||
|         max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) | ||||
|         max_lines = (im_height // (line_height + line_spacing)) | ||||
|  | ||||
|         logger.debug(f"max_lines: {max_lines}") | ||||
|  | ||||
|   | ||||
| @@ -96,9 +96,10 @@ class Stocks(inkycal_module): | ||||
|  | ||||
|         # Set some parameters for formatting feeds | ||||
|         line_spacing = 1 | ||||
|         line_height = self.font.getsize('hg')[1] + line_spacing | ||||
|         text_bbox_height = self.font.getbbox("hg") | ||||
|         line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing | ||||
|         line_width = im_width | ||||
|         max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) | ||||
|         max_lines = (im_height // line_height) | ||||
|  | ||||
|         logger.debug(f"max_lines: {max_lines}") | ||||
|  | ||||
|   | ||||
| @@ -7,11 +7,11 @@ If the content is too long, it will be truncated from the back until it fits | ||||
|  | ||||
| Copyright by aceinnolab | ||||
| """ | ||||
| from inkycal.modules.template import inkycal_module | ||||
| from inkycal.custom import * | ||||
|  | ||||
| from urllib.request import urlopen | ||||
|  | ||||
| from inkycal.custom import * | ||||
| from inkycal.modules.template import inkycal_module | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| @@ -44,7 +44,6 @@ class TextToDisplay(inkycal_module): | ||||
|  | ||||
|         self.make_request = True if self.filepath.startswith("https://") else False | ||||
|  | ||||
|  | ||||
|         # give an OK message | ||||
|         print(f'{__name__} loaded') | ||||
|  | ||||
| @@ -73,10 +72,11 @@ class TextToDisplay(inkycal_module): | ||||
|             raise NetworkNotReachableError | ||||
|  | ||||
|         # Set some parameters for formatting feeds | ||||
|         line_spacing = 1 | ||||
|         line_height = self.font.getsize('hg')[1] + line_spacing | ||||
|         line_spacing = 4 | ||||
|         text_bbox_height = self.font.getbbox("hg") | ||||
|         line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing | ||||
|         line_width = im_width | ||||
|         max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) | ||||
|         max_lines = im_height // line_height | ||||
|  | ||||
|         # Calculate padding from top so the lines look centralised | ||||
|         spacing_top = int(im_height % line_height / 2) | ||||
|   | ||||
| @@ -86,9 +86,10 @@ class Todoist(inkycal_module): | ||||
|  | ||||
|         # Set some parameters for formatting todos | ||||
|         line_spacing = 1 | ||||
|         line_height = self.font.getsize('hg')[1] + line_spacing | ||||
|         text_bbox_height = self.font.getbbox("hg") | ||||
|         line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing | ||||
|         line_width = im_width | ||||
|         max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) | ||||
|         max_lines = im_height // line_height | ||||
|  | ||||
|         # Calculate padding from top so the lines look centralised | ||||
|         spacing_top = int(im_height % line_height / 2) | ||||
|   | ||||
| @@ -12,7 +12,7 @@ import math | ||||
| import decimal | ||||
| import arrow | ||||
|  | ||||
| from pyowm.owm import OWM | ||||
| from inkycal.custom import OpenWeatherMap | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -95,7 +95,7 @@ class Weather(inkycal_module): | ||||
|         self.use_beaufort = config['use_beaufort'] | ||||
|  | ||||
|         # additional configuration | ||||
|         self.owm = OWM(self.api_key).weather_manager() | ||||
|         self.owm =  OpenWeatherMap(api_key=self.api_key, city_id=self.location, units=config['units']) | ||||
|         self.timezone = get_system_tz() | ||||
|         self.locale = config['language'] | ||||
|         self.weatherfont = ImageFont.truetype( | ||||
| @@ -104,6 +104,42 @@ class Weather(inkycal_module): | ||||
|         # give an OK message | ||||
|         print(f"{__name__} loaded") | ||||
|  | ||||
|  | ||||
|     @staticmethod | ||||
|     def mps_to_beaufort(meters_per_second:float) -> int: | ||||
|         """Map meters per second to the beaufort scale. | ||||
|  | ||||
|         Args: | ||||
|             meters_per_second: | ||||
|                 float representing meters per seconds | ||||
|  | ||||
|         Returns: | ||||
|             an integer of the beaufort scale mapping the input | ||||
|         """ | ||||
|         thresholds = [0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.7, 24.5, 28.4] | ||||
|         return next((i for i, threshold in enumerate(thresholds) if meters_per_second < threshold), 11) | ||||
|  | ||||
|     @staticmethod | ||||
|     def mps_to_mph(meters_per_second:float) -> float: | ||||
|         """Map meters per second to miles per hour, rounded to one decimal place. | ||||
|  | ||||
|         Args: | ||||
|             meters_per_second: | ||||
|                 float representing meters per seconds. | ||||
|  | ||||
|         Returns: | ||||
|             float representing the input value in miles per hour. | ||||
|         """ | ||||
|         # 1 m/s is approximately equal to 2.23694 mph | ||||
|         miles_per_hour = meters_per_second * 2.23694 | ||||
|         return round(miles_per_hour, 1) | ||||
|  | ||||
|     @staticmethod | ||||
|     def celsius_to_fahrenheit(celsius:int or float): | ||||
|         """Converts the given temperate from degrees Celsius to Fahrenheit.""" | ||||
|         fahrenheit = (celsius * 9 / 5) + 32 | ||||
|         return fahrenheit | ||||
|  | ||||
|     def generate_image(self): | ||||
|         """Generate image for this module""" | ||||
|  | ||||
| @@ -124,7 +160,11 @@ class Weather(inkycal_module): | ||||
|             raise NetworkNotReachableError | ||||
|  | ||||
|         def get_moon_phase(): | ||||
|             """Calculate the current (approximate) moon phase""" | ||||
|             """Calculate the current (approximate) moon phase | ||||
|  | ||||
|             Returns: | ||||
|                 The corresponding moonphase-icon. | ||||
|             """ | ||||
|  | ||||
|             dec = decimal.Decimal | ||||
|             diff = now - arrow.get(2001, 1, 1) | ||||
| @@ -154,7 +194,7 @@ class Weather(inkycal_module): | ||||
|             return answer | ||||
|  | ||||
|         # Lookup-table for weather icons and weather codes | ||||
|         weathericons = { | ||||
|         weather_icons = { | ||||
|             '01d': '\uf00d', | ||||
|             '02d': '\uf002', | ||||
|             '03d': '\uf013', | ||||
| @@ -227,26 +267,26 @@ class Weather(inkycal_module): | ||||
|             # Increase fontsize to fit specified height and width of text box | ||||
|             size = 8 | ||||
|             font = ImageFont.truetype(font.path, size) | ||||
|             text_width, text_height = font.getsize(text) | ||||
|             text_width, text_height = font.getbbox(text)[2:] | ||||
|  | ||||
|             while (text_width < int(box_width * 0.9) and | ||||
|                    text_height < int(box_height * 0.9)): | ||||
|                 size += 1 | ||||
|                 font = ImageFont.truetype(font.path, size) | ||||
|                 text_width, text_height = font.getsize(text) | ||||
|                 text_width, text_height = font.getbbox(text)[2:] | ||||
|  | ||||
|             text_width, text_height = font.getsize(text) | ||||
|             text_width, text_height = font.getbbox(text)[2:] | ||||
|  | ||||
|             # Align text to desired position | ||||
|             x = int((box_width / 2) - (text_width / 2)) | ||||
|             y = int((box_height / 2) - (text_height / 2) - (icon_size_correction[icon] * size) / 2) | ||||
|             y = int((box_height / 2) - (text_height / 2)) | ||||
|  | ||||
|             # Draw the text in the text-box | ||||
|             draw = ImageDraw.Draw(image) | ||||
|             space = Image.new('RGBA', (box_width, box_height)) | ||||
|             ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) | ||||
|  | ||||
|             if rotation != None: | ||||
|             if rotation: | ||||
|                 space.rotate(rotation, expand=True) | ||||
|  | ||||
|             # Update only region with text (add text with transparent background) | ||||
| @@ -350,14 +390,9 @@ class Weather(inkycal_module): | ||||
|         temp_fc4 = (col7, row3) | ||||
|  | ||||
|         # Create current-weather and weather-forecast objects | ||||
|         if self.location.isdigit(): | ||||
|         logging.debug('looking up location by ID') | ||||
|             weather = self.owm.weather_at_id(int(self.location)).weather | ||||
|             forecast = self.owm.forecast_at_id(int(self.location), '3h') | ||||
|         else: | ||||
|             logging.debug('looking up location by string') | ||||
|             weather = self.owm.weather_at_place(self.location).weather | ||||
|             forecast = self.owm.forecast_at_place(self.location, '3h') | ||||
|         weather = self.owm.get_current_weather() | ||||
|         forecast = self.owm.get_weather_forecast() | ||||
|  | ||||
|         # Set decimals | ||||
|         dec_temp = None if self.round_temperature == True else 1 | ||||
| @@ -369,12 +404,14 @@ class Weather(inkycal_module): | ||||
|         elif self.units == 'imperial': | ||||
|             temp_unit = 'fahrenheit' | ||||
|  | ||||
|         logging.debug(f'temperature unit: {temp_unit}') | ||||
|         logging.debug(f'temperature unit: {self.units}') | ||||
|         logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}') | ||||
|  | ||||
|         # Get current time | ||||
|         now = arrow.utcnow() | ||||
|  | ||||
|         fc_data = {} | ||||
|  | ||||
|         if self.forecast_interval == 'hourly': | ||||
|  | ||||
|             logger.debug("getting hourly forecasts") | ||||
| @@ -386,21 +423,22 @@ class Weather(inkycal_module): | ||||
|             else: | ||||
|                 hour_gap = 3 | ||||
|  | ||||
|             # Create timings for hourly forcasts | ||||
|             # Create timings for hourly forecasts | ||||
|             forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour') | ||||
|                                 for _ in range(0, 12, 3)] | ||||
|  | ||||
|             # Create forecast objects for given timings | ||||
|             forecasts = [forecast.get_weather_at(forecast_time.datetime) for | ||||
|                          forecast_time in forecast_timings] | ||||
|             forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in forecast_timings] | ||||
|  | ||||
|             # Add forecast-data to fc_data dictionary | ||||
|             fc_data = {} | ||||
|             for forecast in forecasts: | ||||
|                 temp = '{}°'.format(round( | ||||
|                     forecast.temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) | ||||
|                 if self.units == "metric": | ||||
|                     temp = f"{round(weather['main']['temp'], ndigits=dec_temp)}°C" | ||||
|                 else: | ||||
|                     temp = f"{round(self.celsius_to_fahrenheit(weather['weather']['main']['temp']), ndigits=dec_temp)}°F" | ||||
|  | ||||
|                 icon = forecast.weather_icon_name | ||||
|                 icon = forecast["weather"][0]["icon"] | ||||
|                 fc_data['fc' + str(forecasts.index(forecast) + 1)] = { | ||||
|                     'temp': temp, | ||||
|                     'icon': icon, | ||||
| @@ -412,38 +450,35 @@ class Weather(inkycal_module): | ||||
|  | ||||
|             logger.debug("getting daily forecasts") | ||||
|  | ||||
|             def calculate_forecast(days_from_today): | ||||
|             def calculate_forecast(days_from_today) -> dict: | ||||
|                 """Get temperature range and most frequent icon code for forecast | ||||
|                 days_from_today should be int from 1-4: e.g. 2 -> 2 days from today | ||||
|                 """ | ||||
|  | ||||
|                 # Create a list containing time-objects for every 3rd hour of the day | ||||
|                 time_range = list(arrow.Arrow.range('hour', | ||||
|                                                     now.shift(days=days_from_today).floor('day'), | ||||
|                                                     now.shift(days=days_from_today).ceil('day') | ||||
|                 time_range = list( | ||||
|                     arrow.Arrow.range('hour', | ||||
|                     now.shift(days=days_from_today).floor('day'),now.shift(days=days_from_today).ceil('day') | ||||
|                     ))[::3] | ||||
|  | ||||
|                 # Get forecasts for each time-object | ||||
|                 forecasts = [forecast.get_weather_at(_.datetime) for _ in time_range] | ||||
|                 forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in time_range] | ||||
|  | ||||
|                 # Get all temperatures for this day | ||||
|                 daily_temp = [round(_.temperature(unit=temp_unit)['temp'], | ||||
|                                     ndigits=dec_temp) for _ in forecasts] | ||||
|                 daily_temp = [round(_["main"]["temp"]) for _ in forecasts] | ||||
|                 # Calculate min. and max. temp for this day | ||||
|                 temp_range = f'{max(daily_temp)}°/{min(daily_temp)}°' | ||||
|                 temp_range = f'{min(daily_temp)}°/{max(daily_temp)}°' | ||||
|  | ||||
|                 # Get all weather icon codes for this day | ||||
|                 daily_icons = [_.weather_icon_name for _ in forecasts] | ||||
|                 daily_icons = [_["weather"][0]["icon"] for _ in forecasts] | ||||
|                 # Find most common element from all weather icon codes | ||||
|                 status = max(set(daily_icons), key=daily_icons.count) | ||||
|  | ||||
|                 weekday = now.shift(days=days_from_today).format('ddd', locale= | ||||
|                 self.locale) | ||||
|                 weekday = now.shift(days=days_from_today).format('ddd', locale=self.locale) | ||||
|                 return {'temp': temp_range, 'icon': status, 'stamp': weekday} | ||||
|  | ||||
|             forecasts = [calculate_forecast(days) for days in range(1, 5)] | ||||
|  | ||||
|             fc_data = {} | ||||
|             for forecast in forecasts: | ||||
|                 fc_data['fc' + str(forecasts.index(forecast) + 1)] = { | ||||
|                     'temp': forecast['temp'], | ||||
| @@ -455,13 +490,15 @@ class Weather(inkycal_module): | ||||
|             logger.debug((key, val)) | ||||
|  | ||||
|         # Get some current weather details | ||||
|         temperature = '{}°'.format(round( | ||||
|             weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) | ||||
|         if dec_temp != 0: | ||||
|             temperature = f"{round(weather['main']['temp'])}°" | ||||
|         else: | ||||
|             temperature = f"{round(weather['main']['temp'],ndigits=dec_temp)}°" | ||||
|  | ||||
|         weather_icon = weather.weather_icon_name | ||||
|         humidity = str(weather.humidity) | ||||
|         sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone) | ||||
|         sunset_raw = arrow.get(weather.sunset_time()).to(self.timezone) | ||||
|         weather_icon = weather["weather"][0]["icon"] | ||||
|         humidity = str(weather["main"]["humidity"]) | ||||
|         sunrise_raw = arrow.get(weather["sys"]["sunrise"]).to(self.timezone) | ||||
|         sunset_raw = arrow.get(weather["sys"]["sunset"]).to(self.timezone) | ||||
|  | ||||
|         logger.debug(f'weather_icon: {weather_icon}') | ||||
|  | ||||
| @@ -469,33 +506,29 @@ class Weather(inkycal_module): | ||||
|             logger.debug('using 12 hour format for sunrise/sunset') | ||||
|             sunrise = sunrise_raw.format('h:mm a') | ||||
|             sunset = sunset_raw.format('h:mm a') | ||||
|  | ||||
|         elif self.hour_format == 24: | ||||
|         else: | ||||
|             # 24 hours format | ||||
|             logger.debug('using 24 hour format for sunrise/sunset') | ||||
|             sunrise = sunrise_raw.format('H:mm') | ||||
|             sunset = sunset_raw.format('H:mm') | ||||
|  | ||||
|         # Format the windspeed to user preference | ||||
|         # Format the wind-speed to user preference | ||||
|         if self.use_beaufort: | ||||
|             logger.debug("using beaufort for wind") | ||||
|             wind = str(weather.wind(unit='beaufort')['speed']) | ||||
|  | ||||
|             wind = str(self.mps_to_beaufort(weather["wind"]["speed"])) | ||||
|         else: | ||||
|  | ||||
|             if self.units == 'metric': | ||||
|                 logging.debug('getting windspeed in metric unit') | ||||
|                 wind = str(weather.wind(unit='meters_sec')['speed']) + 'm/s' | ||||
|  | ||||
|             elif self.units == 'imperial': | ||||
|                 logging.debug('getting wind speed in meters per second') | ||||
|                 wind = f"{weather['wind']['speed']} m/s" | ||||
|             else: | ||||
|                 logging.debug('getting wind speed in imperial unit') | ||||
|                 wind = str(weather.wind(unit='miles_hour')['speed']) + 'miles/h' | ||||
|                 wind = f"{self.mps_to_mph(weather['wind']['speed'])} miles/h" | ||||
|  | ||||
|         dec = decimal.Decimal | ||||
|         moonphase = get_moon_phase() | ||||
|         moon_phase = get_moon_phase() | ||||
|  | ||||
|         # Fill weather details in col 1 (current weather icon) | ||||
|         draw_icon(im_colour, weather_icon_pos, (col_width, im_height), | ||||
|                   weathericons[weather_icon]) | ||||
|                   weather_icons[weather_icon]) | ||||
|  | ||||
|         # Fill weather details in col 2 (temp, humidity, wind) | ||||
|         draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height), | ||||
| @@ -521,7 +554,7 @@ class Weather(inkycal_module): | ||||
|               wind, font=self.font) | ||||
|  | ||||
|         # Fill weather details in col 3 (moonphase, sunrise, sunset) | ||||
|         draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase) | ||||
|         draw_icon(im_colour, moonphase_pos, (col_width, row_height), moon_phase) | ||||
|  | ||||
|         draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051') | ||||
|         write(im_black, sunrise_time_pos, (col_width - icon_small, row_height), | ||||
| @@ -535,7 +568,7 @@ class Weather(inkycal_module): | ||||
|         for pos in range(1, len(fc_data) + 1): | ||||
|             stamp = fc_data[f'fc{pos}']['stamp'] | ||||
|  | ||||
|             icon = weathericons[fc_data[f'fc{pos}']['icon']] | ||||
|             icon = weather_icons[fc_data[f'fc{pos}']['icon']] | ||||
|             temp = fc_data[f'fc{pos}']['temp'] | ||||
|  | ||||
|             write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height), | ||||
| @@ -548,7 +581,7 @@ class Weather(inkycal_module): | ||||
|         border_h = row3 + row_height | ||||
|         border_w = col_width - 3  # leave 3 pixels gap | ||||
|  | ||||
|         # Add borders around each sub-section | ||||
|         # Add borders around each subsection | ||||
|         draw_border(im_black, (col1, row1), (col_width * 3 - 3, border_h), | ||||
|                     shrinkage=(0, 0)) | ||||
|  | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| from config import Config | ||||
| from .config import Config | ||||
|   | ||||
| @@ -57,8 +57,6 @@ class module_test(unittest.TestCase): | ||||
|             print('OK') | ||||
|             if Config.USE_PREVIEW: | ||||
|                 preview(merge(im_black, im_colour)) | ||||
|             im = merge(im_black, im_colour) | ||||
|             im.show() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
| @@ -112,8 +112,6 @@ class TestTextToDisplay(unittest.TestCase): | ||||
|             print('OK') | ||||
|             if Config.USE_PREVIEW: | ||||
|                 preview(merge(im_black, im_colour)) | ||||
|             im = merge(im_black, im_colour) | ||||
|             im.show() | ||||
|  | ||||
|         if delete_file_after_parse: | ||||
|             print("cleaning up temp file") | ||||
|   | ||||
| @@ -46,7 +46,6 @@ class module_test(unittest.TestCase): | ||||
|                 print('OK') | ||||
|                 if Config.USE_PREVIEW: | ||||
|                     preview(merge(im_black, im_colour)) | ||||
|                 merge(im_black, im_colour).show() | ||||
|         else: | ||||
|             print('No api key given, omitting test') | ||||
| 
 | ||||
| @@ -13,7 +13,7 @@ preview = Inkyimage.preview | ||||
| merge = Inkyimage.merge | ||||
| 
 | ||||
| owm_api_key = Config.OPENWEATHERMAP_API_KEY | ||||
| location = 'Stuttgart, DE' | ||||
| location = '2825297' | ||||
| 
 | ||||
| tests = [ | ||||
|     { | ||||
| @@ -184,7 +184,8 @@ class module_test(unittest.TestCase): | ||||
|             im_black, im_colour = module.generate_image() | ||||
|             print('OK') | ||||
|             if Config.USE_PREVIEW: | ||||
|                 preview(merge(im_black, im_colour)) | ||||
|                 merged = merge(im_black, im_colour) | ||||
|                 preview(merged) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @@ -1,30 +1,25 @@ | ||||
| arrow==1.2.3 | ||||
| arrow==1.3.0 | ||||
| certifi==2023.7.22 | ||||
| cycler==0.11.0 | ||||
| cycler==0.12.1 | ||||
| feedparser==6.0.10 | ||||
| fonttools==4.40.0 | ||||
| geojson==2.3.0 | ||||
| icalendar==5.0.7 | ||||
| kiwisolver==1.4.4 | ||||
| lxml==4.9.2 | ||||
| matplotlib==3.7.1 | ||||
| multitasking==0.0.11 | ||||
| numpy==1.25.0 | ||||
| packaging==23.1 | ||||
| pandas==2.0.2 | ||||
| Pillow==9.5.0 | ||||
| pyowm==3.3.0 | ||||
| pyparsing==3.1.0 | ||||
| fonttools==4.44.0 | ||||
| icalendar==5.0.11 | ||||
| kiwisolver==1.4.5 | ||||
| lxml==4.9.3 | ||||
| matplotlib==3.8.1 | ||||
| numpy==1.26.1 | ||||
| packaging==23.2 | ||||
| Pillow==10.1.0 | ||||
| pyparsing==3.1.1 | ||||
| PySocks==1.7.1 | ||||
| python-dateutil==2.8.2 | ||||
| pytz==2023.3 | ||||
| recurring-ical-events==2.0.2 | ||||
| pytz==2023.3.post1 | ||||
| recurring-ical-events==2.1.0 | ||||
| requests==2.31.0 | ||||
| sgmllib3k==1.0.0 | ||||
| six==1.16.0 | ||||
| todoist-api-python==2.0.2 | ||||
| typing_extensions==4.6.3 | ||||
| todoist-api-python==2.1.3 | ||||
| typing_extensions==4.8.0 | ||||
| urllib3==2.0.7 | ||||
| yfinance==0.2.21 | ||||
| python-dotenv==1.0.0 | ||||
| setuptools==68.0.0 | ||||
| setuptools==68.2.2 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user