python 3.11 & code quality improvements

This commit is contained in:
Ace 2023-11-07 22:49:48 +01:00
parent 4f2dacc35a
commit 93c968da53
24 changed files with 243 additions and 151 deletions

View File

@ -1,2 +1,3 @@
from .functions import * from .functions import *
from .inkycal_exceptions import * from .inkycal_exceptions import *
from .openweathermap_wrapper import OpenWeatherMap

View File

@ -6,8 +6,10 @@ Inkycal custom-functions for ease-of-use
Copyright by aceinnolab Copyright by aceinnolab
""" """
import logging import logging
import traceback
from PIL import Image, ImageDraw, ImageFont, ImageColor from PIL import Image, ImageDraw, ImageFont, ImageColor
from urllib.request import urlopen import requests
import os import os
import time import time
@ -98,11 +100,13 @@ def auto_fontsize(font, max_height):
Returns: Returns:
A PIL font object with modified height. A PIL font object with modified height.
""" """
text_bbox = font.getbbox("hg")
fontsize = font.getsize('hg')[1] text_height = text_bbox[3] - text_bbox[1]
while font.getsize('hg')[1] <= (max_height * 0.80): fontsize = text_height
while text_height <= (max_height * 0.80):
fontsize += 1 fontsize += 1
font = ImageFont.truetype(font.path, fontsize) font = ImageFont.truetype(font.path, fontsize)
text_height = text_bbox[3] - text_bbox[1]
return font 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): if autofit or (fill_width != 1.0) or (fill_height != 0.8):
size = 8 size = 8
font = ImageFont.truetype(font.path, size) 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 while (text_width < int(box_width * fill_width) and
text_height < int(box_height * fill_height)): text_height < int(box_height * fill_height)):
size += 1 size += 1
font = ImageFont.truetype(font.path, size) 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 # Truncate text if text is too long so it can fit inside the box
if (text_width, text_height) > (box_width, box_height): if (text_width, text_height) > (box_width, box_height):
logs.debug(('truncating {}'.format(text))) logs.debug(('truncating {}'.format(text)))
while (text_width, text_height) > (box_width, box_height): while (text_width, text_height) > (box_width, box_height):
text = text[0:-1] 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) logs.debug(text)
# Align text to desired position # 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. A list containing chunked strings of the full text.
""" """
lines = [] lines = []
if font.getsize(text)[0] < max_width:
text_width = font.getlength(text)
if text_width < max_width:
lines.append(text) lines.append(text)
else: else:
words = text.split(' ') words = text.split(' ')
i = 0 i = 0
while i < len(words): while i < len(words):
line = '' 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] + " " line = line + words[i] + " "
i += 1 i += 1
if not line: if not line:
@ -249,9 +269,10 @@ def internet_available():
""" """
try: try:
urlopen('https://google.com', timeout=5) requests.get('https://google.com', timeout=5)
return True return True
except: except:
print(f"Network could not be reached: {traceback.print_exc()}")
return False return False

View 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

View File

@ -98,7 +98,9 @@ class Agenda(inkycal_module):
# Calculate the max number of lines that can fit on the image # Calculate the max number of lines that can fit on the image
line_spacing = 1 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 line_width = im_width
max_lines = im_height // line_height max_lines = im_height // line_height
logger.debug(f'max lines: {max_lines}') logger.debug(f'max lines: {max_lines}')
@ -133,8 +135,8 @@ class Agenda(inkycal_module):
# parser.show_events() # parser.show_events()
# Set the width for date, time and event titles # Set the width for date, time and event titles
date_width = int(max([self.font.getsize( date_width = int(max([self.font.getlength(
dates['begin'].format(self.date_format, locale=self.language))[0] dates['begin'].format(self.date_format, locale=self.language))
for dates in agenda_events]) * 1.2) for dates in agenda_events]) * 1.2)
logger.debug(f'date_width: {date_width}') logger.debug(f'date_width: {date_width}')
@ -147,8 +149,9 @@ class Agenda(inkycal_module):
logger.info('Managed to parse events from urls') logger.info('Managed to parse events from urls')
# Find out how much space the event times take # 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) for events in upcoming_events]) * 1.2)
logger.debug(f'time_width: {time_width}') logger.debug(f'time_width: {time_width}')

View File

@ -110,7 +110,8 @@ class Calendar(inkycal_module):
# Allocate space for month-names, weekdays etc. # Allocate space for month-names, weekdays etc.
month_name_height = int(im_height * 0.10) 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"month_name_height: {month_name_height}")
logger.debug(f"weekdays_height: {weekdays_height}") logger.debug(f"weekdays_height: {weekdays_height}")
@ -182,15 +183,15 @@ class Calendar(inkycal_module):
] ]
logger.debug(f'weekday names: {weekday_names}') logger.debug(f'weekday names: {weekday_names}')
for idx, weekday in enumerate(weekday_pos): for index, weekday in enumerate(weekday_pos):
write( write(
im_black, im_black,
weekday, weekday,
(icon_width, weekdays_height), (icon_width, weekdays_height),
weekday_names[idx], weekday_names[index],
font=self.font, font=self.font,
autofit=True, autofit=True,
fill_height=1.0, fill_height=0.9,
) )
# Create a calendar template and flatten (remove nestings) # 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 # remove zeros from calendar since they are not required
calendar_flat = [num for num in calendar_flat if num != 0] 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 # Add the numbers on the correct positions
for number in calendar_flat: for number in calendar_flat:
if number != int(now.day): if number != int(now.day):
@ -215,9 +220,7 @@ class Calendar(inkycal_module):
grid[number], grid[number],
(icon_width, icon_height), (icon_width, icon_height),
str(number), str(number),
font=self.num_font, font=number_font,
fill_height=0.5,
fill_width=0.5,
) )
# Draw a red/black circle with the current day of month in white # 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 from inkycal.modules.ical_parser import iCalendar
# find out how many lines can fit at max in the event section # find out how many lines can fit at max in the event section
line_spacing = 0 line_spacing = 2
max_event_lines = events_height // ( text_bbox_height = self.font.getbbox("hg")
self.font.getsize('hg')[1] + line_spacing 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 # generate list of coordinates for each line
events_offset = im_height - events_height events_offset = im_height - events_height
@ -329,31 +332,18 @@ class Calendar(inkycal_module):
# Find out how much space (width) the date format requires # Find out how much space (width) the date format requires
lang = self.language lang = self.language
date_width = int( date_width = int(max((
max( self.font.getlength(events['begin'].format(self.date_format, locale=lang))
( for events in upcoming_events))* 1.1
self.font.getsize(
events['begin'].format(self.date_format, locale=lang)
)[0]
for events in upcoming_events
)
)
* 1.1
) )
time_width = int( time_width = int(max((
max( self.font.getlength(events['begin'].format(self.time_format, locale=lang))
( for events in upcoming_events))* 1.1
self.font.getsize(
events['begin'].format(self.time_format, locale=lang)
)[0]
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_s = im_width - date_width - time_width
event_width_l = im_width - date_width event_width_l = im_width - date_width
@ -411,12 +401,13 @@ class Calendar(inkycal_module):
cursor += 1 cursor += 1
else: else:
symbol = '- ' symbol = '- '
while self.font.getsize(symbol)[0] < im_width * 0.9:
while self.font.getlength(symbol) < im_width * 0.9:
symbol += ' -' symbol += ' -'
write( write(
im_black, im_black,
event_lines[0], event_lines[0],
(im_width, self.font.getsize(symbol)[1]), (im_width, line_height),
symbol, symbol,
font=self.font, font=self.font,
) )

View File

@ -91,9 +91,11 @@ class Feeds(inkycal_module):
# Set some parameters for formatting feeds # Set some parameters for formatting feeds
line_spacing = 1 line_spacing = 1
line_height = self.font.getsize('hg')[1] + line_spacing
line_width = im_width 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 # Calculate padding from top so the lines look centralised
spacing_top = int(im_height % line_height / 2) spacing_top = int(im_height % line_height / 2)

View File

@ -54,10 +54,11 @@ class Jokes(inkycal_module):
raise NetworkNotReachableError raise NetworkNotReachableError
# Set some parameters for formatting feeds # Set some parameters for formatting feeds
line_spacing = 1 line_spacing = 5
line_height = self.font.getsize('hg')[1] + line_spacing text_bbox = self.font.getbbox("hg")
line_height = text_bbox[3] - text_bbox[1] + line_spacing
line_width = im_width 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}") logger.debug(f"max_lines: {max_lines}")

View File

@ -96,9 +96,10 @@ class Stocks(inkycal_module):
# Set some parameters for formatting feeds # Set some parameters for formatting feeds
line_spacing = 1 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 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}") logger.debug(f"max_lines: {max_lines}")

View File

@ -73,10 +73,11 @@ class TextToDisplay(inkycal_module):
raise NetworkNotReachableError raise NetworkNotReachableError
# Set some parameters for formatting feeds # Set some parameters for formatting feeds
line_spacing = 1 line_spacing = 4
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 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 # Calculate padding from top so the lines look centralised
spacing_top = int(im_height % line_height / 2) spacing_top = int(im_height % line_height / 2)

View File

@ -86,9 +86,10 @@ class Todoist(inkycal_module):
# Set some parameters for formatting todos # Set some parameters for formatting todos
line_spacing = 1 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 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 # Calculate padding from top so the lines look centralised
spacing_top = int(im_height % line_height / 2) spacing_top = int(im_height % line_height / 2)

View File

@ -12,7 +12,7 @@ import math
import decimal import decimal
import arrow import arrow
from pyowm.owm import OWM from inkycal.custom import OpenWeatherMap
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -95,7 +95,7 @@ class Weather(inkycal_module):
self.use_beaufort = config['use_beaufort'] self.use_beaufort = config['use_beaufort']
# additional configuration # 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.timezone = get_system_tz()
self.locale = config['language'] self.locale = config['language']
self.weatherfont = ImageFont.truetype( self.weatherfont = ImageFont.truetype(
@ -104,6 +104,42 @@ class Weather(inkycal_module):
# give an OK message # give an OK message
print(f"{__name__} loaded") 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): def generate_image(self):
"""Generate image for this module""" """Generate image for this module"""
@ -124,7 +160,11 @@ class Weather(inkycal_module):
raise NetworkNotReachableError raise NetworkNotReachableError
def get_moon_phase(): def get_moon_phase():
"""Calculate the current (approximate) moon phase""" """Calculate the current (approximate) moon phase
Returns:
The corresponding moonphase-icon.
"""
dec = decimal.Decimal dec = decimal.Decimal
diff = now - arrow.get(2001, 1, 1) diff = now - arrow.get(2001, 1, 1)
@ -154,7 +194,7 @@ class Weather(inkycal_module):
return answer return answer
# Lookup-table for weather icons and weather codes # Lookup-table for weather icons and weather codes
weathericons = { weather_icons = {
'01d': '\uf00d', '01d': '\uf00d',
'02d': '\uf002', '02d': '\uf002',
'03d': '\uf013', '03d': '\uf013',
@ -227,26 +267,26 @@ class Weather(inkycal_module):
# Increase fontsize to fit specified height and width of text box # Increase fontsize to fit specified height and width of text box
size = 8 size = 8
font = ImageFont.truetype(font.path, size) 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 while (text_width < int(box_width * 0.9) and
text_height < int(box_height * 0.9)): text_height < int(box_height * 0.9)):
size += 1 size += 1
font = ImageFont.truetype(font.path, size) 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 # Align text to desired position
x = int((box_width / 2) - (text_width / 2)) 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 the text in the text-box
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
space = Image.new('RGBA', (box_width, box_height)) space = Image.new('RGBA', (box_width, box_height))
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
if rotation != None: if rotation:
space.rotate(rotation, expand=True) space.rotate(rotation, expand=True)
# Update only region with text (add text with transparent background) # Update only region with text (add text with transparent background)
@ -350,14 +390,9 @@ class Weather(inkycal_module):
temp_fc4 = (col7, row3) temp_fc4 = (col7, row3)
# Create current-weather and weather-forecast objects # Create current-weather and weather-forecast objects
if self.location.isdigit(): logging.debug('looking up location by ID')
logging.debug('looking up location by ID') weather = self.owm.get_current_weather()
weather = self.owm.weather_at_id(int(self.location)).weather forecast = self.owm.get_weather_forecast()
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')
# Set decimals # Set decimals
dec_temp = None if self.round_temperature == True else 1 dec_temp = None if self.round_temperature == True else 1
@ -369,12 +404,14 @@ class Weather(inkycal_module):
elif self.units == 'imperial': elif self.units == 'imperial':
temp_unit = 'fahrenheit' 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}') logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}')
# Get current time # Get current time
now = arrow.utcnow() now = arrow.utcnow()
fc_data = {}
if self.forecast_interval == 'hourly': if self.forecast_interval == 'hourly':
logger.debug("getting hourly forecasts") logger.debug("getting hourly forecasts")
@ -386,21 +423,22 @@ class Weather(inkycal_module):
else: else:
hour_gap = 3 hour_gap = 3
# Create timings for hourly forcasts # Create timings for hourly forecasts
forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour') forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour')
for _ in range(0, 12, 3)] for _ in range(0, 12, 3)]
# Create forecast objects for given timings # Create forecast objects for given timings
forecasts = [forecast.get_weather_at(forecast_time.datetime) for forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in forecast_timings]
forecast_time in forecast_timings]
# Add forecast-data to fc_data dictionary # Add forecast-data to fc_data dictionary
fc_data = {} fc_data = {}
for forecast in forecasts: for forecast in forecasts:
temp = '{}°'.format(round( if self.units == "metric":
forecast.temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) 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)] = { fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
'temp': temp, 'temp': temp,
'icon': icon, 'icon': icon,
@ -412,38 +450,35 @@ class Weather(inkycal_module):
logger.debug("getting daily forecasts") 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 """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 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 # Create a list containing time-objects for every 3rd hour of the day
time_range = list(arrow.Arrow.range('hour', time_range = list(
now.shift(days=days_from_today).floor('day'), arrow.Arrow.range('hour',
now.shift(days=days_from_today).ceil('day') now.shift(days=days_from_today).floor('day'),now.shift(days=days_from_today).ceil('day')
))[::3] ))[::3]
# Get forecasts for each time-object # 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 # Get all temperatures for this day
daily_temp = [round(_.temperature(unit=temp_unit)['temp'], daily_temp = [round(_["main"]["temp"]) for _ in forecasts]
ndigits=dec_temp) for _ in forecasts]
# Calculate min. and max. temp for this day # 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 # 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 # Find most common element from all weather icon codes
status = max(set(daily_icons), key=daily_icons.count) status = max(set(daily_icons), key=daily_icons.count)
weekday = now.shift(days=days_from_today).format('ddd', locale= weekday = now.shift(days=days_from_today).format('ddd', locale=self.locale)
self.locale)
return {'temp': temp_range, 'icon': status, 'stamp': weekday} return {'temp': temp_range, 'icon': status, 'stamp': weekday}
forecasts = [calculate_forecast(days) for days in range(1, 5)] forecasts = [calculate_forecast(days) for days in range(1, 5)]
fc_data = {}
for forecast in forecasts: for forecast in forecasts:
fc_data['fc' + str(forecasts.index(forecast) + 1)] = { fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
'temp': forecast['temp'], 'temp': forecast['temp'],
@ -455,13 +490,15 @@ class Weather(inkycal_module):
logger.debug((key, val)) logger.debug((key, val))
# Get some current weather details # Get some current weather details
temperature = '{}°'.format(round( if dec_temp != 0:
weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) temperature = f"{round(weather['main']['temp'])}°"
else:
temperature = f"{round(weather['main']['temp'],ndigits=dec_temp)}°"
weather_icon = weather.weather_icon_name weather_icon = weather["weather"][0]["icon"]
humidity = str(weather.humidity) humidity = str(weather["main"]["humidity"])
sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone) sunrise_raw = arrow.get(weather["sys"]["sunrise"]).to(self.timezone)
sunset_raw = arrow.get(weather.sunset_time()).to(self.timezone) sunset_raw = arrow.get(weather["sys"]["sunset"]).to(self.timezone)
logger.debug(f'weather_icon: {weather_icon}') logger.debug(f'weather_icon: {weather_icon}')
@ -469,33 +506,29 @@ class Weather(inkycal_module):
logger.debug('using 12 hour format for sunrise/sunset') logger.debug('using 12 hour format for sunrise/sunset')
sunrise = sunrise_raw.format('h:mm a') sunrise = sunrise_raw.format('h:mm a')
sunset = sunset_raw.format('h:mm a') sunset = sunset_raw.format('h:mm a')
else:
elif self.hour_format == 24: # 24 hours format
logger.debug('using 24 hour format for sunrise/sunset') logger.debug('using 24 hour format for sunrise/sunset')
sunrise = sunrise_raw.format('H:mm') sunrise = sunrise_raw.format('H:mm')
sunset = sunset_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: if self.use_beaufort:
logger.debug("using beaufort for wind") logger.debug("using beaufort for wind")
wind = str(weather.wind(unit='beaufort')['speed']) wind = str(self.mps_to_beaufort(weather["wind"]["speed"]))
else: else:
if self.units == 'metric': if self.units == 'metric':
logging.debug('getting windspeed in metric unit') logging.debug('getting wind speed in meters per second')
wind = str(weather.wind(unit='meters_sec')['speed']) + 'm/s' wind = f"{weather['wind']['speed']} m/s"
else:
logging.debug('getting wind speed in imperial unit')
wind = f"{self.mps_to_mph(weather['wind']['speed'])} miles/h"
elif self.units == 'imperial': moon_phase = get_moon_phase()
logging.debug('getting windspeed in imperial unit')
wind = str(weather.wind(unit='miles_hour')['speed']) + 'miles/h'
dec = decimal.Decimal
moonphase = get_moon_phase()
# Fill weather details in col 1 (current weather icon) # Fill weather details in col 1 (current weather icon)
draw_icon(im_colour, weather_icon_pos, (col_width, im_height), 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) # Fill weather details in col 2 (temp, humidity, wind)
draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height), draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height),
@ -521,7 +554,7 @@ class Weather(inkycal_module):
wind, font=self.font) wind, font=self.font)
# Fill weather details in col 3 (moonphase, sunrise, sunset) # 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') draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051')
write(im_black, sunrise_time_pos, (col_width - icon_small, row_height), 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): for pos in range(1, len(fc_data) + 1):
stamp = fc_data[f'fc{pos}']['stamp'] 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'] temp = fc_data[f'fc{pos}']['temp']
write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height), 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_h = row3 + row_height
border_w = col_width - 3 # leave 3 pixels gap 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), draw_border(im_black, (col1, row1), (col_width * 3 - 3, border_h),
shrinkage=(0, 0)) shrinkage=(0, 0))

View File

@ -1 +1 @@
from config import Config from .config import Config

View File

@ -57,8 +57,6 @@ class module_test(unittest.TestCase):
print('OK') print('OK')
if Config.USE_PREVIEW: if Config.USE_PREVIEW:
preview(merge(im_black, im_colour)) preview(merge(im_black, im_colour))
im = merge(im_black, im_colour)
im.show()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -112,8 +112,6 @@ class TestTextToDisplay(unittest.TestCase):
print('OK') print('OK')
if Config.USE_PREVIEW: if Config.USE_PREVIEW:
preview(merge(im_black, im_colour)) preview(merge(im_black, im_colour))
im = merge(im_black, im_colour)
im.show()
if delete_file_after_parse: if delete_file_after_parse:
print("cleaning up temp file") print("cleaning up temp file")

View File

@ -46,7 +46,6 @@ class module_test(unittest.TestCase):
print('OK') print('OK')
if Config.USE_PREVIEW: if Config.USE_PREVIEW:
preview(merge(im_black, im_colour)) preview(merge(im_black, im_colour))
merge(im_black, im_colour).show()
else: else:
print('No api key given, omitting test') print('No api key given, omitting test')

View File

@ -13,7 +13,7 @@ preview = Inkyimage.preview
merge = Inkyimage.merge merge = Inkyimage.merge
owm_api_key = Config.OPENWEATHERMAP_API_KEY owm_api_key = Config.OPENWEATHERMAP_API_KEY
location = 'Stuttgart, DE' location = '2825297'
tests = [ tests = [
{ {
@ -184,7 +184,8 @@ class module_test(unittest.TestCase):
im_black, im_colour = module.generate_image() im_black, im_colour = module.generate_image()
print('OK') print('OK')
if Config.USE_PREVIEW: if Config.USE_PREVIEW:
preview(merge(im_black, im_colour)) merged = merge(im_black, im_colour)
preview(merged)

View File

@ -1,30 +1,28 @@
arrow==1.2.3 arrow==1.3.0
certifi==2023.7.22 certifi==2023.7.22
cycler==0.11.0 cycler==0.12.1
feedparser==6.0.10 feedparser==6.0.10
fonttools==4.40.0 fonttools==4.44.0
geojson==2.3.0 icalendar==5.0.11
icalendar==5.0.7 kiwisolver==1.4.5
kiwisolver==1.4.4 lxml==4.9.3
lxml==4.9.2 matplotlib==3.8.1
matplotlib==3.7.1
multitasking==0.0.11 multitasking==0.0.11
numpy==1.25.0 numpy==1.26.1
packaging==23.1 packaging==23.2
pandas==2.0.2 pandas==2.1.2
Pillow==9.5.0 Pillow==10.1.0
pyowm==3.3.0 pyparsing==3.1.1
pyparsing==3.1.0
PySocks==1.7.1 PySocks==1.7.1
python-dateutil==2.8.2 python-dateutil==2.8.2
pytz==2023.3 pytz==2023.3.post1
recurring-ical-events==2.0.2 recurring-ical-events==2.1.0
requests==2.31.0 requests==2.31.0
sgmllib3k==1.0.0 sgmllib3k==1.0.0
six==1.16.0 six==1.16.0
todoist-api-python==2.0.2 todoist-api-python==2.1.3
typing_extensions==4.6.3 typing_extensions==4.8.0
urllib3==2.0.7 urllib3==2.0.7
yfinance==0.2.21 yfinance==0.2.31
python-dotenv==1.0.0 python-dotenv==1.0.0
setuptools==68.0.0 setuptools==68.2.2