""" Core Configuration - ServiceManagerWeb Configuración centralizada usando Pydantic Settings v2 """ from functools import lru_cache from typing import List, Optional from pydantic_settings import BaseSettings from pydantic import field_validator, Field import os class Settings(BaseSettings): """Configuración de la aplicación.""" model_config = { "env_file": ".env", "env_file_encoding": "utf-8", "case_sensitive": False } # =================================== # GENERAL # =================================== ENVIRONMENT: str = Field(default="development") DEBUG: bool = Field(default=False) SECRET_KEY: str = Field(...) API_VERSION: str = Field(default="v1") # =================================== # DATABASE # =================================== DATABASE_URL: str = Field(...) # =================================== # REDIS # =================================== REDIS_URL: str = Field(...) # =================================== # JWT AUTHENTICATION # =================================== JWT_SECRET_KEY: str = Field(...) JWT_ALGORITHM: str = Field(default="HS256") ACCESS_TOKEN_EXPIRE_MINUTES: int = Field(default=60) REFRESH_TOKEN_EXPIRE_DAYS: int = Field(default=7) # =================================== # CORS # =================================== CORS_ORIGINS: str = Field( default="http://localhost:3000,http://localhost:3001" ) # =================================== # EMAIL # =================================== SMTP_HOST: str = Field(default="localhost") SMTP_PORT: int = Field(default=587) SMTP_USER: Optional[str] = Field(default=None) SMTP_PASSWORD: Optional[str] = Field(default=None) SMTP_USE_TLS: bool = Field(default=True) SMTP_USE_SSL: bool = Field(default=False) DEFAULT_FROM_EMAIL: str = Field(default="noreply@servicemanager.local") DEFAULT_FROM_NAME: str = Field(default="ServiceManager") # =================================== # FILE UPLOADS # =================================== MAX_UPLOAD_SIZE_MB: int = Field(default=10) ALLOWED_FILE_EXTENSIONS_STR: str = Field( default="pdf,jpg,jpeg,png,doc,docx,xls,xlsx,txt", alias="ALLOWED_FILE_EXTENSIONS" ) UPLOAD_PATH: str = Field(default="/app/uploads") @property def ALLOWED_FILE_EXTENSIONS(self) -> List[str]: """Parse the comma-separated file extensions.""" return [ext.strip().lower() for ext in self.ALLOWED_FILE_EXTENSIONS_STR.split(",")] # =================================== # SECURITY # =================================== RATE_LIMIT_ENABLED: bool = Field(default=True) PASSWORD_MIN_LENGTH: int = Field(default=8) # Argon2 settings ARGON2_TIME_COST: int = Field(default=3) ARGON2_MEMORY_COST: int = Field(default=65536) ARGON2_PARALLELISM: int = Field(default=4) # =================================== # LOGGING # =================================== LOG_LEVEL: str = Field(default="INFO") LOG_FORMAT: str = Field(default="json") LOG_FILE: Optional[str] = Field(default=None) # =================================== # FRONTEND URLS # =================================== CLIENT_FRONTEND_URL: str = Field(default="http://localhost:3000") INTERNAL_FRONTEND_URL: str = Field(default="http://localhost:3001") # =================================== # HEALTH CHECKS # =================================== HEALTH_CHECK_TIMEOUT: int = Field(default=30) # =================================== # CELERY # =================================== CELERY_BROKER_URL: str = Field(...) CELERY_RESULT_BACKEND: str = Field(...) def is_production(self) -> bool: """Check if environment is production.""" return self.ENVIRONMENT.lower() == "production" def is_development(self) -> bool: """Check if environment is development.""" return self.ENVIRONMENT.lower() == "development" def is_testing(self) -> bool: """Check if environment is testing.""" return self.ENVIRONMENT.lower() == "testing" @lru_cache() def get_settings() -> Settings: """ Get cached settings instance. Using lru_cache to create a singleton pattern for settings. """ return Settings()