Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 57 additions & 11 deletions settings/config.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,79 @@
from pathlib import Path
from typing import Type

from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, YamlConfigSettingsSource
from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
SettingsConfigDict,
YamlConfigSettingsSource,
)

# Assuming settings.models is available and contains ChainConfig class.
import settings.models as DataModels

# Define the base directory of the project, typically two levels up from this file.
BASE_DIR = Path(__file__).resolve().parent.parent
settings = DataModels.Settings()


def get_chain_settings(chain_config_file: str):
config_file = Path(BASE_DIR, "settings", "chains", f"{chain_config_file}")
def get_chain_settings(chain_config_file: str) -> DataModels.ChainConfig:
"""
Dynamically loads chain-specific configuration from a YAML file.

This method uses Pydantic's custom settings sources to ensure the YAML file
is loaded and prioritized before environment variables or other sources.

:param chain_config_file: The name of the chain configuration YAML file (e.g., 'ethereum.yaml').
:return: An instance of ChainConfig loaded with data from the specified file.
:raises FileNotFoundError: If the specified configuration file does not exist.
"""
# 1. Construct the absolute path to the configuration file.
config_path = BASE_DIR / "settings" / "chains" / chain_config_file

# Check if the file exists to provide an early and clearer error message.
if not config_path.exists():
raise FileNotFoundError(f"Chain configuration file not found at: {config_path}")

# 2. Define a specialized Pydantic BaseSettings class dynamically.
# This nested class inherits the structure (fields) but customizes the source (where data comes from).
class YamlChainConfig(DataModels.ChainConfig):
model_config = SettingsConfigDict(yaml_file=config_file)
file_path: str = chain_config_file
file_name: str = config_file.stem
# Optional: Add metadata fields to the instance, derived from the file path.
# These are not Pydantic fields, just class attributes for context.
file_path: str = str(config_path)
file_name: str = config_path.stem

# Pydantic configuration dictionary.
model_config = SettingsConfigDict(
# Ignore extra fields found in the YAML for robustness.
extra='ignore'
)

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
cls: Type[BaseSettings],
settings_cls: Type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
sources = super().settings_customise_sources(
"""
Overrides the default source loading order.

It places the YAML configuration source first, guaranteeing that values
from the file are loaded and prioritized over all subsequent sources
(like environment variables).
"""
# Create the YAML source instance.
yaml_source = YamlConfigSettingsSource(settings_cls, yaml_file=config_path)

# Retrieve default sources from the base class.
default_sources = super().settings_customise_sources(
settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings
)
return YamlConfigSettingsSource(settings_cls, yaml_file=config_file), *sources

# Prepend the YAML source to the tuple of default sources.
return (yaml_source, *default_sources)

# 3. Instantiate the dynamically configured class. Pydantic automatically
# loads the data from the customized sources upon instantiation.
return YamlChainConfig()