"""Config file for lambda function.""" from __future__ import annotations import os from dataclasses import dataclass, field from dotenv import load_dotenv from secrets_manager.lambda_ext import LambdaSecretsManager from src.connectors.mysql import MySQLConfig from src.enums import Environment # Load environment variables from a .env file if present load_dotenv(override=True) @dataclass class AppConfig: """App configuration.""" app_name: str = 'lambda-abacus-payment-allocation' env: Environment = field( default_factory=lambda: Environment( os.environ.get('ENVIRONMENT', Environment.DEV) ) ) batch_size: int = field( default_factory=lambda: int(os.environ.get('BATCH_SIZE', '10000')) ) contract_batch_size: int = field( default_factory=lambda: int(os.environ.get('CONTRACT_BATCH_SIZE', '250000')) ) payment_allocation_description: str = field( default_factory=lambda: os.environ.get( 'PAYMENT_ALLOCATION_DESCRIPTION', 'Flowthrough payment allocation' ) ) sentry_dsn: str | None = None mysql: MySQLConfig = field(init=False) def __post_init__(self) -> None: """Initialize connectors and override with secrets for non-dev environments.""" self.mysql = self._init_mysql() if self.env.is_managed: self._apply_secrets() def _init_mysql(self) -> MySQLConfig: return MySQLConfig( host=os.environ.get('MYSQL_DB_HOST', ''), user=os.environ.get('MYSQL_DB_USER', ''), password=os.environ.get('MYSQL_DB_PASS', ''), database=os.environ.get('MYSQL_DB_NAME', 'royalty_accounting'), port=int(os.environ.get('MYSQL_DB_PORT', '3306')), ) def _apply_secrets(self) -> None: """Fetch credentials from AWS Secrets Manager for higher environments.""" client = LambdaSecretsManager( environment=self.env.value, service_name=self.app_name ) self.mysql = MySQLConfig( **self.mysql.model_dump(exclude={'password'}), password=client.get_cred('MYSQL_DB_PASS'), ) try: self.sentry_dsn = client.get_cred('SENTRY_DSN') except Exception: pass def validate_config(cfg: AppConfig) -> None: """Validate configuration values on startup to fail fast. Raises: ValueError: If any configuration value is logically invalid. """ errors = [] if not (1_000 <= cfg.batch_size <= 250_000): errors.append(f'batch_size must be [1_000, 250_000], got {cfg.batch_size}') if not (1_000 <= cfg.contract_batch_size <= 250_000): errors.append( f'contract_batch_size must be [1_000, 250_000], got {cfg.contract_batch_size}' ) if not cfg.payment_allocation_description.strip(): errors.append('payment_allocation_description must not be empty') if cfg.env.is_managed: if not cfg.mysql.password: errors.append('mysql.password is empty after applying secrets') if errors: raise ValueError( 'Configuration validation failed:\n' + '\n'.join(f' - {e}' for e in errors) ) config = AppConfig() validate_config(config)