""" DigiPal Configuration Management Handles environment-specific configuration for deployment """ import os from typing import Optional, Dict, Any from dataclasses import dataclass from pathlib import Path import logging @dataclass class DatabaseConfig: """Database configuration settings""" path: str = "digipal.db" backup_dir: str = "assets/backups" backup_interval_hours: int = 24 max_backups: int = 10 @dataclass class AIModelConfig: """AI model configuration settings""" qwen_model: str = "Qwen/Qwen3-0.6B" kyutai_model: str = "kyutai/stt-2.6b-en-trfs" flux_model: str = "black-forest-labs/FLUX.1-dev" device: str = "auto" torch_dtype: str = "auto" enable_quantization: bool = True max_memory_gb: Optional[int] = None @dataclass class GradioConfig: """Gradio interface configuration""" server_name: str = "0.0.0.0" server_port: int = 7860 share: bool = False debug: bool = False auth: Optional[tuple] = None ssl_keyfile: Optional[str] = None ssl_certfile: Optional[str] = None @dataclass class MCPConfig: """MCP server configuration""" enabled: bool = True host: str = "localhost" port: int = 8080 max_connections: int = 100 timeout_seconds: int = 30 @dataclass class LoggingConfig: """Logging configuration""" level: str = "INFO" format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" file_path: Optional[str] = "logs/digipal.log" max_file_size_mb: int = 10 backup_count: int = 5 enable_structured_logging: bool = True @dataclass class SecurityConfig: """Security configuration""" secret_key: Optional[str] = None session_timeout_hours: int = 24 max_login_attempts: int = 5 rate_limit_per_minute: int = 60 enable_cors: bool = True allowed_origins: list = None @dataclass class PerformanceConfig: """Performance optimization settings""" cache_size_mb: int = 512 background_update_interval: int = 60 max_concurrent_users: int = 100 enable_model_caching: bool = True image_cache_max_age_days: int = 30 class DigiPalConfig: """Main configuration class for DigiPal application""" def __init__(self, env: str = None): self.env = env or os.getenv("DIGIPAL_ENV", "development") self.load_config() def load_config(self): """Load configuration based on environment""" # Base configuration self.database = DatabaseConfig() self.ai_models = AIModelConfig() self.gradio = GradioConfig() self.mcp = MCPConfig() self.logging = LoggingConfig() self.security = SecurityConfig() self.performance = PerformanceConfig() # Environment-specific overrides if self.env == "production": self._load_production_config() elif self.env == "testing": self._load_testing_config() elif self.env == "development": self._load_development_config() # Load from environment variables self._load_from_env() # Validate configuration self._validate_config() def _load_production_config(self): """Production environment configuration""" # Use /tmp for HuggingFace Spaces (writable temporary storage) import tempfile import os # Create temp directory for this session temp_dir = os.path.join(tempfile.gettempdir(), "digipal_session") os.makedirs(temp_dir, exist_ok=True) self.database.path = os.path.join(temp_dir, "digipal.db") self.database.backup_dir = os.path.join(temp_dir, "backups") self.gradio.debug = False self.gradio.share = False self.logging.level = "INFO" self.logging.file_path = os.path.join(temp_dir, "digipal.log") self.security.session_timeout_hours = 12 self.security.rate_limit_per_minute = 30 self.performance.cache_size_mb = 1024 self.performance.max_concurrent_users = 500 def _load_testing_config(self): """Testing environment configuration""" self.database.path = "test_digipal.db" self.database.backup_dir = "test_assets/backups" self.gradio.debug = True self.gradio.server_port = 7861 self.logging.level = "DEBUG" self.logging.file_path = None # Console only self.ai_models.enable_quantization = False self.performance.cache_size_mb = 128 def _load_development_config(self): """Development environment configuration""" self.gradio.debug = True self.gradio.share = False self.logging.level = "DEBUG" self.logging.file_path = "logs/digipal_dev.log" self.security.rate_limit_per_minute = 120 self.performance.cache_size_mb = 256 def _load_from_env(self): """Load configuration from environment variables""" # Database if os.getenv("DIGIPAL_DB_PATH"): self.database.path = os.getenv("DIGIPAL_DB_PATH") # Gradio if os.getenv("GRADIO_SERVER_NAME"): self.gradio.server_name = os.getenv("GRADIO_SERVER_NAME") if os.getenv("GRADIO_SERVER_PORT"): self.gradio.server_port = int(os.getenv("GRADIO_SERVER_PORT")) if os.getenv("GRADIO_SHARE"): self.gradio.share = os.getenv("GRADIO_SHARE").lower() == "true" # Logging if os.getenv("DIGIPAL_LOG_LEVEL"): self.logging.level = os.getenv("DIGIPAL_LOG_LEVEL") if os.getenv("DIGIPAL_LOG_FILE"): self.logging.file_path = os.getenv("DIGIPAL_LOG_FILE") # Security if os.getenv("DIGIPAL_SECRET_KEY"): self.security.secret_key = os.getenv("DIGIPAL_SECRET_KEY") # AI Models if os.getenv("QWEN_MODEL"): self.ai_models.qwen_model = os.getenv("QWEN_MODEL") if os.getenv("KYUTAI_MODEL"): self.ai_models.kyutai_model = os.getenv("KYUTAI_MODEL") if os.getenv("FLUX_MODEL"): self.ai_models.flux_model = os.getenv("FLUX_MODEL") def _validate_config(self): """Validate configuration settings""" # Ensure required directories exist (only if writable) try: Path(self.database.backup_dir).mkdir(parents=True, exist_ok=True) except (PermissionError, OSError) as e: # In HuggingFace Spaces, directories may be created automatically # or may not be writable during initialization logging.warning(f"Could not create backup directory {self.database.backup_dir}: {e}") if self.logging.file_path: try: Path(self.logging.file_path).parent.mkdir(parents=True, exist_ok=True) except (PermissionError, OSError) as e: logging.warning(f"Could not create log directory {Path(self.logging.file_path).parent}: {e}") # Validate port ranges if not (1024 <= self.gradio.server_port <= 65535): raise ValueError(f"Invalid Gradio port: {self.gradio.server_port}") if not (1024 <= self.mcp.port <= 65535): raise ValueError(f"Invalid MCP port: {self.mcp.port}") # Validate memory settings if self.performance.cache_size_mb < 64: logging.warning("Cache size is very low, performance may be affected") def get_database_url(self) -> str: """Get database connection URL""" return f"sqlite:///{self.database.path}" def get_log_config(self) -> Dict[str, Any]: """Get logging configuration dictionary""" config = { "version": 1, "disable_existing_loggers": False, "formatters": { "standard": { "format": self.logging.format }, "structured": { "()": "structlog.stdlib.ProcessorFormatter", "processor": "structlog.dev.ConsoleRenderer", } if self.logging.enable_structured_logging else { "format": self.logging.format } }, "handlers": { "console": { "class": "logging.StreamHandler", "level": self.logging.level, "formatter": "structured" if self.logging.enable_structured_logging else "standard", "stream": "ext://sys.stdout" } }, "loggers": { "digipal": { "level": self.logging.level, "handlers": ["console"], "propagate": False } }, "root": { "level": self.logging.level, "handlers": ["console"] } } # Add file handler if specified if self.logging.file_path: config["handlers"]["file"] = { "class": "logging.handlers.RotatingFileHandler", "level": self.logging.level, "formatter": "standard", "filename": self.logging.file_path, "maxBytes": self.logging.max_file_size_mb * 1024 * 1024, "backupCount": self.logging.backup_count } config["loggers"]["digipal"]["handlers"].append("file") config["root"]["handlers"].append("file") return config def to_dict(self) -> Dict[str, Any]: """Convert configuration to dictionary""" return { "env": self.env, "database": self.database.__dict__, "ai_models": self.ai_models.__dict__, "gradio": self.gradio.__dict__, "mcp": self.mcp.__dict__, "logging": self.logging.__dict__, "security": self.security.__dict__, "performance": self.performance.__dict__ } # Global configuration instance config = DigiPalConfig() def get_config() -> DigiPalConfig: """Get the global configuration instance""" return config def reload_config(env: str = None): """Reload configuration with optional environment override""" global config config = DigiPalConfig(env) return config