"""Configuration management using Pydantic settings.""" import os from typing import List, Literal from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """ Application settings with environment variable support. Settings are loaded from (in order of precedence): 1. Environment variables 2. .env file (if present) 3. Default values Works without .env file for HuggingFace Spaces and containerized deployments. """ model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore", # Don't error if .env file is missing (for HF Spaces, Docker, etc.) env_ignore_empty=True, ) # Application Settings app_name: str = Field(default="AI Writing Studio", description="Application name") app_version: str = Field(default="1.0.0", description="Application version") environment: Literal["development", "staging", "production"] = Field( default="development", description="Runtime environment" ) debug: bool = Field(default=False, description="Enable debug mode") # Server Configuration host: str = Field(default="0.0.0.0", description="Server host") port: int = Field(default=7860, ge=1, le=65535, description="Server port") server_workers: int = Field(default=4, ge=1, description="Number of worker processes") # Model Configuration default_model: str = Field( default="google/flan-t5-base", description="Default HuggingFace model (instruction-tuned for revision)" ) max_model_length: int = Field(default=512, ge=1, description="Maximum model input length") default_max_length: int = Field(default=512, ge=1, description="Default generation length") default_num_sequences: int = Field(default=1, ge=1, description="Number of sequences") # Security allowed_origins: str = Field( default="http://localhost:7860,http://127.0.0.1:7860", description="Comma-separated CORS origins", ) rate_limit_per_minute: int = Field(default=10, ge=1, description="Rate limit per minute") max_text_length: int = Field( default=10000, ge=1, description="Maximum input text length" ) enable_auth: bool = Field(default=False, description="Enable authentication") secret_key: str = Field(default="", description="Secret key for sessions") # Logging log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field( default="INFO", description="Logging level" ) log_format: Literal["json", "text"] = Field(default="json", description="Log format") log_file_path: str = Field(default="./logs/app.log", description="Log file path") log_max_bytes: int = Field(default=10485760, ge=1, description="Max log file size") log_backup_count: int = Field(default=5, ge=0, description="Number of log backups") # Monitoring enable_metrics: bool = Field(default=True, description="Enable Prometheus metrics") metrics_port: int = Field(default=8000, ge=1, le=65535, description="Metrics port") # Cache Configuration enable_cache: bool = Field(default=True, description="Enable caching") cache_ttl: int = Field(default=3600, ge=1, description="Cache TTL in seconds") cache_max_size: int = Field(default=100, ge=1, description="Maximum cache entries") # Feature Flags enable_diff_highlighting: bool = Field(default=True, description="Enable diff view") enable_rubric_scoring: bool = Field(default=True, description="Enable rubric scoring") enable_prompt_packs: bool = Field(default=True, description="Enable prompt packs") @field_validator("allowed_origins") @classmethod def parse_origins(cls, v: str) -> List[str]: """Parse comma-separated origins into a list.""" if isinstance(v, str): return [origin.strip() for origin in v.split(",") if origin.strip()] return v @field_validator("log_file_path") @classmethod def ensure_directory_exists(cls, v: str) -> str: """Ensure directory exists for file paths.""" directory = os.path.dirname(v) if os.path.splitext(v)[1] else v if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) return v @property def is_production(self) -> bool: """Check if running in production.""" return self.environment == "production" @property def is_development(self) -> bool: """Check if running in development.""" return self.environment == "development" # Global settings instance settings = Settings()