Files
2026-07-13 13:26:28 +08:00

122 lines
4.5 KiB
Python

"""Centralized configuration using python-dotenv.
This module loads all configuration from the .env file in the repository root.
It provides explicit, fail-fast configuration with clear error messages.
Configuration priority:
1. .env file (ONLY source - no fallbacks)
2. Validation ensures paths exist or provides clear instructions
Usage:
from utils import ML4T_PATH, ML4T_DATA_PATH, REPO_ROOT
"""
import os
from pathlib import Path
from dotenv import load_dotenv
# Find repository root (where .env lives)
REPO_ROOT = Path(__file__).parent.parent.resolve()
ENV_FILE = REPO_ROOT / ".env"
# Auto-create .env from .env.example if missing (CI, first-time setup)
if not ENV_FILE.exists():
example = REPO_ROOT / ".env.example"
if example.exists():
import contextlib
import shutil
with contextlib.suppress(OSError):
shutil.copy(example, ENV_FILE)
else:
raise FileNotFoundError(
f".env file not found at {ENV_FILE}\n\n"
f"Copy .env.example to .env and configure paths:\n"
f" cd {REPO_ROOT}\n"
f" cp .env.example .env\n"
f" # Edit .env with your paths:\n"
f" # ML4T_PATH={REPO_ROOT}\n"
f" # ML4T_DATA_PATH=/path/to/data\n"
)
# Load environment variables from .env
# override=False means environment variables take precedence over .env file
load_dotenv(ENV_FILE, override=False)
# ============================================================================
# Required Paths
# ============================================================================
# Priority: 1. Environment variable, 2. .env file, 3. Default
ML4T_PATH = Path(os.getenv("ML4T_PATH", str(REPO_ROOT))).expanduser().resolve()
data_path = os.getenv("ML4T_DATA_PATH")
if data_path is None:
ML4T_DATA_PATH = ML4T_PATH / "data"
else:
ML4T_DATA_PATH = Path(data_path).expanduser().resolve()
# Case studies directory - centralized artifact store for all strategies
# Default: case_studies/ in repo root (git-tracked configs, gitignored binaries)
case_studies_path = os.getenv("CASE_STUDIES_DIR")
if case_studies_path is None:
CASE_STUDIES_DIR = REPO_ROOT / "case_studies"
else:
CASE_STUDIES_DIR = Path(case_studies_path).expanduser().resolve()
# ============================================================================
# Validation
# ============================================================================
# Validate ML4T_PATH exists
if not ML4T_PATH.exists():
raise FileNotFoundError(
f"ML4T_PATH does not exist: {ML4T_PATH}\n\n"
f"Update ML4T_PATH in .env to point to the repository root."
)
# Validate ML4T_DATA_PATH exists
if not ML4T_DATA_PATH.exists():
raise FileNotFoundError(
f"Data directory not found: {ML4T_DATA_PATH}\n\n"
f"Options:\n"
f" 1. Download data:\n"
f" python data/download_all.py\n"
f" 2. Update ML4T_DATA_PATH in .env to point to existing data\n"
)
# ============================================================================
# CUDA Library Path (local uv environments)
# ============================================================================
# PyTorch bundles its own CUDA libraries which may be newer than system ones.
# Without this, torch imports fail with "undefined symbol" errors on systems
# where the system libcudart.so is older than what torch expects.
# Docker images don't need this (CUDA is installed system-wide).
if not os.environ.get("LD_LIBRARY_PATH", "").startswith("/usr/local/cuda"):
try:
import torch as _torch
_torch_root = Path(_torch.__file__).parent
_cuda_paths = [str(_torch_root / "lib")]
_nvidia_dir = _torch_root.parent / "nvidia"
if _nvidia_dir.exists():
_cuda_paths.extend(str(p) for p in _nvidia_dir.glob("*/lib"))
_existing_ld = os.environ.get("LD_LIBRARY_PATH", "")
os.environ["LD_LIBRARY_PATH"] = ":".join(
_cuda_paths + ([_existing_ld] if _existing_ld else [])
)
del _torch, _torch_root, _cuda_paths, _nvidia_dir, _existing_ld
except ImportError:
pass
# ============================================================================
# API Keys (optional - only needed for data downloads)
# ============================================================================
# These are optional and only needed if you're downloading data
DATABENTO_API_KEY = os.getenv("DATABENTO_API_KEY", "")
OANDA_API_KEY = os.getenv("OANDA_API_KEY", "")
ALPACA_API_KEY = os.getenv("ALPACA_API_KEY", "")
ALPACA_SECRET_KEY = os.getenv("ALPACA_SECRET_KEY", "")