From bc5d07925fb3724dcc36ced6e7a42f54c5046d4e Mon Sep 17 00:00:00 2001 From: Emma Nora Theuer Date: Mon, 30 Dec 2024 03:40:50 +0100 Subject: [PATCH] Improved overall type annotations. WIP. Fixed a logical error in _ConfigLib._set_behavior() --- src/wallman_lib.py | 79 ++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/src/wallman_lib.py b/src/wallman_lib.py index 8ee82b7..dd69fcd 100644 --- a/src/wallman_lib.py +++ b/src/wallman_lib.py @@ -1,9 +1,10 @@ from sys import exit from os import chdir, getenv, system -from typing import List +from typing import List, Dict, Union import logging import tomllib from datetime import datetime, time +from apscheduler.schedulers.background import BackgroundScheduler, BlockingScheduler from apscheduler.triggers.cron import CronTrigger # Setup Logging. NOTE: Declaration as a global variable is necessary to ensure correct functionality across multiple modules. @@ -14,15 +15,15 @@ class ConfigError(Exception): pass class _ConfigLib: - # Initializes the most important config values. TODO: Add handling for the empty config case - def __init__(self): - self.config_file: dict = self._initialize_config() # Full config + # Initializes the most important config values. TODO: Add handling for the empty config edge case + def __init__(self) -> None: + self.config_file: Dict[str, Dict[str, Union[int, str, bool, List[str]]]] = self._initialize_config() # Full config # Dictionaries - self.config_general: dict = self.config_file["general"] - self.config_changing_times: dict = self.config_file["changing_times"] + self.config_general: Dict[str, Union[int, str, bool, List[str]]] = self.config_file["general"] + self.config_changing_times: Dict[str, str] = self.config_file["changing_times"] # Values in Dicts self.config_wallpaper_sets_enabled: bool = self.config_general["enable_wallpaper_sets"] - self.config_used_sets: list = self.config_general["used_sets"] + self.config_used_sets: List[str] = self.config_general["used_sets"] self.config_wallpapers_per_set: int = self.config_general["wallpapers_per_set"] self.config_total_changing_times: int = len(self.config_changing_times) self.config_log_level: str = self.config_general.get("loglevel", "INFO").upper() @@ -34,9 +35,9 @@ class _ConfigLib: self.config_notify: bool = False logger.warning("'notify' is not set in dictionary general in the config file, defaulting to 'false'.") try: - self.config_systray = self.config_general["systray"] + self.config_systray: bool = self.config_general["systray"] except KeyError: - self.config_systray = True + self.config_systray: bool = True logger.warning("'systray' is not set in the dictionary general in the config file, defaulting to 'true'.") # Setup logging @@ -46,10 +47,10 @@ class _ConfigLib: self._initialize_systray() # Read config. TODO: Add error handling for the config not found case. - def _initialize_config(self) -> dict: + def _initialize_config(self) -> Dict[str, Dict[str, Union[int, str, bool, List[str]]]]: chdir(str(getenv("HOME")) + "/.config/wallman/") with open("wallman.toml", "rb") as config_file: - data = tomllib.load(config_file) + data: Dict[str, Dict[str, Union[int, str, bool, List[str]]]] = tomllib.load(config_file) return data # HACK on this to avoid double importing of wallman_systray due to variable scope. Idea: Global variable or Variable that is inherited? @@ -63,7 +64,7 @@ class _ConfigLib: global logging global logger chdir("/var/log/wallman/") - numeric_level = getattr(logging, self.config_log_level, logging.INFO) + numeric_level: int = getattr(logging, self.config_log_level, logging.INFO) logger.setLevel(numeric_level) logging.basicConfig(filename="wallman.log", encoding="utf-8", level=numeric_level) @@ -73,13 +74,13 @@ class _ConfigLib: try: self.config_general.get("behavior") except KeyError: - logger.info("There is no wallpaper behavior specified in general, defaulting to fill...") - print("There is no wallpaper behavior specified in general, defaulting to fill...") + logger.error("There is no wallpaper behavior specified in general, defaulting to fill...") + print("ERROR: There is no wallpaper behavior specified in general, defaulting to fill...") human_behaviors: List[str] = ["pure", "tile", "center", "fill", "max", "scale"] machine_behaviors: List[str] = ["--bg", "--bg-tile", "--bg-center", "--bg-fill", "--bg-max", "--bg-scale"] behavior: str = self.config_general.get("behavior", "--bg-fill").lower() - if behavior not in human_behaviors or behavior not in machine_behaviors: + if behavior not in human_behaviors and behavior not in machine_behaviors: logging.error(f"The value provided for behaviors, {behavior}, is not valid. Defaulting to fill...") print(f"ERROR: The value provided for behaviors, {behavior}, is not valid. Defaulting to --bg-fill...") @@ -102,7 +103,7 @@ class _ConfigLib: return behavior - def _set_fallback_wallpaper(self): + def _set_fallback_wallpaper(self) -> None: if self.config_general["fallback_wallpaper"]: system(f"feh {self.config_behavior} --no-fehbg {self.config_general['fallback_wallpaper']}") logger.info("The fallback Wallpaper has been set.") @@ -115,7 +116,7 @@ class ConfigValidity(_ConfigLib): def __init__(self): super().__init__() - def _check_fallback_wallpaper(self): + def _check_fallback_wallpaper(self) -> bool: if self.config_general["fallback_wallpaper"]: logger.debug("A fallback wallpaper has been defined.") return True @@ -155,7 +156,7 @@ class ConfigValidity(_ConfigLib): logger.debug("A valid amount of options has been provided in general") return True - def _check_wallpaper_dicts(self): + def _check_wallpaper_dicts(self) -> bool: # This block checks if a dictionary for each wallpaper set exists for wallpaper_set in self.config_used_sets: if wallpaper_set in self.config_file: @@ -171,8 +172,9 @@ class ConfigValidity(_ConfigLib): except ConfigError: logger.critical(f"No dictionary {wallpaper_set} has been found in the config exiting...") raise ConfigError(f"The dictionary {wallpaper_set} has not been found in the config, exiting...") + return False - def _check_wallpaper_amount(self): + def _check_wallpaper_amount(self) -> bool: # This block checks if if each wallpaper set dictionary provides enough wallpapers to satisfy wallpapers_per_set for wallpaper_set in self.config_used_sets: if len(self.config_file[wallpaper_set]) == self.config_wallpapers_per_set: @@ -187,6 +189,7 @@ class ConfigValidity(_ConfigLib): except ConfigError: logger.critical(f"Dictionary {wallpaper_set} does not have sufficient entries, exciting...") raise ConfigError(f"Dictionary {wallpaper_set} does not have the correct amount of entries, exciting...") + return False def validate_config(self) -> bool: # NOTE: Consider changing this to exit(-1) @@ -207,34 +210,35 @@ class ConfigValidity(_ConfigLib): # TODO: Improve modularity. See notes inside the class for more details. # TODO: Ensure functionality and if needed add handling for the 1 wallpaper per set case. class WallpaperLogic(_ConfigLib): - def __init__(self): + def __init__(self) -> None: super().__init__() # NOTE: This looks a bit ugly. Consider pros and cons of adding this into _ConfigLib - self.chosen_wallpaper_set = False + self.chosen_wallpaper_set: Union[bool, List[str]] = False # NOTE: This function could be in a different file because it's not needed in the case only 1 wallpaper per set is needed. # Returns a list of a split string that contains a changing time from the config file - def _clean_times(self, desired_time) -> list: - unclean_times = list(self.config_changing_times.values())[desired_time] + def _clean_times(self, desired_time: int) -> List[str]: + unclean_times: str = list(self.config_changing_times.values())[desired_time] return unclean_times.split(":") # NOTE: This could be in a different file because it's not needed in the "Only one wallpaper set" case. + # FIXME: Use a TypedDict here def _choose_wallpaper_set(self) -> None: from random import choice as choose_from self.chosen_wallpaper_set = choose_from(self.config_used_sets) - self.wallpaper_list = list(self.config_file[self.chosen_wallpaper_set].values()) + self.wallpaper_list: List[str] = list(self.config_file[self.chosen_wallpaper_set].values()) logger.debug(f"Chose wallpaper set {self.chosen_wallpaper_set}") # NOTE: Same as _clean_times() # Verify if a given time is in a given range - def _time_in_range(self, start, end, x) -> bool: + def _time_in_range(self, start: time, end: time, x: time) -> bool: if start <= end: return start <= x <= end else: return start <= x or x < end # NOTE: Potentially add handling for this to be also usable for notify_user and add logging if notify_user fails. Consider adding an argument that is where it's called from and handle accordingly. - def _check_system_exitcode(self, code) -> bool: + def _check_system_exitcode(self, code: int) -> bool: if code != 0: try: self._set_fallback_wallpaper() @@ -264,14 +268,14 @@ class WallpaperLogic(_ConfigLib): self._choose_wallpaper_set() for time_range in range(self.config_total_changing_times - 1): self.current_time_range = time_range # Store current time for better debugging output - clean_time = self._clean_times(time_range) - clean_time_two = self._clean_times(time_range + 1) + clean_time: List[str] = self._clean_times(time_range) + clean_time_two: List[str] = self._clean_times(time_range + 1) # HACK on this to make it more readable. This function call is way too long. Consider storing these in a bunch of temporary variables, though keep function length in mind. # HACK on this to see if this logic can be simplified. It's very ugly to check it that way. # Check if the current time is between a given and the following changing time and if so, set that wallpaper. If not, keep trying. if self._time_in_range(time(int(clean_time[0]), int(clean_time[1]), int(clean_time[2])), time(int(clean_time_two[0]), int(clean_time_two[1]), int(clean_time_two[2])), datetime.now().time()): - exitcode = system(f"feh {self.config_behavior} --no-fehbg --quiet {self.wallpaper_list[time_range]}") - has_wallpaper_been_set = self._check_system_exitcode(exitcode) + exitcode: int = system(f"feh {self.config_behavior} --no-fehbg --quiet {self.wallpaper_list[time_range]}") + has_wallpaper_been_set: bool = self._check_system_exitcode(exitcode) # TODO: Add this check to _notify_user. if self.config_notify: self._notify_user() @@ -279,15 +283,15 @@ class WallpaperLogic(_ConfigLib): else: continue - exitcode = system(f"feh {self.config_behavior} --no-fehbg {self.wallpaper_list[-1]}") - has_wallpaper_been_set = self._check_system_exitcode(exitcode) + exitcode: int = system(f"feh {self.config_behavior} --no-fehbg {self.wallpaper_list[-1]}") + has_wallpaper_been_set: bool = self._check_system_exitcode(exitcode) if self.config_notify: self._notify_user() return has_wallpaper_been_set # NOTE: Consider avoiding nested functions. - def schedule_wallpapers(self): - def _schedule_background_wallpapers(): + def schedule_wallpapers(self) -> None: + def _schedule_background_wallpapers() -> BackgroundScheduler: from apscheduler.schedulers.background import BackgroundScheduler scheduler = BackgroundScheduler() # Create a scheduled job for every changing time @@ -299,7 +303,7 @@ class WallpaperLogic(_ConfigLib): logger.info("The background scheduler has been started.") return scheduler - def _schedule_blocking_wallpapers(): + def _schedule_blocking_wallpapers() -> None: from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler() # Create a scheduled job for every changing time @@ -314,8 +318,8 @@ class WallpaperLogic(_ConfigLib): # NOTE: The wallman_systray impomrt should be handled differently. See the note in Config_Validity. import wallman_systray as systray from functools import partial - scheduler = _schedule_background_wallpapers() - menu = systray.Menu ( + scheduler: BackgroundScheduler = _schedule_background_wallpapers() + menu: systray.Menu = systray.Menu ( systray.item("Re-Set Wallpaper", partial(systray.set_wallpaper_again, wallpaper_setter=self.set_wallpaper_by_time)), systray.item("Reroll Wallpapers", partial(systray.reroll_wallpapers, wallpaper_chooser=self._choose_wallpaper_set, wallpaper_setter=self.set_wallpaper_by_time)), systray.item("Quit", partial(systray.on_quit, shutdown_scheduler=scheduler.shutdown)) @@ -323,4 +327,5 @@ class WallpaperLogic(_ConfigLib): icon = systray.Icon("wallman_icon", systray.icon_image, "My Tray Icon", menu) icon.run() else: + _schedule_blocking_wallpapers()