haiku.rag/haiku_rag_slim/haiku/rag/config/loader.py
2026-06-09 12:11:33 +03:00

88 lines
2.5 KiB
Python

import logging
import os
from pathlib import Path
from typing import Any
import yaml
logger = logging.getLogger(__name__)
def find_config_file(cli_path: Path | None = None) -> Path | None:
"""Find the YAML config file using the search path.
Search order:
1. CLI-provided path (via HAIKU_RAG_CONFIG_PATH env var or parameter)
2. ./haiku.rag.yaml (current directory)
3. Platform-specific user config directory
Returns None if no config file is found.
"""
# Check environment variable first (set by CLI --config flag)
if not cli_path:
env_path = os.getenv("HAIKU_RAG_CONFIG_PATH")
if env_path:
cli_path = Path(env_path).expanduser()
if cli_path:
if cli_path.exists():
return cli_path
raise FileNotFoundError(f"Config file not found: {cli_path}")
cwd_config = Path.cwd() / "haiku.rag.yaml"
if cwd_config.exists():
return cwd_config
# Use same directory as data storage for config
from haiku.rag.utils import get_default_data_dir
data_dir = get_default_data_dir()
user_config = data_dir / "haiku.rag.yaml"
if user_config.exists():
return user_config
return None
def load_yaml_config(path: Path) -> dict:
"""Load and parse a YAML config file."""
with open(path) as f:
data = yaml.safe_load(f)
return data or {}
def generate_default_config() -> dict:
"""Generate a default YAML config structure from AppConfig defaults."""
from haiku.rag.config.models import AppConfig
default_config = AppConfig()
return default_config.model_dump(mode="json", exclude_none=False)
_SECRET_KEY_HINTS = ("key", "password", "token", "secret")
def _is_secret_key(key: str) -> bool:
lowered = key.lower()
return any(hint in lowered for hint in _SECRET_KEY_HINTS)
def redact_secrets(data: Any) -> Any:
"""Recursively mask secret-bearing values in a config dump. Any scalar
whose key contains key/password/token/secret becomes "***" when set or
None when unset; everything else is preserved."""
if isinstance(data, dict):
result = {}
for key, value in data.items():
if (
isinstance(key, str)
and _is_secret_key(key)
and not isinstance(value, (dict, list))
):
result[key] = "***" if value else None
else:
result[key] = redact_secrets(value)
return result
if isinstance(data, list):
return [redact_secrets(item) for item in data]
return data