重构配置逻辑
This commit is contained in:
37
reelforge/config/__init__.py
Normal file
37
reelforge/config/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
ReelForge Configuration System
|
||||
|
||||
Unified configuration management with Pydantic validation.
|
||||
|
||||
Usage:
|
||||
from reelforge.config import config_manager
|
||||
|
||||
# Access config (type-safe)
|
||||
api_key = config_manager.config.llm.api_key
|
||||
|
||||
# Update config
|
||||
config_manager.update({"llm": {"api_key": "xxx"}})
|
||||
config_manager.save()
|
||||
|
||||
# Validate
|
||||
if config_manager.validate():
|
||||
print("Config is valid!")
|
||||
"""
|
||||
from .schema import ReelForgeConfig, LLMConfig, TTSConfig, ImageConfig
|
||||
from .manager import ConfigManager
|
||||
from .loader import load_config_dict, save_config_dict
|
||||
|
||||
# Global singleton instance
|
||||
config_manager = ConfigManager()
|
||||
|
||||
__all__ = [
|
||||
"ReelForgeConfig",
|
||||
"LLMConfig",
|
||||
"TTSConfig",
|
||||
"ImageConfig",
|
||||
"ConfigManager",
|
||||
"config_manager",
|
||||
"load_config_dict",
|
||||
"save_config_dict",
|
||||
]
|
||||
|
||||
53
reelforge/config/loader.py
Normal file
53
reelforge/config/loader.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Configuration loader - Pure YAML
|
||||
|
||||
Handles loading and saving configuration from/to YAML files.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def load_config_dict(config_path: str = "config.yaml") -> dict:
|
||||
"""
|
||||
Load configuration from YAML file
|
||||
|
||||
Args:
|
||||
config_path: Path to config file
|
||||
|
||||
Returns:
|
||||
Configuration dictionary
|
||||
"""
|
||||
config_file = Path(config_path)
|
||||
|
||||
if not config_file.exists():
|
||||
logger.warning(f"Config file not found: {config_path}")
|
||||
logger.info("Using default configuration")
|
||||
return {}
|
||||
|
||||
try:
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
logger.info(f"Configuration loaded from {config_path}")
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load config: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def save_config_dict(config: dict, config_path: str = "config.yaml"):
|
||||
"""
|
||||
Save configuration to YAML file
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
config_path: Path to config file
|
||||
"""
|
||||
try:
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(config, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
||||
logger.info(f"Configuration saved to {config_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save config: {e}")
|
||||
raise
|
||||
|
||||
119
reelforge/config/manager.py
Normal file
119
reelforge/config/manager.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
Configuration Manager - Singleton pattern
|
||||
|
||||
Provides unified access to configuration with automatic validation.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from loguru import logger
|
||||
from .schema import ReelForgeConfig
|
||||
from .loader import load_config_dict, save_config_dict
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
"""
|
||||
Configuration Manager (Singleton)
|
||||
|
||||
Provides unified access to configuration with automatic validation.
|
||||
"""
|
||||
_instance: Optional['ConfigManager'] = None
|
||||
|
||||
def __new__(cls, config_path: str = "config.yaml"):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, config_path: str = "config.yaml"):
|
||||
# Only initialize once
|
||||
if hasattr(self, '_initialized'):
|
||||
return
|
||||
|
||||
self.config_path = Path(config_path)
|
||||
self.config: ReelForgeConfig = self._load()
|
||||
self._initialized = True
|
||||
|
||||
def _load(self) -> ReelForgeConfig:
|
||||
"""Load configuration from file"""
|
||||
data = load_config_dict(str(self.config_path))
|
||||
return ReelForgeConfig(**data)
|
||||
|
||||
def reload(self):
|
||||
"""Reload configuration from file"""
|
||||
self.config = self._load()
|
||||
logger.info("Configuration reloaded")
|
||||
|
||||
def save(self):
|
||||
"""Save current configuration to file"""
|
||||
save_config_dict(self.config.to_dict(), str(self.config_path))
|
||||
|
||||
def update(self, updates: dict):
|
||||
"""
|
||||
Update configuration with new values
|
||||
|
||||
Args:
|
||||
updates: Dictionary of updates (e.g., {"llm": {"api_key": "xxx"}})
|
||||
"""
|
||||
current = self.config.to_dict()
|
||||
|
||||
# Deep merge
|
||||
def deep_merge(base: dict, updates: dict) -> dict:
|
||||
for key, value in updates.items():
|
||||
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
||||
deep_merge(base[key], value)
|
||||
else:
|
||||
base[key] = value
|
||||
return base
|
||||
|
||||
merged = deep_merge(current, updates)
|
||||
self.config = ReelForgeConfig(**merged)
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""Dict-like access (for backward compatibility)"""
|
||||
return self.config.to_dict().get(key, default)
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate configuration completeness"""
|
||||
return self.config.validate_required()
|
||||
|
||||
def get_llm_config(self) -> dict:
|
||||
"""Get LLM configuration as dict"""
|
||||
return {
|
||||
"api_key": self.config.llm.api_key,
|
||||
"base_url": self.config.llm.base_url,
|
||||
"model": self.config.llm.model,
|
||||
}
|
||||
|
||||
def set_llm_config(self, api_key: str, base_url: str, model: str):
|
||||
"""Set LLM configuration"""
|
||||
self.update({
|
||||
"llm": {
|
||||
"api_key": api_key,
|
||||
"base_url": base_url,
|
||||
"model": model,
|
||||
}
|
||||
})
|
||||
|
||||
def get_image_config(self) -> dict:
|
||||
"""Get image configuration as dict"""
|
||||
return {
|
||||
"default": self.config.image.default,
|
||||
"comfyui_url": self.config.image.comfyui_url,
|
||||
"runninghub_api_key": self.config.image.runninghub_api_key,
|
||||
"prompt_prefix": self.config.image.prompt_prefix,
|
||||
}
|
||||
|
||||
def set_image_config(
|
||||
self,
|
||||
comfyui_url: Optional[str] = None,
|
||||
runninghub_api_key: Optional[str] = None
|
||||
):
|
||||
"""Set image configuration"""
|
||||
updates = {}
|
||||
if comfyui_url is not None:
|
||||
updates["comfyui_url"] = comfyui_url
|
||||
if runninghub_api_key is not None:
|
||||
updates["runninghub_api_key"] = runninghub_api_key
|
||||
|
||||
if updates:
|
||||
self.update({"image": updates})
|
||||
|
||||
54
reelforge/config/schema.py
Normal file
54
reelforge/config/schema.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Configuration schema with Pydantic models
|
||||
|
||||
Single source of truth for all configuration defaults and validation.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class LLMConfig(BaseModel):
|
||||
"""LLM configuration"""
|
||||
api_key: str = Field(default="", description="LLM API Key")
|
||||
base_url: str = Field(default="", description="LLM API Base URL")
|
||||
model: str = Field(default="", description="LLM Model Name")
|
||||
|
||||
|
||||
class TTSConfig(BaseModel):
|
||||
"""TTS configuration"""
|
||||
default: str = Field(default="edge", description="Default TTS workflow")
|
||||
|
||||
|
||||
class ImageConfig(BaseModel):
|
||||
"""Image generation configuration"""
|
||||
default: str = Field(default="image_default.json", description="Default image workflow")
|
||||
comfyui_url: str = Field(default="http://127.0.0.1:8188", description="ComfyUI Server URL")
|
||||
runninghub_api_key: str = Field(default="", description="RunningHub API Key (optional)")
|
||||
prompt_prefix: str = Field(
|
||||
default="Pure white background, minimalist illustration, matchstick figure style, black and white line drawing, simple clean lines",
|
||||
description="Prompt prefix for all image generation"
|
||||
)
|
||||
|
||||
|
||||
class ReelForgeConfig(BaseModel):
|
||||
"""ReelForge main configuration"""
|
||||
project_name: str = Field(default="ReelForge", description="Project name")
|
||||
llm: LLMConfig = Field(default_factory=LLMConfig)
|
||||
tts: TTSConfig = Field(default_factory=TTSConfig)
|
||||
image: ImageConfig = Field(default_factory=ImageConfig)
|
||||
|
||||
def is_llm_configured(self) -> bool:
|
||||
"""Check if LLM is properly configured"""
|
||||
return bool(
|
||||
self.llm.api_key and self.llm.api_key.strip() and
|
||||
self.llm.base_url and self.llm.base_url.strip() and
|
||||
self.llm.model and self.llm.model.strip()
|
||||
)
|
||||
|
||||
def validate_required(self) -> bool:
|
||||
"""Validate required configuration"""
|
||||
return self.is_llm_configured()
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary (for backward compatibility)"""
|
||||
return self.model_dump()
|
||||
|
||||
Reference in New Issue
Block a user