Source code for surfactant.configmanager

import os
import platform
from pathlib import Path
from threading import Lock
from typing import Any, Dict, Optional, Union

import tomlkit


[docs] class ConfigManager: """A configuration manager for handling settings stored in a configuration file. The configuration manager internally caches a copy of the loaded configuration file, so external changes won't affect the setting value while a program is running. Attributes: app_name (str): The name of the application. (Default: 'surfactant') config_dir (Optional[Path]): The directory where the configuration file is stored. config (tomlkit.document): The configuration document loaded by tomlkit. Preserves formatting and comments. config_file_path (Path): The path to the configuration file. """ _initialized: bool = False _instances: Dict[str, "ConfigManager"] = {} _lock = Lock() def __new__( cls, app_name: str = "surfactant", config_dir: Optional[Union[str, Path]] = None ) -> "ConfigManager": """Manage singleton configuration manager for each unique application name. Args: app_name (str): The name of the application. (Default: 'surfactant') config_dir (Optional[Union[str, Path]]): The directory where the application configuration is stored. Returns: ConfigManager: The singleton instance of the configuration manager for the given application name. """ with cls._lock: if app_name not in cls._instances: instance = super(ConfigManager, cls).__new__(cls) instance._initialized = False cls._instances[app_name] = instance return cls._instances[app_name] def __init__( self, app_name: str = "surfactant", config_dir: Optional[Union[str, Path]] = None ) -> None: """Initializes the configuration manager. Args: app_name (str): The name of the application. (Default: 'surfactant') config_dir (Optional[Union[str, Path]]): The directory where the application configuration is stored. """ if self._initialized: return self._initialized = True self.app_name = app_name self.config_dir = Path(config_dir) / app_name if config_dir else None self.config = tomlkit.document() self.config_file_path = self._get_config_file_path() self._load_config() def _get_config_file_path(self) -> Path: """Determines the path to the configuration file. Returns: Path: The path to the configuration file. """ if self.config_dir: config_dir = Path(self.config_dir) else: if platform.system() == "Windows": config_dir = Path(os.getenv("APPDATA", str(Path("~\\AppData\\Roaming")))) else: config_dir = Path(os.getenv("XDG_CONFIG_HOME", str(Path("~/.config")))) config_dir = config_dir / self.app_name / "config.toml" return config_dir.expanduser() def _load_config(self) -> None: """Loads the configuration from the configuration file.""" if self.config_file_path.exists(): with open(self.config_file_path, "r") as configfile: self.config = tomlkit.parse(configfile.read())
[docs] def get(self, section: str, option: str, fallback: Optional[Any] = None) -> Any: """Gets a configuration value. Args: section (str): The section within the configuration file. option (str): The option within the section. fallback (Optional[Any]): The fallback value if the option is not found. Returns: Any: The configuration value or the fallback value. """ return self.config.get(section, {}).get(option, fallback)
[docs] def set(self, section: str, option: str, value: Any) -> None: """Sets a configuration value. Args: section (str): The section within the configuration file. option (str): The option within the section. value (Any): The value to set. """ if section not in self.config: self.config[section] = tomlkit.table() self.config[section][option] = value self._save_config()
def _save_config(self) -> None: """Saves the configuration to the configuration file.""" if not self.config_file_path.exists(): self.config_file_path.parent.mkdir(parents=True, exist_ok=True) with open(self.config_file_path, "w") as configfile: configfile.write(tomlkit.dumps(self.config)) def __getitem__(self, key: str) -> Any: """Enables dictionary-like syntax for accessing configuration settings. NOTE: Remember to check that the value returned is not 'None' before trying to access nested keys. Args: key (str): The key for accessing a TOML value or table. Returns: Any: The configuration value or 'NoneType' if the key doesn't exist. """ if key not in self.config: return None return self.config[key]
[docs] @classmethod def delete_instance(cls, app_name: str) -> None: """Deletes the singleton instance for the given application name. Args: app_name (str): The name of the application. """ with cls._lock: if app_name in cls._instances: del cls._instances[app_name]
[docs] def get_data_dir_path(self) -> Path: """Determines the path to the data directory, for storing things such as databases. Returns: Path: The path to the data directory. """ if platform.system() == "Windows": data_dir = Path(os.getenv("LOCALAPPDATA", str(Path("~\\AppData\\Local")))) else: data_dir = Path(os.getenv("XDG_DATA_HOME", str(Path("~/.local/share")))) data_dir = data_dir / self.app_name return data_dir.expanduser()