fund-flow-backend / src /config_loader.py
Aniket2006's picture
docs: improve enterprise code documentation, formatting, and 10-agent Copilot architecture details
2817797
Raw
History Blame Contribute Delete
5.25 kB
"""
================================================================================
CONFIGURATION LOADER - Centralized Settings Management
================================================================================
PURPOSE:
Single-point configuration loading for the entire application. Reads config.yaml
once at startup, caches the result, and resolves relative paths to absolute
paths for consistent behavior regardless of working directory.
KEY RESPONSIBILITIES:
1. Parse config.yaml (YAML format)
2. Cache configuration globally (singleton pattern)
3. Resolve relative paths to absolute paths
4. Support both project-root and working-directory lookups
5. Provide consistent paths for models, data, and ML artifacts
CONFIGURATION STRUCTURE (config.yaml):
graph: # Graph analysis settings
pagerank_alpha: 0.85 # PageRank damping factor
pagerank_max_iter: 100 # PageRank iterations
betweenness_k: auto # Betweenness centrality sampling
ml: # Machine learning configuration
model_path: models/... # Scikit-learn model location
scaler_path: models/... # Feature scaler pickle
gnn_model_path: models/... # PyTorch GNN weights
feature_columns: [...] # Expected input features
data: # Data paths
raw_path: data/raw/ # Raw transaction CSV
processed_path: data/proc/ # Processed data
transactions_path: data/... # Transaction database
alerts_db_path: data/alerts.db # Alert cache
detection: # Detector thresholds
cycle_threshold: 3 # Min cycle length
mule_threshold: 5 # Min mule members
round_trip_threshold: 0.8 # Confidence cutoff
api: # API configuration
host: 0.0.0.0
port: 8000
debug: true
PATH RESOLUTION:
Relative paths are resolved relative to project root:
config.yaml location → parent dir → project root
Lookup strategy:
1. Try config_path = ${PROJECT_ROOT}/config.yaml
2. If not found: Try config_path = cwd/config.yaml
3. Use whichever directory contains config.yaml as base
Path resolution (ml and data sections):
- Absolute paths (starting with /) are used as-is
- Relative paths are joined with project_root
- Result: paths work regardless of cwd
SINGLETON PATTERN:
_config: Global cache (module-level)
- None until first get_config() call
- Loaded once, reused thereafter
- Thread-safe for read operations
- (Update requires module reload)
PERFORMANCE:
- Single YAML parse (not per-request)
- Cached in memory
- Path resolution happens once at load time
- No file I/O after initial load
DEPENDENCIES:
- PyYAML: YAML parsing
- os: Path manipulation
USAGE EXAMPLE:
from src.config_loader import get_config
# First call loads and caches
cfg = get_config()
# Subsequent calls return cached result
cfg = get_config() # No file I/O, instant
# Access settings
pagerank_alpha = cfg['graph']['pagerank_alpha']
model_path = cfg['ml']['model_path'] # Absolute path
# Create loader with config
loader = DataLoader(cfg['data']['raw_path'])
predictor = HybridPredictor()
predictor.load_models() # Uses cfg['ml']['...']
CONFIGURATION BEST PRACTICES:
1. Store config.yaml in project root (not in src/)
2. Use relative paths (so it works in any deployment)
3. Never hardcode paths in code
4. Use get_config() for all settings
5. Document all config options in config.yaml
ERROR HANDLING:
- FileNotFoundError if config.yaml not found
- yaml.YAMLError if YAML syntax invalid
- KeyError if accessing non-existent config key (handle explicitly)
NOTES:
- Configuration is read-only after startup
- To change config: restart the application
- Environment variables can override (if added to loader)
- No validation of config values (application code must check)
================================================================================
"""
import yaml
import os
_config = None
def get_config() -> dict:
global _config
if _config is None:
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
config_path = os.path.join(project_root, 'config.yaml')
if not os.path.exists(config_path):
config_path = 'config.yaml'
project_root = os.getcwd()
with open(config_path, 'r') as f:
_config = yaml.safe_load(f)
# Resolve relative model/data paths to absolute so they work regardless of cwd
if 'ml' in _config:
for key in ('model_path', 'scaler_path', 'gnn_model_path'):
if key in _config['ml'] and not os.path.isabs(_config['ml'][key]):
_config['ml'][key] = os.path.join(project_root, _config['ml'][key])
if 'data' in _config:
for key in ('raw_path', 'processed_path', 'transactions_path', 'alerts_db_path'):
if key in _config['data'] and not os.path.isabs(_config['data'][key]):
_config['data'][key] = os.path.join(project_root, _config['data'][key])
return _config