from pydantic_settings import BaseSettings from functools import lru_cache class Settings(BaseSettings): # Environment env: str = "development" # "development" or "production" # Server server_host: str = "0.0.0.0" server_port: int = 8080 # Database database_host: str = "localhost" database_port: int = 5432 database_user: str = "orchard" database_password: str = "" database_dbname: str = "orchard" database_sslmode: str = "disable" # Database connection pool settings database_pool_size: int = 5 # Number of connections to keep open database_max_overflow: int = 10 # Max additional connections beyond pool_size database_pool_timeout: int = 30 # Seconds to wait for a connection from pool database_pool_recycle: int = ( 1800 # Recycle connections after this many seconds (30 min) ) database_query_timeout: int = 30 # Query timeout in seconds (0 = no timeout) # S3 s3_endpoint: str = "" s3_region: str = "us-east-1" s3_bucket: str = "orchard-artifacts" s3_access_key_id: str = "" s3_secret_access_key: str = "" s3_use_path_style: bool = True s3_verify_ssl: bool = True # Set to False for self-signed certs (dev only) s3_connect_timeout: int = 10 # Connection timeout in seconds s3_read_timeout: int = 60 # Read timeout in seconds s3_max_retries: int = 3 # Max retry attempts for transient failures # Upload settings max_file_size: int = 10 * 1024 * 1024 * 1024 # 10GB default max file size min_file_size: int = 1 # Minimum 1 byte (empty files rejected) # Download settings download_mode: str = "presigned" # "presigned", "redirect", or "proxy" presigned_url_expiry: int = ( 3600 # Presigned URL expiry in seconds (default: 1 hour) ) # Logging settings log_level: str = "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL log_format: str = "auto" # "json", "standard", or "auto" (json in production) # Initial admin user settings admin_password: str = "" # Initial admin password (if empty, uses 'changeme123') # JWT Authentication settings (optional, for external identity providers) jwt_enabled: bool = False # Enable JWT token validation jwt_secret: str = "" # Secret key for HS256, or leave empty for RS256 with JWKS jwt_algorithm: str = "HS256" # HS256 or RS256 jwt_issuer: str = "" # Expected issuer (iss claim), leave empty to skip validation jwt_audience: str = "" # Expected audience (aud claim), leave empty to skip validation jwt_jwks_url: str = "" # JWKS URL for RS256 (e.g., https://auth.example.com/.well-known/jwks.json) jwt_username_claim: str = ( "sub" # JWT claim to use as username (sub, email, preferred_username, etc.) ) @property def database_url(self) -> str: sslmode = f"?sslmode={self.database_sslmode}" if self.database_sslmode else "" return f"postgresql://{self.database_user}:{self.database_password}@{self.database_host}:{self.database_port}/{self.database_dbname}{sslmode}" @property def is_development(self) -> bool: return self.env.lower() == "development" @property def is_production(self) -> bool: return self.env.lower() == "production" class Config: env_prefix = "ORCHARD_" case_sensitive = False @lru_cache() def get_settings() -> Settings: return Settings()