Source code for src.config

"""Module that loads the tool's runtime configuration from a file and
makes it accessible to other modules."""
from __future__ import annotations

import logging
from configparser import ConfigParser
from datetime import datetime
from os import getenv
from pathlib import Path
from typing import Any, Optional, TypeVar

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(funcName)s:%(lineno)d} %(levelname)s - "
    "%(message)s",
    datefmt="%H:%M:%S",
)
logger: logging.Logger = logging.getLogger(__name__)


[docs] class Singleton:
[docs] def __new__(cls, *args: Any, **kw: dict[str, Any]) -> Singleton: if not hasattr(cls, "_instance"): orig = super() cls._instance = orig.__new__(cls, *args, **kw) return cls._instance
T = TypeVar("T") # Declare type variable
[docs] class Context(Singleton): """ Attributes: settings: mapping of detected runtime configuration options to their values. key naming scheme is <section>_<name> """ config_file_name: str = "occmdcfg.ini"
[docs] def __init__(self): self.CONFIG_FILE_LOCATIONS: list[Path] = [ Path.cwd(), Path.cwd().parent, Path.home(), Path.home() / ".config/occmd/", Path.cwd() / "examples/", # fall back to example config ] # there is a single timestamp per run self.timestamp: datetime = datetime.utcnow() # read config from file self.config: ConfigParser = self._read_config_file() # store options as attributes self.settings: dict[str, Any] = {} self._set_config_attributes() # handle config values that are no strings self._transform_settings()
[docs] def _read_config_file(self) -> ConfigParser: for location in self.CONFIG_FILE_LOCATIONS: config_file = location / self.config_file_name if config_file.exists() and config_file.is_file(): logger.info(f"Found config file in {location=})") config = ConfigParser() config.read(config_file.absolute().as_posix()) return config logger.info(f"Unable to find config file in {location=})") raise RuntimeError("Config file not found")
[docs] def _set_config_attributes(self) -> None: for section in self.config.sections(): for key, conf_val in self.config[section].items(): conf_name: str = f"{section}_{key}" env_val: Optional[str] = getenv(conf_name.upper()) if env_val: logger.info( f"Config {conf_name} was overridden by environment variable" ) value: str = env_val if env_val else conf_val logger.info(f"Setting config option {conf_name} = {value}") self.settings |= {f"{conf_name}": value}
[docs] def _transform_settings(self) -> None: try: self._transform_setting("local_repo_db_resources_dir", Path) Path(self.settings["local_repo_db_resources_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("local_repo_db_path_db_raw", Path) Path(self.settings["local_repo_db_path_db_raw"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("Dashboard_file", Path) self._transform_setting("Dashboard_pl_whitelist", Path) self._transform_setting("Dashboard_pl_whitelist_wiki", Path) self._transform_setting("Secrets_baselines_dir", Path) Path(self.settings["Secrets_baselines_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("CheckedInBinaries_blacklist_dir", Path) Path(self.settings["CheckedInBinaries_blacklist_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("SastUsageBasic_tools_dir", Path) Path(self.settings["SastUsageBasic_tools_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("SastUsageBasic_tool_schema", Path) self._transform_setting("SastUsageBasic_tools_csv", Path) except AttributeError as e: raise RuntimeError("Required config option was not set") from e except Exception as e: raise RuntimeError("Required config option was invalid") from e
[docs] def _transform_setting( self, setting_name: str, new_type: type[Any] ) -> None: old_value: str = str(self.settings[setting_name]) self.settings |= {setting_name: new_type(old_value)}
context: Context = Context()