import json import tempfile from pathlib import Path from typing import Self, Union from .Enums import ConfigKeys class Config(): pathnames = set() @staticmethod def for_each(items: list) -> Self: """ Returns a generator which iterates over each item in a list of item configs Args: items (list): The list to iterate over Returns: Self: Instance of the Config class Yields: Iterator[Self]: Config class for the current item """ for item in items: yield Config(item) @staticmethod def from_json_file(pathname: str) -> Self: """ Load item configs from a JSON file Args: pathname (str): _description_ Returns: Self: _description_ """ with open(pathname, "r") as f: config = json.load(f) for item in config: Config.pathnames.add(item[ConfigKeys.ABSPATH_TARGET.value]) return Config.for_each(config) @staticmethod def __throw_missing_key(key: ConfigKeys) -> None: """ Raises a KeyError for an item config key if it does not exist Args: key (ConfigKeys): The key to raise an error for Raises: KeyError: Raised from an item config key """ raise KeyError(f"Expected required item config key '{key.value}' but it was not found") @staticmethod def __throw_value_error(key: ConfigKeys, expected_type: str) -> None: """ Raise a ValueError for a key with an expected type Args: key (ConfigKeys): The item config key to raise an error for expected_type (str): The expected type Raises: ValueError: Raised from an item config key and expected value type """ raise ValueError(f"Item config key '{key.value}' expects a value of type {expected_type}") def __init__(self, item: dict): """ Create a new Config instance """ self.__item = item @property def password(self) -> str|bool: """ Returns the password for this item, or None if unset Returns: str|False: Password or None if no password is set """ if not self.__key_exists(ConfigKeys.PASSWORD.value): return False return self.__item[ConfigKeys.PASSWORD.value] if isinstance(self.__item[ConfigKeys.PASSWORD.value], str) else None @property def compression(self) -> int: """ Returns the compression level for this item, or false if STORE mode should be used Returns: str|False: Compression level for this item, false if compression is disabled """ if not self.__key_exists(ConfigKeys.COMPRESSION.value): return 0 if not isinstance(self.__item[ConfigKeys.COMPRESSION.value], int) or self.__item[ConfigKeys.COMPRESSION.value] == 0: return 0 return max(1, min(self.__item[ConfigKeys.COMPRESSION.value], 10)) @property def abspath_temp(self) -> str: """ Returns the path to the directory where the created archive will be stored until it's uploaded Returns: str: Absolute path to the destination directory """ if not self.__key_exists(ConfigKeys.ABSPATH_TEMP.value): return tempfile.gettempdir() return self.__item[ConfigKeys.ABSPATH_TEMP.value] if isinstance(self.__item[ConfigKeys.ABSPATH_TEMP.value], str) else tempfile.gettempdir() @property def abspath_target(self) -> str: """ Returns an absolute path to the target to be archived Returns: str: Absolute path to the target """ if not self.__key_exists(ConfigKeys.ABSPATH_TARGET.value): return Config.__throw_missing_key(ConfigKeys.ABSPATH_TARGET) if not isinstance(self.__item[ConfigKeys.ABSPATH_TARGET.value], str): return Config.__throw_value_error(ConfigKeys.ABSPATH_TARGET, str) return self.__item[ConfigKeys.ABSPATH_TARGET.value] @property def abspath_destination(self) -> str: """ Returns an absolute path to the target to be archived Returns: str: Absolute path to the target """ if not self.__key_exists(ConfigKeys.ASBPATH_DESTINATION.value): return Config.__throw_missing_key(ConfigKeys.ASBPATH_DESTINATION) if not isinstance(self.__item[ConfigKeys.ASBPATH_DESTINATION.value], str): return Config.__throw_value_error(ConfigKeys.ASBPATH_DESTINATION, str) return self.__item[ConfigKeys.ASBPATH_DESTINATION.value] def __key_exists(self, key: str) -> bool: """ Returns true if a property key is defined for the current item Args: key (str): The key to test Returns: bool: True if key exists """ return key in self.__item