refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route modules in bot/routes/: core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6), bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13), profile_picture.py (26), manual_send.py (3), servers.py (6), figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1), config.py (7), logging_config.py (9), voice.py (3), memory.py (10) All 146 routes verified present via test_route_split.py (149 tests). 21/21 regression tests (test_config_state.py) pass. Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
This commit is contained in:
183
bot/routes/config.py
Normal file
183
bot/routes/config.py
Normal file
@@ -0,0 +1,183 @@
|
||||
"""Configuration management routes: get/set/reset/validate config."""
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
import globals
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger('api')
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
async def get_full_config():
|
||||
"""
|
||||
Get full configuration including static, runtime, and state.
|
||||
Useful for debugging and config display in UI.
|
||||
"""
|
||||
try:
|
||||
from config_manager import config_manager
|
||||
full_config = config_manager.get_full_config()
|
||||
return {
|
||||
"success": True,
|
||||
"config": full_config
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.get("/config/static")
|
||||
async def get_static_config():
|
||||
"""
|
||||
Get static configuration from config.yaml.
|
||||
These are default values that can be overridden at runtime.
|
||||
"""
|
||||
try:
|
||||
from config_manager import config_manager
|
||||
return {
|
||||
"success": True,
|
||||
"config": config_manager.static_config
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get static config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.get("/config/runtime")
|
||||
async def get_runtime_config():
|
||||
"""
|
||||
Get runtime configuration overrides.
|
||||
These are values changed via Web UI that override config.yaml.
|
||||
"""
|
||||
try:
|
||||
from config_manager import config_manager
|
||||
return {
|
||||
"success": True,
|
||||
"config": config_manager.runtime_config,
|
||||
"path": str(config_manager.runtime_config_path)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get runtime config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.post("/config/set")
|
||||
async def set_config_value(request: Request):
|
||||
"""
|
||||
Set a configuration value with optional persistence.
|
||||
|
||||
Body: {
|
||||
"key_path": "discord.language_mode", // Dot-separated path
|
||||
"value": "japanese",
|
||||
"persist": true // Save to config_runtime.yaml
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = await request.json()
|
||||
key_path = data.get("key_path")
|
||||
value = data.get("value")
|
||||
persist = data.get("persist", True)
|
||||
|
||||
if not key_path:
|
||||
return {"success": False, "error": "key_path is required"}
|
||||
|
||||
from config_manager import config_manager
|
||||
config_manager.set(key_path, value, persist=persist)
|
||||
|
||||
# ── Sync globals for every runtime-relevant key path ──
|
||||
_GLOBALS_SYNC = {
|
||||
"discord.language_mode": ("LANGUAGE_MODE", str),
|
||||
"autonomous.debug_mode": ("AUTONOMOUS_DEBUG", bool),
|
||||
"voice.debug_mode": ("VOICE_DEBUG_MODE", bool),
|
||||
"memory.use_cheshire_cat": ("USE_CHESHIRE_CAT", bool),
|
||||
"gpu.prefer_amd": ("PREFER_AMD_GPU", bool),
|
||||
}
|
||||
|
||||
if key_path in _GLOBALS_SYNC:
|
||||
attr, converter = _GLOBALS_SYNC[key_path]
|
||||
setattr(globals, attr, converter(value))
|
||||
elif key_path == "runtime.mood.dm_mood":
|
||||
# DM mood needs description loaded alongside
|
||||
if isinstance(value, str) and value in getattr(globals, "AVAILABLE_MOODS", []):
|
||||
globals.DM_MOOD = value
|
||||
try:
|
||||
from utils.moods import load_mood_description
|
||||
globals.DM_MOOD_DESCRIPTION = load_mood_description(value)
|
||||
except Exception:
|
||||
globals.DM_MOOD_DESCRIPTION = f"I'm feeling {value} today."
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Set {key_path} = {value}",
|
||||
"persisted": persist
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.post("/config/reset")
|
||||
async def reset_config(request: Request):
|
||||
"""
|
||||
Reset configuration to defaults.
|
||||
|
||||
Body: {
|
||||
"key_path": "discord.language_mode", // Optional: reset specific key
|
||||
"persist": true // Remove from config_runtime.yaml
|
||||
}
|
||||
|
||||
If key_path is omitted, resets all runtime config to defaults.
|
||||
"""
|
||||
try:
|
||||
data = await request.json()
|
||||
key_path = data.get("key_path")
|
||||
persist = data.get("persist", True)
|
||||
|
||||
from config_manager import config_manager
|
||||
config_manager.reset_to_defaults(key_path)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Reset {key_path or 'all config'} to defaults"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reset config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.post("/config/validate")
|
||||
async def validate_config_endpoint():
|
||||
"""
|
||||
Validate current configuration.
|
||||
Returns list of errors if validation fails.
|
||||
"""
|
||||
try:
|
||||
from config_manager import config_manager
|
||||
is_valid, errors = config_manager.validate_config()
|
||||
|
||||
return {
|
||||
"success": is_valid,
|
||||
"is_valid": is_valid,
|
||||
"errors": errors
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to validate config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.get("/config/state")
|
||||
async def get_config_state():
|
||||
"""
|
||||
Get runtime state (not persisted config).
|
||||
These are transient values like current mood, evil mode, etc.
|
||||
"""
|
||||
try:
|
||||
from config_manager import config_manager
|
||||
return {
|
||||
"success": True,
|
||||
"state": config_manager.runtime_state
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get config state: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
Reference in New Issue
Block a user