From 154e6dab153271dbd1cfd7a5e247ef475a3090d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donncha=20=C3=93=20Cearbhaill?= Date: Tue, 24 Dec 2024 23:30:18 +0000 Subject: [PATCH] Add config file parser for MVT --- src/mvt/common/config.py | 91 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/mvt/common/config.py diff --git a/src/mvt/common/config.py b/src/mvt/common/config.py new file mode 100644 index 0000000..4341d87 --- /dev/null +++ b/src/mvt/common/config.py @@ -0,0 +1,91 @@ +import os +import uuid +import yaml +import json + +from typing import Tuple, Type +from appdirs import user_config_dir +from pydantic import AnyHttpUrl, Field +from pydantic_settings import ( + BaseSettings, + InitSettingsSource, + PydanticBaseSettingsSource, + SettingsConfigDict, + YamlConfigSettingsSource, +) + +MVT_CONFIG_FOLDER = user_config_dir("mvt") +MVT_CONFIG_PATH = os.path.join(MVT_CONFIG_FOLDER, "config.yaml") + + +class MVTSettings(BaseSettings): + model_config = SettingsConfigDict( + env_prefix="MVT_", env_nested_delimiter="_", extra="ignore" + ) + # Allow to decided if want to load environment variables + load_env: bool = Field(True, exclude=True) + + # General settings + PYPI_UPDATE_URL: AnyHttpUrl = Field( + "https://pypi.org/pypi/mvt/json", + validate_default=False, + ) + INDICATORS_UPDATE_URL: AnyHttpUrl = Field( + default="https://raw.githubusercontent.com/mvt-project/mvt-indicators/main/indicators.yaml", + validate_default=False, + ) + NETWORK_ACCESS_ALLOWED: bool = True + NETWORK_TIMEOUT: int = 15 + + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[BaseSettings], + init_settings: InitSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> Tuple[PydanticBaseSettingsSource, ...]: + sources = ( + YamlConfigSettingsSource(settings_cls, MVT_CONFIG_PATH), + init_settings, + ) + # Load env variables if enabled + if init_settings.init_kwargs.get("load_env", True): + sources = (env_settings,) + sources + return sources + + def save_settings( + self, + ) -> None: + """ + Save the current settings to a file. + """ + if not os.path.isdir(MVT_CONFIG_FOLDER): + os.makedirs(MVT_CONFIG_FOLDER) + + # Dump the settings to the YAML file + model_serializable = json.loads(self.model_dump_json(exclude_defaults=True)) + with open(MVT_CONFIG_PATH, "w") as config_file: + config_file.write(yaml.dump(model_serializable, default_flow_style=False)) + + @classmethod + def initialise(cls) -> "MVTSettings": + """ + Initialise the settings file. + + We first initialise the settings (without env variable) and then persist + them to file. This way we can update the config file with the default values. + + Afterwards we load the settings again, this time including the env variables. + """ + # Set invalid env prefix to avoid loading env variables. + settings = MVTSettings(load_env=False) + settings.save_settings() + + # Load the settings again with any ENV variables. + settings = MVTSettings(load_env=True) + return settings + + +settings = MVTSettings.initialise()