nanobot/nanobot/config/loader.py

117 lines
3.6 KiB
Python

"""Configuration loading utilities."""
import json
import os
from pathlib import Path
from nanobot.config.schema import Config
def get_config_path() -> Path:
"""Get the default configuration file path."""
return Path.home() / ".nanobot" / "config.json"
def get_data_dir() -> Path:
"""Get the nanobot data directory."""
from nanobot.utils.helpers import get_data_path
return get_data_path()
def _load_env_file(workspace: Path | None = None) -> None:
"""Load .env file from workspace directory if it exists."""
if workspace:
env_file = Path(workspace) / ".env"
else:
# Try current directory and workspace
env_file = Path(".env")
if not env_file.exists():
# Try workspace directory
try:
from nanobot.utils.helpers import get_workspace_path
workspace_path = get_workspace_path()
env_file = workspace_path / ".env"
except:
pass
if env_file.exists():
try:
with open(env_file) as f:
for line in f:
line = line.strip()
# Skip comments and empty lines
if not line or line.startswith("#"):
continue
# Parse KEY=VALUE format
if "=" in line:
key, value = line.split("=", 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
# Only set if not already in environment
if key and key not in os.environ:
os.environ[key] = value
except Exception:
# Silently fail if .env can't be loaded
pass
def load_config(config_path: Path | None = None) -> Config:
"""
Load configuration from file or create default.
Args:
config_path: Optional path to config file. Uses default if not provided.
Returns:
Loaded configuration object.
"""
# Load .env file before loading config (so env vars are available to pydantic)
try:
from nanobot.utils.helpers import get_workspace_path
workspace = get_workspace_path()
_load_env_file(workspace)
except:
# Fallback to current directory
_load_env_file()
path = config_path or get_config_path()
if path.exists():
try:
with open(path) as f:
data = json.load(f)
data = _migrate_config(data)
return Config.model_validate(data)
except (json.JSONDecodeError, ValueError) as e:
print(f"Warning: Failed to load config from {path}: {e}")
print("Using default configuration.")
return Config()
def save_config(config: Config, config_path: Path | None = None) -> None:
"""
Save configuration to file.
Args:
config: Configuration to save.
config_path: Optional path to save to. Uses default if not provided.
"""
path = config_path or get_config_path()
path.parent.mkdir(parents=True, exist_ok=True)
data = config.model_dump(by_alias=True)
with open(path, "w") as f:
json.dump(data, f, indent=2)
def _migrate_config(data: dict) -> dict:
"""Migrate old config formats to current."""
# Move tools.exec.restrictToWorkspace → tools.restrictToWorkspace
tools = data.get("tools", {})
exec_cfg = tools.get("exec", {})
if "restrictToWorkspace" in exec_cfg and "restrictToWorkspace" not in tools:
tools["restrictToWorkspace"] = exec_cfg.pop("restrictToWorkspace")
return data